fix(security): reject path traversal in build_script and validate default_jobs range

This commit is contained in:
2026-08-08 19:00:00 -04:00
parent 9473b01a79
commit 9963b8f2f5
5 changed files with 747 additions and 1 deletions
+19
View File
@@ -1418,3 +1418,22 @@ The sandbox test (test 2) creates a sentinel file, serves an index containing `o
- **Escape in unit tests**: all 8 pre-existing info.d tests used recipes WITH a `deps`
field, so the absent-deps path was never exercised. Regression tests must cover the
*negative* field, not just happy paths.
## F2 review — build_script path validation & default_jobs range check
### Path-traversal guard pattern (fetch.d)
- Any user-influenced path destined for `cacheDir ~ "/" ~ path` must be
validated: reject `..` substrings, leading `/` (absolute), and `\`
(Windows separators). Also verify the joined destination with
`startsWith(cacheDir ~ "/")` as defense in depth. Keep the check in a
small private @safe function so it is directly unit-testable without
spinning up the mock HTTP server.
- Valid relative paths (plain names, `scripts/build.sh`) are untouched —
proven by the existing custom-build integration test and smoketest.
### Range-check before cast (config.d)
- TOML integers parse as `long`; `cast(int)` silently truncates huge
values (5000000000 → negative). Always range-validate before casting
(1..1024 for jobs), warn to stderr, and fall back to the default. This
mirrors the existing `parseIntOr` warning pattern already used for env
vars.
+21
View File
@@ -36,3 +36,24 @@ everywhere else).
Verified: `dub build` ✓, `dub test` — 23 modules passed ✓, smoketest — 16 checks PASS ✓.
## RESOLVED (F2 review, 2026-08-08)
**Fixed:** Both F2 findings.
1. **BLOCKER — path traversal in fetch.d (`build_script`)**: extracted
`validateBuildScriptPath(string)` (private @safe) — rejects `..`
segments, leading `/`, and `\` with `FetchException("build_script path
is unsafe: <path>")`. Wired into `fetchRecipe` step 4 before building
`bsUrl`/`bsDest`, plus defense-in-depth containment check
(`bsDest.startsWith(cacheDir ~ "/")`). Added direct unit tests and an
integration test (recipe with `build_script = "../../evil.sh"` →
FetchException, no file escapes the cache).
2. **MAJOR — `cast(int)` truncation in config.d `default_jobs`**:
range-validate before casting. `tJobs < 1 || tJobs > 1024` → stderr
warning + fallback to 1. Added tests for 5000000000→1, 1024→1024,
2048→1, -3→1.
Verified: `dub build` ✓, `dub test` — 23 modules passed ✓,
smoketest — 16 checks PASS ✓.