Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions context/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,26 @@ See `SEARCH_GUIDE.md` for more search patterns.

---

## [2026-08-26] - security: OS command injection via shell:true build/scaffold sinks (ICM-50570)

External report (two confirmed PoCs, commit 30f5967): `normalizePath()` (`src/utils/index.ts`) blocks `..` traversal and absolute/Windows-drive paths but never sanitized shell metacharacters (`;`, `"`, `&`, `|`, `$`, backticks). Its output reached shell-executing sinks unescaped — `scaffold-fastedge-project` (`src/tools/local/scaffolding/scaffolds.ts`) built an `npx` command string for `child_process.exec` (always shell-backed) by interpolating the normalized `outputDir`; `build-wasm`'s JS/TS path (`src/tools/local/workspace/compiler/jsBuild.ts`) called `child_process.spawn(..., { shell: true })`. Either let an attacker-controlled `outputDir`/`entryFile` (from a malicious repo an agent scaffolds/builds, or a direct HTTP/SSE tool call) run arbitrary commands with the operator's `GCORE_API_KEY` in the process env.

**First draft (reverted) added a shell-metacharacter denylist to `normalizePath()` itself.** Codex (MoM) review caught that this was the wrong choke point: `normalizePath()` is also used by non-shell callers (`uploadBinary` in `src/tools/api/binaries/api.ts`, build-directory/tsconfig resolution in `src/tools/local/workspace/compiler/index.ts`), so the denylist rejected legitimate paths like `dist/app(v2).wasm` that never reach a shell. It also missed a third shell sink review didn't originally cover — see below — so patching the normalizer wasn't even sufficient on its own.

**Fix — remove the shell from every affected sink instead of sanitizing input for it**:

- `src/tools/local/scaffolding/scaffolds.ts` — `scaffold-fastedge-project` switched from `exec(command string)` to `execFile("npx", argsArray)`. `outputPath` is now passed as a discrete argv element, never concatenated into shell text.
- `src/tools/local/workspace/compiler/jsBuild.ts` — `spawn` no longer hardcodes `shell: true`.
- `src/tools/local/workspace/compiler/asBuild.ts` — same `spawn("npx", ascArgs, { shell: true })` pattern as `jsBuild.ts`, feeding the same `normalizePath`-derived `entryFilePath`/`outputFilePath`. Missed in the first pass; found by the Codex (MoM) review. Fixed the same way.
- All three: no `shell` option at all — not even conditionally on `win32`. An earlier revision of this fix set `shell: process.platform === "win32"`, to preserve `npx`'s ability to run as a `.cmd` shim on native Windows. GitHub Copilot's PR review (on #43) correctly pointed out that still left a real `cmd.exe` injection surface (`%`, `!`, `^`, and historically CVE-2024-27980) — and per `DEVELOPMENT.md`, this server has no supported native-Windows dev path anyway: both end users and in-house devs run it via Docker (`STANDALONE-SETUP.md`, `build-local.sh`). There's nothing to preserve, so the shell is off unconditionally. Native Windows use of `build-wasm`/`scaffold-fastedge-project` outside Docker is not supported.
- `src/utils/index.ts` — left unchanged (reverted to pre-fix behavior): traversal/absolute-path checks only, no metacharacter denylist.

`src/tools/local/workspace/compiler/rustBuild.ts` spawns `cargo` with `shell: "/bin/bash"` unconditionally, and interpolates a `target` value read from `.cargo/config.toml`/`Cargo.toml` in the cloned project (`rustConfigWasiTarget`) into `--target=${target}`. Flagged by Codex (MoM) review as a structurally similar (untrusted-file-content → shell arg) but distinct issue — not `normalizePath`-derived, and the repo comment says bash is intentional for the container's cargo/rustup shims. Not touched here; needs its own investigation before changing.

**Verified**: full `pnpm run test` suite (78 tests) and reference-index tests still pass; `normalizePath("/workspace", "dist/app(v2).wasm")` now resolves correctly instead of being rejected; the two PoC payloads still can't execute anything at any of the three sinks (no shell at all, on any platform, so they're inert argv/array elements, not shell text).

---

## [2026-08-25] - security: batch_execute policy bypass via resolved paths (ICM-50568)

External report: `batch_execute` ran `checkAllowed` on the **template** path (where `$name.field` is one opaque segment, so `/fastedge/v1/apps/$planted.v` matched `/fastedge/v1/apps/{app_id}`), then dispatched the **resolved** path with no second check. A prior step's data is untrusted (prompt injection, or just an API response containing free text), so `$planted.v = "../../../cdn/resources/123"` produced a request `new URL()` normalized to `/cdn/resources/123` — outside the allowlist, sent with the operator's `GCORE_API_KEY`.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
"author": "Gcore",
"license": "Apache-2.0",
"dependencies": {
"@gcoredev/fastedge-sdk-js": "^2.2.0",
"@modelcontextprotocol/sdk": "^1.25.2",
"@gcoredev/fastedge-sdk-js": "^2.5.1",
"@modelcontextprotocol/sdk": "^1.30.0",
"dedent": "^1.7.0",
"qs": "^6.14.0",
"toml": "^3.0.0",
Expand Down
Loading