CVE-2025-58158
Description
Harness Open Source is an end-to-end developer platform with Source Control Management, CI/CD Pipelines, Hosted Developer Environments, and Artifact Registries. Prior to version 3.3.0, Open Source Harness git LFS server (Gitness) exposes api to retrieve and upload files via git LFS. Implementation of upload git LFS file api is vulnerable to arbitrary file write. Due to improper sanitization for upload path, a malicious authenticated user who has access to Harness Gitness server api can use a crafted upload request to write arbitrary file to any location on file system, may even compromise the server. Users using git LFS are vulnerable. This issue has been patched in version 3.3.0.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/harness/gitnessGo | >= 1.0.4, < 3.3.0 | 3.3.0 |
github.com/harness/gitnessGo | < 1.0.4-gitspaces-beta.0.20250808064055-21c5ce42ae13 | 1.0.4-gitspaces-beta.0.20250808064055-21c5ce42ae13 |
Affected products
1Patches
2c7470a71a698CDE fix issues (#3639)
27 files changed · +368 −151
app/api/controller/gitspace/action.go+1 −1 modified@@ -79,7 +79,7 @@ func (c *Controller) Action( // All the actions should be idempotent. switch in.Action { case enum.GitspaceActionTypeStart: - err = c.gitspaceLimiter.Usage(ctx, space.ID) + err = c.gitspaceLimiter.Usage(ctx, space.ID, gitspaceConfig.InfraProviderResource.InfraProviderType) if err != nil { return nil, err }
app/api/controller/gitspace/create.go+6 −6 modified@@ -89,11 +89,6 @@ func (c *Controller) Create( return nil, err } - err = c.gitspaceLimiter.Usage(ctx, space.ID) - if err != nil { - return nil, err - } - // check if it's an internal repo if in.CodeRepoType == enum.CodeRepoTypeGitness && *in.CodeRepoRef != "" { repo, err := c.repoFinder.FindByRef(ctx, *in.CodeRepoRef) @@ -111,7 +106,7 @@ func (c *Controller) Create( } identifier, err := buildIdentifier(in.Identifier) if err != nil { - return nil, fmt.Errorf("could not generate identrifier for gitspace config : %q %w", in.Identifier, err) + return nil, fmt.Errorf("could not generate identifier for gitspace config : %q %w", in.Identifier, err) } now := time.Now().UnixMilli() var gitspaceConfig *types.GitspaceConfig @@ -150,6 +145,11 @@ func (c *Controller) Create( return nil, err } + err = c.gitspaceLimiter.Usage(ctx, space.ID, infraProviderResource.InfraProviderType) + if err != nil { + return nil, err + } + err = c.tx.WithTx(ctx, func(ctx context.Context) error { codeRepo := types.CodeRepo{ URL: in.CodeRepoURL,
app/api/controller/limiter/gitspace.go+4 −2 modified@@ -16,12 +16,14 @@ package limiter import ( "context" + + "github.com/harness/gitness/types/enum" ) // Gitspace is an interface for managing gitspace limitations. type Gitspace interface { // Usage checks if the total usage for the root space and all sub-spaces is under a limit. - Usage(ctx context.Context, spaceID int64) error + Usage(ctx context.Context, spaceID int64, infraProviderType enum.InfraProviderType) error } var _ Gitspace = (*UnlimitedUsage)(nil) @@ -34,6 +36,6 @@ func NewUnlimitedUsage() Gitspace { return UnlimitedUsage{} } -func (UnlimitedUsage) Usage(_ context.Context, _ int64) error { +func (UnlimitedUsage) Usage(_ context.Context, _ int64, _ enum.InfraProviderType) error { return nil }
app/gitspace/orchestrator/container/devcontainer_container_utils.go+1 −4 modified@@ -24,7 +24,6 @@ import ( "io" "os/exec" "path/filepath" - goruntime "runtime" "strconv" "strings" @@ -312,9 +311,7 @@ func prepareHostConfig( } extraHosts := getExtraHosts(runArgsMap) - if goruntime.GOOS == "linux" { - extraHosts = append(extraHosts, "host.docker.internal:host-gateway") - } + extraHosts = append(extraHosts, "host.docker.internal:host-gateway") restartPolicy, err := getRestartPolicy(runArgsMap) if err != nil {
app/gitspace/scm/scm.go+1 −1 modified@@ -119,7 +119,7 @@ func detectDefaultGitBranch(ctx context.Context, gitRepoDir string) (string, err ) output := &bytes.Buffer{} if err := cmd.Run(ctx, command.WithStdout(output)); err != nil { - return "", fmt.Errorf("failed to ls remote repo") + return "", fmt.Errorf("failed to ls remote repo: %w", err) } var lsRemoteHeadRegexp = regexp.MustCompile(`ref: refs/heads/([^\s]+)\s+HEAD`) match := lsRemoteHeadRegexp.FindStringSubmatch(strings.TrimSpace(output.String()))
types/enum/membership_role.go+6 −0 modified@@ -60,6 +60,11 @@ var membershipRoleContributorPermissions = slices.Clip(slices.Insert(membershipR PermissionArtifactsUpload, PermissionArtifactsDelete, + + PermissionGitspaceCreate, + PermissionGitspaceEdit, + PermissionGitspaceDelete, + PermissionGitspaceUse, )) var membershipRoleSpaceOwnerPermissions = slices.Clip(slices.Insert(membershipRoleReaderPermissions, 0, @@ -92,6 +97,7 @@ var membershipRoleSpaceOwnerPermissions = slices.Clip(slices.Insert(membershipRo PermissionTemplateDelete, PermissionTemplateEdit, + PermissionGitspaceCreate, PermissionGitspaceEdit, PermissionGitspaceDelete, PermissionGitspaceUse,
web/src/cde-gitness/components/CDEIDESelect/CDEIDESelect.tsx+9 −2 modified@@ -33,11 +33,18 @@ interface CDEIDESelectProps { isEditMode?: boolean } -export const CDEIDESelect = ({ onChange, selectedIde, filteredIdeOptions = [], isEditMode }: CDEIDESelectProps) => { +export const CDEIDESelect = ({ + onChange, + selectedIde, + filteredIdeOptions = [], + isEditMode = false +}: CDEIDESelectProps) => { const { getString } = useStrings() const selectedIDEOption = useMemo(() => { - if (!selectedIde) return undefined + if (!selectedIde) { + return undefined + } const foundOption = filteredIdeOptions.find(item => item.value === selectedIde) return foundOption || (filteredIdeOptions.length > 0 ? filteredIdeOptions[0] : undefined)
web/src/cde-gitness/components/DetailsCard/DetailsCard.module.scss+56 −0 modified@@ -6,8 +6,64 @@ --button-height: 0 !important; } +.detailsContainer { + display: grid !important; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)) !important; + gap: 20px; + + > div { + margin-bottom: 10px; + } +} + .horizontalContainer { :global(.Button--button--maDuR[class*='bp3-button']:not([class*='bp3-intent-']) > span svg path) { fill: var(--white) !important; } } + +.marginLeftContainer { + margin-left: var(--layout-spacing) !important; +} + +.clickableText { + cursor: pointer !important; + color: var(--primary-7) !important; + font-size: var(--font-size-normal) !important; + text-align: left !important; + margin-left: var(--spacing-small) !important; +} + +.iconMarginRight { + margin-right: 1px !important; +} + +.infoIcon { + height: 14px !important; + width: 14px !important; + fill: white !important; + color: #0278d5 !important; +} + +.standardIcon { + height: 20px !important; + width: 20px !important; + margin-right: 1px !important; +} + +.statusText { + color: var(--black) !important; + font-size: var(--font-size-normal) !important; + font-weight: 600 !important; + text-align: left !important; +} + +.providerText { + color: var(--grey-500) !important; + font-size: var(--font-size-normal) !important; + text-align: left !important; +} + +.greyText { + color: var(--grey-500) !important; +}
web/src/cde-gitness/components/DetailsCard/DetailsCard.module.scss.d.ts+9 −0 modified@@ -16,6 +16,15 @@ /* eslint-disable */ // This is an auto-generated file +export declare const clickableText: string +export declare const detailsContainer: string +export declare const greyText: string export declare const horizontalContainer: string +export declare const iconMarginRight: string export declare const infoButton: string +export declare const infoIcon: string +export declare const marginLeftContainer: string +export declare const providerText: string export declare const rowHeaders: string +export declare const standardIcon: string +export declare const statusText: string
web/src/cde-gitness/components/DetailsCard/DetailsCard.tsx+100 −38 modified@@ -16,30 +16,59 @@ import { Color } from '@harnessio/design-system' import { Button, ButtonVariation, Container, Layout, Text } from '@harnessio/uicore' -import React from 'react' +import React, { useEffect, useState } from 'react' import ReactTimeago from 'react-timeago' import { PopoverPosition } from '@blueprintjs/core' -import { Circle, InfoEmpty } from 'iconoir-react' +import { Circle, InfoEmpty, Cloud } from 'iconoir-react' import type { IconName } from '@harnessio/icons' +import Secret from 'cde-gitness/assests/secret.svg?url' import { useStrings } from 'framework/strings' import { GitspaceStatus } from 'cde-gitness/constants' import { getGitspaceChanges, getIconByRepoType } from 'cde-gitness/utils/SelectRepository.utils' -import type { TypesGitspaceConfig } from 'services/cde' +import type { TypesGitspaceConfig, TypesInfraProviderConfig } from 'services/cde' +import { useInfraListingApi } from 'cde-gitness/hooks/useGetInfraListProvider' import { getStatusColor, getStatusText } from '../GitspaceListing/ListGitspaces' +import getProviderIcon from '../../utils/InfraProvider.utils' import ResourceDetails from '../ResourceDetails/ResourceDetails' import { getRepoNameFromURL } from '../../utils/SelectRepository.utils' import css from './DetailsCard.module.scss' export const DetailsCard = ({ - data + data, + standalone }: { - data: TypesGitspaceConfig | TypesGitspaceConfig | null + data: TypesGitspaceConfig | null + standalone?: boolean loading?: boolean }) => { const { getString } = useStrings() const { branch, state, name, branch_url, code_repo_url, code_repo_type, instance, resource } = data || {} const repoName = getRepoNameFromURL(code_repo_url) || '' + const [infraProvidersList, setInfraProvidersList] = useState<TypesInfraProviderConfig[]>([]) + const { + data: infraProvidersData, + refetch: refetchInfraProviders, + loading: infraProvidersLoading + } = useInfraListingApi({ + queryParams: { + acl_filter: 'true' + } + }) + + useEffect(() => { + // Only fetch infra providers if not in standalone mode + if (!standalone) { + refetchInfraProviders() + } + }, [refetchInfraProviders, standalone]) + + useEffect(() => { + if (infraProvidersData) { + setInfraProvidersList(infraProvidersData) + } + }, [infraProvidersData]) + const { has_git_changes } = instance || {} const gitChanges = getGitspaceChanges(has_git_changes, getString, '--') const color = getStatusColor(state) @@ -52,24 +81,19 @@ export const DetailsCard = ({ : { icon: undefined } return ( <> - <Layout.Horizontal - width={'90%'} - flex={{ justifyContent: 'space-between' }} - padding={{ bottom: 'xlarge', top: 'xlarge' }}> - <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> + <Layout.Horizontal width={'90%'} className={css.detailsContainer} padding={{ bottom: 'xlarge', top: 'xlarge' }}> + <Layout.Vertical + spacing="small" + flex={{ justifyContent: 'center', alignItems: 'flex-start' }} + className={css.marginLeftContainer}> <Text className={css.rowHeaders}>{getString('cde.status')}</Text> <Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'start' }}> {state !== GitspaceStatus.STARTING && <Circle height={10} width={10} color={color} fill={color} />} - <Text - {...customProps} - color={Color.BLACK} - title={name} - font={{ align: 'left', size: 'normal', weight: 'semi-bold' }}> + <Text {...customProps} className={css.statusText} title={name}> {getStatusText(getString, state)} </Text> </Layout.Horizontal> </Layout.Vertical> - <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> <Text className={css.rowHeaders}>{getString('cde.repository.repo')}</Text> <Layout.Horizontal @@ -80,35 +104,75 @@ export const DetailsCard = ({ e.stopPropagation() }}> {getIconByRepoType({ repoType: code_repo_type, height: 20 })} - <Text - title={'RepoName'} - color={Color.PRIMARY_7} - margin={{ left: 'small' }} - style={{ cursor: 'pointer' }} - font={{ align: 'left', size: 'normal' }} - onClick={() => window.open(code_repo_url, '_blank')}> + <Text title={'RepoName'} className={css.clickableText} onClick={() => window.open(code_repo_url, '_blank')}> {repoName} </Text> </Layout.Horizontal> </Layout.Vertical> - <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> <Text className={css.rowHeaders}>{getString('branch')}</Text> <Text iconProps={{ size: 10 }} - color={Color.PRIMARY_7} icon="git-branch" - style={{ cursor: 'pointer' }} + className={css.clickableText} onClick={() => window.open(branch_url, '_blank')}> {branch} </Text> </Layout.Vertical> + {!standalone && ( + <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> + <Text className={css.rowHeaders}>{getString('cde.infraProvider')}</Text> + <Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'start' }}> + {(() => { + const providerType = resource?.infra_provider_type || '' + const providerConfigId = resource?.config_identifier || '' + const providerIcon = getProviderIcon(providerType) - <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> - <Text className={css.rowHeaders}>{getString('cde.regionMachineType')}</Text> - <ResourceDetails resource={resource} /> - </Layout.Vertical> + const provider = infraProvidersList.find(item => item.identifier === providerConfigId) + const displayName = provider?.name || providerConfigId + return ( + <> + {providerIcon ? ( + <img src={providerIcon} className={css.standardIcon} alt={'provider icon'} /> + ) : ( + <Cloud className={css.standardIcon} /> + )} + {infraProvidersLoading ? ( + <Text + icon="loading" + iconProps={{ size: 16 }} + lineClamp={1} + font={{ align: 'left', size: 'normal' }}></Text> + ) : ( + <Text lineClamp={1} className={css.providerText} title={displayName}> + {displayName} + </Text> + )} + </> + ) + })()} + </Layout.Horizontal> + </Layout.Vertical> + )} + {!standalone && ( + <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> + <Text className={css.rowHeaders}>{getString('cde.regionMachineType')}</Text> + <ResourceDetails resource={resource} /> + </Layout.Vertical> + )} + {/* Conditional SSH Key field - only shown when ssh_token_identifier exists */} + {data?.ssh_token_identifier && ( + <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> + <Text className={css.rowHeaders}>{'SSH Key'}</Text> + <Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'start' }}> + <img src={Secret} className={css.standardIcon} alt={'secret'} /> + <Text lineClamp={1} className={css.providerText} title={data.ssh_token_identifier}> + {data.ssh_token_identifier} + </Text> + </Layout.Horizontal> + </Layout.Vertical> + )} <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> <Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'start' }} @@ -127,16 +191,15 @@ export const DetailsCard = ({ </Container> } tooltipProps={{ isDark: true, position: PopoverPosition.AUTO }}> - <InfoEmpty height={14} color="#0278D5" fill="white" /> + <InfoEmpty className={css.infoIcon} /> </Button> </Layout.Horizontal> {instance?.active_time_started ? ( <ReactTimeago date={instance?.active_time_started || 0} /> ) : ( - <Text color={Color.GREY_500}>{getString('cde.na')}</Text> + <Text className={css.greyText}>{getString('cde.na')}</Text> )} </Layout.Vertical> - <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> <Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'start' }} @@ -155,16 +218,15 @@ export const DetailsCard = ({ </Container> } tooltipProps={{ isDark: true, position: PopoverPosition.AUTO }}> - <InfoEmpty height={14} color="#0278D5" fill="white" /> + <InfoEmpty className={css.infoIcon} /> </Button> </Layout.Horizontal> {instance?.last_used ? ( <ReactTimeago date={instance?.last_used || 0} /> ) : ( - <Text color={Color.GREY_500}>{getString('cde.na')}</Text> + <Text className={css.greyText}>{getString('cde.na')}</Text> )} </Layout.Vertical> - <Layout.Vertical spacing="small" flex={{ justifyContent: 'center', alignItems: 'flex-start' }}> <Layout.Horizontal flex={{ alignItems: 'center', justifyContent: 'start' }} @@ -183,10 +245,10 @@ export const DetailsCard = ({ </Container> } tooltipProps={{ isDark: true, position: PopoverPosition.AUTO }}> - <InfoEmpty height={14} color="#0278D5" fill="white" /> + <InfoEmpty className={css.infoIcon} /> </Button> </Layout.Horizontal> - <Text color={Color.GREY_500}>{gitChanges}</Text> + <Text className={css.greyText}>{gitChanges}</Text> </Layout.Vertical> </Layout.Horizontal> </>
web/src/cde-gitness/components/GitnessRepoImportForm/GitnessRepoImportForm.tsx+1 −1 modified@@ -210,7 +210,7 @@ export const GitnessRepoImportForm = ({ isCDE }: { isCDE?: boolean }) => { code_repo_url: repo.git_url, branch: repo.default_branch, identifier: repoParams?.[repoParams.length - 1], - name: repo.path, + name: '', code_repo_ref: repo.path, ...codeRepoType }
web/src/cde-gitness/components/GitspaceListing/ListGitspaces.module.scss+14 −2 modified@@ -24,7 +24,7 @@ div[class*='TableV2--cells'], div[class*='TableV2--header'] { display: grid !important; - grid-template-columns: 1fr 1fr 1fr 0.7fr 50px; + grid-template-columns: 1fr 1fr 1fr 0.7fr 50px !important; } } @@ -38,7 +38,7 @@ div[class*='TableV2--cells'], div[class*='TableV2--header'] { display: grid !important; - grid-template-columns: 1.2fr 1fr 1.5fr 0.75fr 0.75fr 1fr 50px; + grid-template-columns: 1.4fr 1.4fr 1.5fr 0.6fr 0.7fr 0.8fr 50px !important; } } @@ -107,3 +107,15 @@ fill: #004ba4; } } + +.standardProviderIcon { + height: 20px !important; + width: 20px !important; + margin-right: 1px !important; +} + +.providerNameText { + color: var(--black) !important; + font-size: var(--font-size-normal) !important; + text-align: left !important; +}
web/src/cde-gitness/components/GitspaceListing/ListGitspaces.module.scss.d.ts+2 −0 modified@@ -25,6 +25,8 @@ export declare const gitspaceIdContainer: string export declare const gitspaceUrl: string export declare const listContainer: string export declare const popover: string +export declare const providerNameText: string export declare const repositoryCell: string +export declare const standardProviderIcon: string export declare const stopModal: string export declare const table: string
web/src/cde-gitness/components/GitspaceListing/ListGitspaces.tsx+63 −16 modified@@ -30,7 +30,7 @@ import { Color } from '@harnessio/design-system' import type { Renderer, CellProps } from 'react-table' //import { Icon } from '@harnessio/icons' import ReactTimeago from 'react-timeago' -import { Circle, Cpu, Clock, Play, Db, ModernTv } from 'iconoir-react' +import { Circle, Cpu, Clock, Play, Db, ModernTv, Cloud } from 'iconoir-react' import { Intent, Menu, MenuItem, PopoverInteractionKind, Position } from '@blueprintjs/core' import { useHistory } from 'react-router-dom' import type { IconName } from '@harnessio/icons' @@ -45,6 +45,7 @@ import type { EnumIDEType, TypesGitspaceConfig, TypesInfraProviderResource, + TypesInfraProviderConfig, TypesGitspaceSettingsResponse } from 'services/cde' import gitspaceIcon from 'cde-gitness/assests/gitspace.svg?url' @@ -56,7 +57,9 @@ import { useGitspaceActions } from 'cde-gitness/hooks/useGitspaceActions' import { useDeleteGitspaces } from 'cde-gitness/hooks/useDeleteGitspaces' import { useOpenVSCodeBrowserURL } from 'cde-gitness/hooks/useOpenVSCodeBrowserURL' import { getGitspaceChanges, getIconByRepoType } from 'cde-gitness/utils/SelectRepository.utils' +import { useInfraListingApi } from 'cde-gitness/hooks/useGetInfraListProvider' import { usePaginationProps } from 'cde-gitness/hooks/usePaginationProps' +import getProviderIcon from '../../utils/InfraProvider.utils' import ResourceDetails from '../ResourceDetails/ResourceDetails' import CopyButton from '../CopyButton/CopyButton' import { EditGitspace } from '../EditGitspace/EditGitspace' @@ -164,16 +167,36 @@ export const RenderGitspaceName: Renderer< ) } -export const RenderRegionAndMachine: Renderer< - CellProps<TypesGitspaceConfig & { resource?: TypesInfraProviderResource }> -> = ({ row }) => { - const details = row.original - const { resource } = details - return ( - <Layout.Vertical spacing={'medium'}> - <ResourceDetails resource={resource} isListingPage={true} /> - </Layout.Vertical> - ) +const getRenderInfraProvider = (infraList: TypesInfraProviderConfig[], infraListLoading: boolean) => { + return ({ row }: CellProps<TypesGitspaceConfig & { resource?: TypesInfraProviderResource }>) => { + const details = row.original + const { resource } = details + const providerType = details.resource?.infra_provider_type || '' + const providerConfigId = details.resource?.config_identifier || '' + const providerIcon = getProviderIcon(providerType) + + const provider = infraList.find(item => item.identifier === providerConfigId) + const displayName = provider?.name || '' + return ( + <Layout.Vertical spacing={'medium'}> + <Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'start' }}> + {providerIcon ? ( + <img src={providerIcon} className={css.standardProviderIcon} /> + ) : ( + <Cloud className={css.standardProviderIcon} /> + )} + {infraListLoading ? ( + <Text icon="loading" iconProps={{ size: 16 }} lineClamp={1} className={css.providerNameText}></Text> + ) : ( + <Text lineClamp={1} color={Color.BLACK} title={displayName} className={css.providerNameText}> + {displayName} + </Text> + )} + </Layout.Horizontal> + <ResourceDetails resource={resource} /> + </Layout.Vertical> + ) + } } export const OwnerAndCreatedAt: Renderer<CellProps<TypesGitspaceConfig>> = ({ row }) => { @@ -775,6 +798,30 @@ export const ListGitspaces = ({ const { routes, standalone } = useAppContext() const [currentRow, setCurrentRow] = useState<TypesGitspaceConfig>() + const [infraProvidersList, setInfraProvidersList] = useState<TypesInfraProviderConfig[]>([]) + + // Fetch the infrastructure providers list + const { + data: infraProvidersData, + refetch: refetchInfraProviders, + loading: infraProvidersLoading + } = useInfraListingApi({ + queryParams: { + acl_filter: 'true' + } + }) + + useEffect(() => { + if (!standalone) { + refetchInfraProviders() + } + }, [refetchInfraProviders, standalone]) + + useEffect(() => { + if (infraProvidersData) { + setInfraProvidersList(infraProvidersData) + } + }, [infraProvidersData]) const [handleStartGitspace, hideStartModal] = useModalHook(() => { return ( @@ -855,13 +902,13 @@ export const ListGitspaces = ({ } ] - const regionColumn = standalone + const infraProviderColumn = standalone ? [] : [ { - id: 'gitsapceId', - Header: getString('cde.regionAndMachineSize'), - Cell: RenderRegionAndMachine + id: 'infraProvider', + Header: getString('cde.listing.infrastructureDetails'), + Cell: getRenderInfraProvider(infraProvidersList, infraProvidersLoading) // Pass the list here } ] @@ -924,7 +971,7 @@ export const ListGitspaces = ({ Header: getString('cde.gitspaces'), Cell: RenderGitspaceName }, - ...regionColumn, + ...infraProviderColumn, { id: 'repository', Header: getString('cde.repositoryAndBranch'),
web/src/cde-gitness/components/ResourceDetails/ResourceDetails.module.scss+20 −0 modified@@ -3,3 +3,23 @@ align-items: center; gap: 4px; } + +.container { + width: 100%; + overflow: hidden; +} + +.iconContainer { + flex-shrink: 0; +} + +.textContainer { + overflow: hidden; +} + +.truncatedText { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 88%; +}
web/src/cde-gitness/components/ResourceDetails/ResourceDetails.module.scss.d.ts+4 −0 modified@@ -16,4 +16,8 @@ /* eslint-disable */ // This is an auto-generated file +export declare const container: string +export declare const iconContainer: string export declare const iconTextStyle: string +export declare const textContainer: string +export declare const truncatedText: string
web/src/cde-gitness/components/ResourceDetails/ResourceDetails.tsx+10 −31 modified@@ -6,42 +6,21 @@ import { useStrings } from 'framework/strings' import type { TypesInfraProviderResource } from 'services/cde' import css from './ResourceDetails.module.scss' -const ResourceDetails = ({ - resource, - isListingPage = false -}: { - resource?: TypesInfraProviderResource - isListingPage?: boolean -}) => { +const ResourceDetails = ({ resource }: { resource?: TypesInfraProviderResource }) => { const { getString } = useStrings() const { region, name } = resource || {} - return isListingPage ? ( - <Layout.Vertical spacing="small"> - <span className={css.iconTextStyle}> - <img height={12} width={12} src={RegionIcon} /> - <Text font={{ size: 'small' }} lineClamp={1}> - {region?.toUpperCase() || getString('cde.na')} - </Text> - </span> - <span className={css.iconTextStyle}> - <Cpu height={12} width={12} /> - <Text font={{ size: 'small' }} lineClamp={1}> - {name || getString('cde.na')} - </Text> - </span> - </Layout.Vertical> - ) : ( - <Layout.Horizontal spacing="small"> - <Layout.Horizontal spacing={'xsmall'} flex={{ alignItems: 'center' }}> - <img height={12} width={12} src={RegionIcon} />{' '} - <Text font={{ size: 'small' }} lineClamp={1}> + return ( + <Layout.Horizontal spacing="small" className={css.container}> + <Layout.Horizontal spacing={'xsmall'} flex={{ alignItems: 'center' }} className={css.textContainer}> + <img height={12} width={12} src={RegionIcon} className={css.iconContainer} /> + <Text font={{ size: 'small' }} lineClamp={1} title={region?.toUpperCase() || getString('cde.na')}> {region?.toUpperCase() || getString('cde.na')} </Text> </Layout.Horizontal> - <Container width={'5px'} /> - <Layout.Horizontal spacing={'xsmall'} flex={{ alignItems: 'center' }}> - <Cpu height={12} width={12} />{' '} - <Text font={{ size: 'small' }} lineClamp={1}> + <Container width={'3px'} /> + <Layout.Horizontal spacing={'xsmall'} flex={{ alignItems: 'center' }} className={css.textContainer}> + <Cpu height={12} width={12} className={css.iconContainer} /> + <Text font={{ size: 'small' }} title={name || getString('cde.na')} lineClamp={1} className={css.truncatedText}> {name || getString('cde.na')} </Text> </Layout.Horizontal>
web/src/cde-gitness/components/SelectInfraProviderType/SelectInfraProviderType.tsx+2 −16 modified@@ -6,27 +6,13 @@ import { useFormikContext } from 'formik' import { Color } from '@harnessio/design-system' import { useStrings } from 'framework/strings' import type { OpenapiCreateGitspaceRequest, TypesInfraProviderConfig } from 'services/cde' -import { HARNESS_GCP, HYBRID_VM_GCP, HYBRID_VM_AWS } from 'cde-gitness/constants' +import { HARNESS_GCP } from 'cde-gitness/constants' import infrasvg from 'icons/Infrastructure.svg?url' -import googleCloudIcon from 'icons/google-cloud.svg?url' -import awsIcon from 'cde-gitness/assests/aws.svg?url' -import HarnessIcon from 'icons/Harness.svg?url' import type { dropdownProps } from 'cde-gitness/constants' +import getProviderIcon from '../../utils/InfraProvider.utils' import { CDECustomDropdown } from '../CDECustomDropdown/CDECustomDropdown' import css from './SelectInfraProviderType.module.scss' -// Function to get provider icon based on provider type -const getProviderIcon = (providerType: string) => { - if (providerType === HYBRID_VM_GCP) { - return googleCloudIcon - } else if (providerType === HARNESS_GCP) { - return HarnessIcon - } else if (providerType === HYBRID_VM_AWS) { - return awsIcon - } - return null -} - interface SelectInfraProviderTypeProps { infraProviders: dropdownProps[] allProviders?: TypesInfraProviderConfig[]
web/src/cde-gitness/components/ThirdPartyRepoImportForm/ThirdPartyRepoImportForm.tsx+2 −2 modified@@ -23,7 +23,7 @@ import { Color } from '@harnessio/design-system' import { useHistory } from 'react-router-dom' import { Icon } from '@harnessio/icons' import { useStrings } from 'framework/strings' -import { getRepoIdFromURL, getRepoNameFromURL, isValidUrl } from 'cde-gitness/utils/SelectRepository.utils' +import { getRepoIdFromURL, isValidUrl } from 'cde-gitness/utils/SelectRepository.utils' import { BranchInput } from 'cde-gitness/components/BranchInput/BranchInput' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import NewRepoModalButton from 'components/NewRepoModalButton/NewRepoModalButton' @@ -69,7 +69,7 @@ export const ThirdPartyRepoImportForm = () => { code_repo_url: response.url, branch: response.branch, identifier: getRepoIdFromURL(response.url), - name: getRepoNameFromURL(response.url), + name: '', code_repo_type: EnumGitspaceCodeRepoType.UNKNOWN } })
web/src/cde-gitness/pages/GitspaceCreate/CDECreateGitspace.tsx+13 −6 modified@@ -34,6 +34,7 @@ import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import { useStrings } from 'framework/strings' import { useAppContext } from 'AppContext' import { getErrorMessage } from 'utils/Utils' +import { useFindGitspaceSettings } from 'services/cde' import { GitspaceSelect } from 'cde-gitness/components/GitspaceSelect/GitspaceSelect' import harnessCode from 'cde-gitness/assests/harnessCode.svg?url' import codeSandboxIcon from 'cde-gitness/assests/codeSandboxLogo.svg?url' @@ -45,7 +46,7 @@ import bitbucket from 'cde-gitness/assests/bitbucket.svg?url' import { CDEAnyGitImport } from 'cde-gitness/components/CDEAnyGitImport/CDEAnyGitImport' import { CDEIDESelect } from 'cde-gitness/components/CDEIDESelect/CDEIDESelect' import { SelectInfraProvider } from 'cde-gitness/components/SelectInfraProvider/SelectInfraProvider' -import { OpenapiCreateGitspaceRequest, useCreateGitspace, TypesGitspaceSettingsResponse } from 'services/cde' +import { OpenapiCreateGitspaceRequest, useCreateGitspace } from 'services/cde' import { useGetCDEAPIParams } from 'cde-gitness/hooks/useGetCDEAPIParams' import { EnumGitspaceCodeRepoType, getIDETypeOptions, getIDEOption } from 'cde-gitness/constants' import { CDESSHSelect } from 'cde-gitness/components/CDESSHSelect/CDESSHSelect' @@ -71,10 +72,6 @@ export interface RepoQueryParams { codeRepoType?: EnumGitspaceCodeRepoType } -export interface CDECreateGitspaceProps { - gitspaceSettings: TypesGitspaceSettingsResponse | null -} - export const scmOptions: SCMType[] = [ { name: 'Harness Code', value: EnumGitspaceCodeRepoType.HARNESS_CODE, icon: harnessCode }, { name: 'GitHub Cloud', value: EnumGitspaceCodeRepoType.GITHUB, icon: github }, @@ -99,7 +96,7 @@ export const scmOptionsCDE: SCMType[] = [ ...onPremSCMOptions ] -export const CDECreateGitspace = ({ gitspaceSettings }: CDECreateGitspaceProps) => { +export const CDECreateGitspace = () => { const { getString } = useStrings() const { routes, currentUserProfileURL, hooks, currentUser } = useAppContext() const { useGetUserSourceCodeManagers } = hooks @@ -119,6 +116,16 @@ export const CDECreateGitspace = ({ gitspaceSettings }: CDECreateGitspaceProps) const [repoURLviaQueryParam, setrepoURLviaQueryParam] = useState<RepoQueryParams>({ ...repoQueryParams }) + const { data: gitspaceSettings, error: settingsError } = useFindGitspaceSettings({ + accountIdentifier + }) + + useEffect(() => { + if (settingsError) { + showError(getErrorMessage(settingsError)) + } + }, [settingsError, getString, showError]) + useEffect(() => { if (gitspaceSettings?.settings?.gitspace_config) { const { scm } = gitspaceSettings.settings.gitspace_config
web/src/cde-gitness/pages/GitspaceCreate/GitnessCreateGitspace.tsx+19 −6 modified@@ -27,12 +27,13 @@ import { useToaster } from '@harnessio/uicore' import { useHistory } from 'react-router-dom' -import { FontVariation } from '@harnessio/design-system' +import { Color, FontVariation } from '@harnessio/design-system' import { useCreateGitspace, type OpenapiCreateGitspaceRequest } from 'cde-gitness/services' import RepositoryTypeButton, { RepositoryType } from 'cde-gitness/components/RepositoryTypeButton/RepositoryTypeButton' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import { useStrings } from 'framework/strings' import { useAppContext } from 'AppContext' +import { getIDETypeOptions } from 'cde-gitness/constants' import codeSandboxIcon from 'cde-gitness/assests/codeSandboxLogo.svg?url' import { getErrorMessage } from 'utils/Utils' import { GitnessRepoImportForm } from 'cde-gitness/components/GitnessRepoImportForm/GitnessRepoImportForm' @@ -51,6 +52,9 @@ export const GitnessCreateGitspace = () => { const [activeButton, setActiveButton] = useState(RepositoryType.GITNESS) const { showSuccess, showError } = useToaster() const { mutate } = useCreateGitspace({}) + const allIdeOptions = useMemo(() => { + return getIDETypeOptions(getString) + }, [getString]) const suggestedName = useMemo(() => generateGitspaceName(), []) return ( @@ -76,7 +80,10 @@ export const GitnessCreateGitspace = () => { showError(getErrorMessage(error)) } }} - initialValues={gitnessFormInitialValues} + initialValues={{ + ...gitnessFormInitialValues, + ide: allIdeOptions.length > 0 ? allIdeOptions[0].value : getString('cde.create.ideEmpty') + }} validationSchema={validateGitnessForm(getString)} formName="importRepoForm" enableReinitialize> @@ -114,14 +121,16 @@ export const GitnessCreateGitspace = () => { <Layout.Horizontal className={css.leftSection}> <img src={codeSandboxIcon} alt="gitspace" className={css.icon} /> <Layout.Vertical className={css.textSection} spacing={'small'}> - <Text>{getString('cde.create.gitspaceNameLabel')}</Text> + <Text color={Color.GREY_500} font={{ weight: 'bold' }}> + {getString('cde.create.gitspaceNameLabel')} + </Text> <Layout.Vertical spacing={'xsmall'}> <Text font={'small'}>{getString('cde.create.gitspaceNameHelpertext1')}</Text> - <Layout.Horizontal> + <Layout.Horizontal spacing={'xsmall'}> <Text font={'small'}>{getString('cde.create.gitspaceNameHelpertext2')}</Text> <Text className={css.suggestedName} - font={{ variation: FontVariation.SMALL }} + font={'small'} onClick={e => { e.stopPropagation() formik.setFieldValue('name', suggestedName) @@ -141,7 +150,11 @@ export const GitnessCreateGitspace = () => { /> </Container> </Layout.Horizontal> - <CDEIDESelect onChange={formik.setFieldValue} selectedIde={formik.values.ide} /> + <CDEIDESelect + onChange={formik.setFieldValue} + selectedIde={formik.values.ide} + filteredIdeOptions={allIdeOptions} + /> <Button width={'100%'} variation={ButtonVariation.PRIMARY} height={50} type="submit"> {getString('cde.createGitspace')} </Button>
web/src/cde-gitness/pages/GitspaceCreate/GitspaceCreate.tsx+2 −13 modified@@ -20,8 +20,6 @@ import { Color } from '@harnessio/design-system' import { useStrings } from 'framework/strings' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import { useAppContext } from 'AppContext' -import { useFindGitspaceSettings } from 'services/cde' -import { useGetCDEAPIParams } from 'cde-gitness/hooks/useGetCDEAPIParams' import { GitnessCreateGitspace } from './GitnessCreateGitspace' import { CDECreateGitspace } from './CDECreateGitspace' import css from './GitspaceCreate.module.scss' @@ -30,15 +28,6 @@ const GitspaceCreate = () => { const { getString } = useStrings() const space = useGetSpaceParam() const { standalone, routes } = useAppContext() - const { accountIdentifier = '' } = useGetCDEAPIParams() - - const { - data: gitspaceSettings, - loading: settingsLoading, - error: settingsError - } = useFindGitspaceSettings({ - accountIdentifier - }) return ( <> @@ -53,7 +42,7 @@ const GitspaceCreate = () => { /> } /> - <Page.Body className={css.main} loading={settingsLoading} error={settingsError}> + <Page.Body className={css.main}> <Container className={css.titleContainer}> <Layout.Vertical spacing="small" margin={{ bottom: 'medium' }}> <Heading font={{ weight: 'bold' }} color={Color.BLACK} level={2}> @@ -64,7 +53,7 @@ const GitspaceCreate = () => { </Container> <Card className={css.cardMain}> <Container className={css.subContainers}> - {standalone ? <GitnessCreateGitspace /> : <CDECreateGitspace gitspaceSettings={gitspaceSettings} />} + {standalone ? <GitnessCreateGitspace /> : <CDECreateGitspace />} </Container> </Card> </Page.Body>
web/src/cde-gitness/pages/GitspaceDetails/GitspaceDetails.tsx+3 −3 modified@@ -276,7 +276,7 @@ const GitspaceDetails = () => { error: settingsError } = useFindGitspaceSettings({ accountIdentifier: accountIdentifier || '', - lazy: !accountIdentifier + lazy: !accountIdentifier || standalone }) const { refetchToken, setSelectedRowUrl } = useOpenVSCodeBrowserURL() @@ -575,7 +575,7 @@ const GitspaceDetails = () => { )} <Card className={css.cardContainer}> <Text font={{ variation: FontVariation.CARD_TITLE }}>{getString('cde.gitspaceDetail')}</Text> - <DetailsCard data={data} loading={mutateLoading} /> + <DetailsCard data={data} standalone={standalone} loading={mutateLoading} /> </Card> <Card className={css.cardContainer}> <EventTimelineAccordion data={eventData as TypesGitspaceEventResponse[]} /> @@ -660,7 +660,7 @@ const GitspaceDetails = () => { }}> <EditGitspace isOpen={isEditModalOpen} - gitspaceSettings={gitspaceSettings} + gitspaceSettings={gitspaceSettings || null} onClose={() => setIsEditModalOpen(false)} gitspaceId={gitspaceId} onGitspaceUpdated={() => {
web/src/cde-gitness/strings/strings.en.yaml+1 −0 modified@@ -312,6 +312,7 @@ listing: uninitialized: Uninitialized cleaning: Cleaning ownerAndCreated: Owner & Created On + infrastructureDetails: Infrastructure Details create: repositoryDetails: Repository Details gitnessRepositories: Harness Repositories
web/src/cde-gitness/utils/InfraProvider.utils.ts+17 −0 added@@ -0,0 +1,17 @@ +import googleCloudIcon from 'icons/google-cloud.svg?url' +import awsIcon from 'cde-gitness/assests/aws.svg?url' +import HarnessIcon from 'icons/Harness.svg?url' +import { HARNESS_GCP, HYBRID_VM_GCP, HYBRID_VM_AWS } from 'cde-gitness/constants' + +const getProviderIcon = (providerType: string) => { + if (providerType === HYBRID_VM_GCP) { + return googleCloudIcon + } else if (providerType === HARNESS_GCP) { + return HarnessIcon + } else if (providerType === HYBRID_VM_AWS) { + return awsIcon + } + return null +} + +export default getProviderIcon
web/src/cde-gitness/utils/SelectRepository.utils.tsx+1 −1 modified@@ -65,7 +65,7 @@ export const getIconByRepoType = ({ }): React.ReactNode => { const scmOption = [...scmOptions, ...onPremSCMOptions].find(option => option.value === repoType) return ( - <img height={height} width={height} src={defaultTo(scmOption?.icon, genericGit)} style={{ marginRight: '10px' }} /> + <img height={height} width={height} src={defaultTo(scmOption?.icon, genericGit)} style={{ marginRight: '4px' }} /> ) }
web/src/framework/strings/stringTypes.ts+1 −0 modified@@ -1602,6 +1602,7 @@ export interface StringsMap { 'cde.lastUsedTooltip': string 'cde.listing.cleaning': string 'cde.listing.error': string + 'cde.listing.infrastructureDetails': string 'cde.listing.offline': string 'cde.listing.online': string 'cde.listing.ownerAndCreated': string
21c5ce42ae13fix: [CODE-4271] Git LFS upload vulnerability with random OID (#4235)
1 file changed · +31 −4
app/api/controller/lfs/upload.go+31 −4 modified@@ -15,11 +15,14 @@ package lfs import ( - "bufio" + "bytes" "context" + "crypto/sha256" + "encoding/hex" "errors" "fmt" "io" + "strings" "time" "github.com/harness/gitness/app/api/usererror" @@ -50,10 +53,34 @@ func (c *Controller) Upload(ctx context.Context, return nil, usererror.BadRequest("no file or content provided") } - bufReader := bufio.NewReader(io.LimitReader(file, pointer.Size)) + _, err = c.lfsStore.Find(ctx, repoCore.ID, pointer.OId) + if err != nil && !errors.Is(err, store.ErrResourceNotFound) { + return nil, fmt.Errorf("failed to check if object exists: %w", err) + } + if err == nil { + return nil, usererror.Conflict("LFS object already exists and cannot be modified") + } + + limitedReader := io.LimitReader(file, pointer.Size) + content, err := io.ReadAll(limitedReader) + if err != nil { + return nil, fmt.Errorf("failed to read uploaded content: %w", err) + } + + hasher := sha256.New() + hasher.Write(content) + calculatedHash := hex.EncodeToString(hasher.Sum(nil)) + + expectedHash := strings.TrimPrefix(pointer.OId, "sha256:") + + if calculatedHash != expectedHash { + return nil, usererror.BadRequest("content hash doesn't match provided OID") + } + + contentReader := bytes.NewReader(content) objPath := getLFSObjectPath(pointer.OId) - err = c.blobStore.Upload(ctx, bufReader, objPath) + err = c.blobStore.Upload(ctx, contentReader, objPath) if err != nil { return nil, fmt.Errorf("failed to upload file: %w", err) } @@ -70,7 +97,7 @@ func (c *Controller) Upload(ctx context.Context, // create the object in lfs store after successful upload to the blob store. err = c.lfsStore.Create(ctx, object) if err != nil && !errors.Is(err, store.ErrDuplicate) { - return nil, fmt.Errorf("failed to find object: %w", err) + return nil, fmt.Errorf("failed to create object: %w", err) } return &UploadOut{
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.