From b200c5cb46ac7ef943d03d1921a83fc269fae000 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Sat, 19 Sep 2026 07:42:41 -0500 Subject: [PATCH] fix(security): nombrar el .csproj desde el directorio validado, no desde input.name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Última js/path-injection abierta tras #737 (análisis 19d736da): el scaffolder .NET volvía a leer `input.name` como propiedad al escribir `${projectDir}/${input.name}.csproj`. Es la misma cadena que el use case validó, pero CodeQL solo ve el sanitizador sobre la variable local, no sobre una segunda lectura de la propiedad. `path.basename(projectDir)` es exactamente ese valor (`projectDir` se construye como `${cwd}/${name}`) y ya está saneado para el motor. Test: `Billing.Api` produce `Billing.Api.csproj`. Co-Authored-By: Claude Opus 5 --- .../src/application/services/project-scaffolder.service.ts | 7 ++++++- .../use-cases/initialize-project.use-case.spec.ts | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/packages/core-domain/src/application/services/project-scaffolder.service.ts b/src/packages/core-domain/src/application/services/project-scaffolder.service.ts index 2d46f9504..b309582b7 100644 --- a/src/packages/core-domain/src/application/services/project-scaffolder.service.ts +++ b/src/packages/core-domain/src/application/services/project-scaffolder.service.ts @@ -1,6 +1,7 @@ import { IFileSystem } from '../../domain/interfaces'; import { IPlatformProviders } from '../ports/platform-detection.port'; import { InitProjectInput } from './use-case.types'; +import * as path from 'path'; /** * The commit types GIT-08 itself enumerates, in its own `pattern`. Kept in the @@ -185,7 +186,11 @@ evolith sdlc gate-status `; - await this.fs.writeFile(`${projectDir}/${input.name}.csproj`, csproj); + // The .csproj takes its name from the directory the use case already + // validated and built (`${cwd}/${name}`), not from a second read of + // `input.name`: same string, but CodeQL only sees the sanitizer on the + // variable, and this property read kept js/path-injection open on the sink. + await this.fs.writeFile(`${projectDir}/${path.basename(projectDir)}.csproj`, csproj); await this.fs.ensureDir(`${projectDir}/src`); } diff --git a/src/packages/core-domain/src/application/use-cases/initialize-project.use-case.spec.ts b/src/packages/core-domain/src/application/use-cases/initialize-project.use-case.spec.ts index 1bca52413..b845f27ad 100644 --- a/src/packages/core-domain/src/application/use-cases/initialize-project.use-case.spec.ts +++ b/src/packages/core-domain/src/application/use-cases/initialize-project.use-case.spec.ts @@ -83,6 +83,11 @@ describe('InitializeProjectUseCase · the name is a directory, not a path (CWE-2 }, ); + it('names the .csproj after the validated directory, never a second read of input.name', async () => { + const { fs, root } = await init({ name: 'Billing.Api', runtime: 'dotnet' }); + expect(fs.files.has(`${root}/Billing.Api.csproj`)).toBe(true); + }); + it('keeps accepting the dotted and dashed names npm does', async () => { const { root } = await init({ name: 'acme.billing-api_v2' }); expect(root).toBe('/tmp/acme.billing-api_v2');