From d52b978d98d297236ce2f0c1dd3f19f363dbe5a1 Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Mon, 27 Jul 2026 10:00:04 +0200 Subject: [PATCH 1/2] fix: ensure resolved stays inside base path --- apps/cli-go/internal/utils/denos/extract.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/cli-go/internal/utils/denos/extract.ts b/apps/cli-go/internal/utils/denos/extract.ts index ab25df1313..0b64f5859e 100644 --- a/apps/cli-go/internal/utils/denos/extract.ts +++ b/apps/cli-go/internal/utils/denos/extract.ts @@ -32,6 +32,18 @@ async function extractEszip( const absPath = path.fromFileUrl(specifier); const relPath = path.relative(basePath, absPath); const dest = path.join(destPath, relPath); + + const resolvedRoot = path.resolve(destPath); + const resolvedDest = path.resolve(dest); + if ( + resolvedDest !== resolvedRoot && + !resolvedDest.startsWith(resolvedRoot + path.sep) + ) { + throw new Error( + `Refusing to extract "${specifier}" outside of ${destPath}`, + ); + } + console.info(path.resolve(dest)); await write(dest, module); } From 72499b9d0dae3bdd226146b0e6687a377304d8cf Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Mon, 27 Jul 2026 10:20:53 +0200 Subject: [PATCH 2/2] fix: codex review --- .../internal/functions/download/download.go | 2 +- apps/cli-go/internal/utils/denos/extract.ts | 24 ++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/cli-go/internal/functions/download/download.go b/apps/cli-go/internal/functions/download/download.go index 3e3737ba1e..635296dd8f 100644 --- a/apps/cli-go/internal/functions/download/download.go +++ b/apps/cli-go/internal/functions/download/download.go @@ -116,7 +116,7 @@ func downloadFunction(ctx context.Context, projectRef, slug, extractScriptPath s resBuf := bytes.NewReader(resp.Body) funcDir := filepath.Join(utils.FunctionsDir, slug) - args := []string{"run", "-A", extractScriptPath, funcDir, *meta.EntrypointPath} + args := []string{"run", "-A", extractScriptPath, funcDir, *meta.EntrypointPath, utils.FunctionsDir} cmd := exec.CommandContext(ctx, denoPath, args...) var errBuf bytes.Buffer cmd.Stdin = resBuf diff --git a/apps/cli-go/internal/utils/denos/extract.ts b/apps/cli-go/internal/utils/denos/extract.ts index 0b64f5859e..1e47399677 100644 --- a/apps/cli-go/internal/utils/denos/extract.ts +++ b/apps/cli-go/internal/utils/denos/extract.ts @@ -17,11 +17,18 @@ async function loadEszip(bytes: Uint8Array) { async function extractEszip( destPath: string, entrypointUrl: string, + rootPath: string, parser: Parser, specifiers: string[], ) { const entrypointPath = path.fromFileUrl(entrypointUrl); const basePath = path.dirname(entrypointPath); + // Containment is enforced against rootPath (the shared functions + // directory), not destPath (this function's own directory): a common + // layout imports a sibling like "../_shared/cors.ts", which legitimately + // resolves one level above destPath, but must never resolve outside + // rootPath entirely. + const resolvedRoot = path.resolve(rootPath); for (const specifier of specifiers) { // skip remote dependencies if (!specifier.startsWith("file:")) { @@ -33,14 +40,15 @@ async function extractEszip( const relPath = path.relative(basePath, absPath); const dest = path.join(destPath, relPath); - const resolvedRoot = path.resolve(destPath); const resolvedDest = path.resolve(dest); + const relToRoot = path.relative(resolvedRoot, resolvedDest); if ( - resolvedDest !== resolvedRoot && - !resolvedDest.startsWith(resolvedRoot + path.sep) + relToRoot === ".." || + relToRoot.startsWith(`..${path.SEP}`) || + path.isAbsolute(relToRoot) ) { throw new Error( - `Refusing to extract "${specifier}" outside of ${destPath}`, + `Refusing to extract "${specifier}" outside of ${rootPath}`, ); } @@ -49,11 +57,15 @@ async function extractEszip( } } -async function extractSource(destPath: string, entrypointUrl: string) { +async function extractSource( + destPath: string, + entrypointUrl: string, + rootPath: string, +) { const buf = await readAll(Deno.stdin); const { parser, specifiers } = await loadEszip(buf); - await extractEszip(destPath, entrypointUrl, parser, specifiers); + await extractEszip(destPath, entrypointUrl, rootPath, parser, specifiers); } extractSource(...Deno.args);