Skip to content

fix(rules): confine the raw path, not the redacted target (security C1)#89

Merged
fu351 merged 1 commit into
mainfrom
fix/f3-path-confinement/raw-path-match
Jul 9, 2026
Merged

fix(rules): confine the raw path, not the redacted target (security C1)#89
fu351 merged 1 commit into
mainfrom
fix/f3-path-confinement/raw-path-match

Conversation

@fu351

@fu351 fu351 commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Slice

  • Repo: doberman-core
  • Feature / Slice: F3 — security fix C1 (path-confinement raw-path match)
  • Plan reference: security review, 2026-07-09 (finding C1)

What this PR does

Closes a confirmed path-confinement bypass. ProtectedPathRule evaluated the redacted action.target: normalize replaces any string arg >256 chars (or secret-shaped) with the literal "<redacted>" before it becomes action.target, so a padded ../ traversal (e.g. ("../"*90)+"etc/passwd", 270 chars) — or a padded path that canonicalizes to a protected/sensitive glob — was confined as the harmless marker, returned PASS, and the executor forwarded the real path downstream. This defeated both the escapes_root block and the protected-path glob block.

The rule now confines the raw path from ctx.metadata['raw_arguments'] (the same un-redacted args the secret and destructive-command rules already read), falling back to action.target/metadata['raw_paths'] when raw args are absent. The raw path is used for matching only — it never enters action.target, action.metadata, or any log field (build_record still excludes metadata; the persisted target_path_class stays redacted).

No import-boundary change: the key list is local to engine/rules/paths.py; engine does not import proxy (lint-imports green).

Tests added (run in CI)

  • tests/unit/test_path_confinement_raw.py (9 tests): padded-traversal → BLOCK; padded blocked-glob → BLOCK; padded sensitive-glob → AUTH; batch raw paths escalate to the worst member; raw path never leaks into action.target or any build_record field; short .env still BLOCKs; normal short path still PASSes; a ctx with no raw_arguments falls back to action.target. The 4 bypass tests return PASS on main and BLOCK/AUTH with this fix.

Public-release safety (doberman-core only)

  • Contains nothing from the "not allowed" list: no enterprise/hosted code, no proprietary detection, no customer data, no secrets, no commercial-license code
  • Core still builds/tests/runs with NO enterprise package installed

Security checklist

  • Fails closed on error / uncertainty (canonicalize still blocks on error — unchanged)
  • No secret, full file, or unredacted prompt logged or committed (raw path used for matching only; regression test asserts it never appears in action.target or the log record)
  • Any guardrail/learning change is raise-only (strictly raises — a previously-PASS bypass now BLOCK/AUTH; no loosening)
  • Every BLOCK/AUTH carries reason codes + a human explanation (reuses protected_path_blocked/sensitive_path_access + existing static explanations; no raw path in the explanation)
  • doberman-core does not import doberman_enterprise

Edge cases covered / Deviations from plan / Risks introduced

  • _RAW_PATH_KEYS is deliberately the file-path keys (path/file/filename/target), not url/command — including those would make the rule canonicalize URLs/commands as paths and risk false positives; the file actions ProtectedPathRule guards carry their path in these keys.
  • Fallback preserves existing behavior for callers/tests without raw_arguments.
  • Full suite green locally: 1363 tests, 91% coverage, ruff + lint-imports clean.
  • Risk: none introduced; strictly closes a bypass (raise-only).

…rget

A path value >256 chars (or secret-shaped) was redacted to "<redacted>" before ProtectedPathRule ran, so a padded traversal or a padded path resolving to a protected glob canonicalized to a harmless in-root string and PASSed while the executor forwarded the real path. The rule now confines the raw path from ctx.metadata['raw_arguments'] (falling back to action.target); the raw path is used for matching only and never enters action.target or any log field. Adds tests/unit/test_path_confinement_raw.py (padded-traversal BLOCK, padded blocked/sensitive globs, batch, redaction-preserved, no-regression).
@fu351 fu351 merged commit d71d402 into main Jul 9, 2026
6 of 9 checks passed
@fu351 fu351 deleted the fix/f3-path-confinement/raw-path-match branch July 9, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant