Skip to content

fix(security): D-4 — file_write blocks ~/.codec/ and <repo>/skills/#45

Merged
AVADSA25 merged 2 commits into
mainfrom
fix/pr1c-file-write-block-roots
May 17, 2026
Merged

fix(security): D-4 — file_write blocks ~/.codec/ and <repo>/skills/#45
AVADSA25 merged 2 commits into
mainfrom
fix/pr1c-file-write-block-roots

Conversation

@AVADSA25
Copy link
Copy Markdown
Owner

What

Phase 1 Wave 1 — PR-1C. Closes D-4 (CRITICAL) — see docs/audits/PHASE-1-SECURITY.md finding D-4 and the consolidated triage.

Before this PR, the file_write skill (SKILL_MCP_EXPOSE=True, NOT in _HTTP_BLOCKED) gave claude.ai over the 30-day OAuth token a write-path to every security-sensitive path on the machine:

Reachable target (before) What it enables
~/.codec/skills/<x>.py Drop a malicious skill → restart → RCE
~/.codec/plugins/<x>.py Drop a plugin → wraps every tool call
~/.codec/oauth_state.json Tamper with bearer tokens
~/.codec/audit.log Erase forensic evidence
~/.codec/config.json Exfiltrate API keys
~/.codec/memory.db Poison conversation memory
~/.codec/agents/<id>/* Tamper with Step 9 runtime state
<repo>/skills/<x>.py Contaminate the trusted manifest

The old _BLOCKED_ROOTS only listed /System, /etc, etc. — none of these were caught.

How

skills/file_write.py:_is_safe_target refactored with three changes:

  1. Realpath the blocklist at module load. _BLOCKED_SYSTEM_ROOTS is resolved via os.path.realpath so macOS aliases match regardless of which name the caller uses. (/etc → /private/etc, /bin → /usr/bin, etc.)
  2. Block the entire ~/.codec/ tree. One rule covers every CODEC state file at once — current and future. file_write is for user-facing files (Documents, Desktop, code projects). It has no business writing into CODEC's own state directory.
  3. Block <repo>/skills/. Defense in depth with PR-1A's manifest: never let file_write reach a directory where the load-time gate would catch the tampering.

Pre-existing bug also fixed (side effect)

The old code hard-coded /private in _BLOCKED_ROOTS, and macOS realpaths /tmp → /private/tmp. So every write to /tmp tripped the /private block. The refactor drops /private (its dangerous subdirs /private/etc and /private/var are still covered via realpath of /etc and /var) and realpath-resolves /tmp + $HOME in the sanity check. Test test_accepts_tmp_dir surfaced this in the RED phase.

Tests

+21 new tests in tests/test_file_write.py (TDD: RED → GREEN).

  • 13 refusal tests for ~/.codec/skills/, ~/.codec/plugins/, ~/.codec/oauth_state.json, ~/.codec/audit.log, ~/.codec/config.json, ~/.codec/memory.db, ~/.codec/agents/*/state.json, ~/.codec/agent_global_grants.json, ~/.codec/pending_questions.json, ~/.codec/triggers_killed.json, <repo>/skills/, arbitrary ~/.codec/<file>, plus a symlink-into-codec test (creates a symlink in tmp_path pointing at ~/.codec/skills/, verifies realpath resolves it and the write is refused).
  • 5 acceptance tests for ~/Documents, ~/Desktop, /tmp, ~/Projects, ~/codec-workspace — regression checks so the skill's utility is preserved.
  • 3 pre-existing protections verified still active: /etc/passwd, ~/.ssh/id_rsa, .env files.

Files

File Change
skills/file_write.py Refactor _is_safe_target + new _BLOCKED_SYSTEM_ROOTS / _codec_blocked_roots / _build_blocked_roots helpers + realpath-resolved _BLOCKED_ROOTS_REAL. +89/−10 LOC. Backward-compat alias _BLOCKED_ROOTS preserved.
skills/.manifest.json Regenerated — file_write.py hash updated. Still 74 skills total.
tests/test_file_write.py NEW. 21 tests, +220 LOC.
docs/audits/PHASE-1-SECURITY.md D-4 closure footnote.
docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md D-4 row → W1 — CLOSED (PR-1C).

Diff: 5 files, +299/−16.

Verification

Check Result
pytest tests/test_file_write.py 21 passed (TDD: watched 14 fail, then GREEN)
pytest tests/test_skill_routes.py tests/test_skill_registry.py tests/test_skill_contracts.py tests/test_oauth_provider.py tests/test_retry.py 28 passed (PR-1A + PR-1B suites still green)
python3 tests/test_skill_imports.py 76 skills parsed, 0 errors
python3 tools/generate_skill_manifest.py --check ok
ruff check skills/file_write.py + tests/test_file_write.py 6 pre-existing errors (E402 + F401), same as main — no new ruff errors introduced

Out of scope (later)

  • PR-1D — D-5 permission_gate realpath + _PATH_BLOCKLIST_SUBSTRINGS extension (last of the four D-1 enabling paths).
  • Optional PR-1E — positive allowlist refactor of is_dangerous_skill_code (closes D-17).
  • The pre-existing E402 ruff errors in skill files are a Wave 3 / A-3 cleanup.

Test plan

  • pytest tests/test_file_write.py -v — 21 passed
  • Full regression on Wave 1 suites — all green
  • Manifest current
  • No new ruff errors vs main
  • CI green on PR
  • Manual verification once running locally:
    • Try file_write skill with task="save to ~/.codec/skills/x.py\n```\npayload\n```" → refused
    • Try file_write with task="save to ~/Documents/notes.md\n```\nhi\n```" → succeeds
    • Try file_write with task="save to /tmp/test.log\n```\nhi\n```" → succeeds (previously was silently broken)
  • Mickael chat review

🤖 Generated with Claude Code

Mikarina13 and others added 2 commits May 17, 2026 13:56
Closes D-4 (CRITICAL) per docs/audits/PHASE-1-SECURITY.md. Before this
PR, the file_write skill (SKILL_MCP_EXPOSE=True, NOT in _HTTP_BLOCKED)
gave claude.ai over the 30-day OAuth token a write-path to any of:

  ~/.codec/skills/<x>.py    → drop a malicious skill, restart, RCE
  ~/.codec/plugins/<x>.py   → drop a plugin, wraps every tool call
  ~/.codec/oauth_state.json → tamper with bearer tokens
  ~/.codec/audit.log        → erase forensic evidence
  ~/.codec/config.json      → exfiltrate API keys
  ~/.codec/memory.db        → poison conversation memory
  ~/.codec/agents/<id>/*    → tamper with Step 9 runtime state
  <repo>/skills/<x>.py      → contaminate the trusted manifest

The old _BLOCKED_ROOTS only listed /System, /etc, etc. — none of the
above are caught.

Fix: refactor _is_safe_target with three changes.

1. Realpath the blocklist at module load.
   _BLOCKED_SYSTEM_ROOTS gets resolved via os.path.realpath so macOS
   aliases work regardless of which name the caller uses (/etc both
   matches a write to /etc/passwd AND a write to /private/etc/passwd
   via realpath of the parent). /bin → /usr/bin, /sbin → /usr/sbin
   resolved redundantly with /usr; harmless.

2. Block the entire ~/.codec/ tree.
   The file_write skill is for user-facing files (Documents, Desktop,
   code projects). It has no business writing into CODEC's own state
   directory. One rule covers skills, plugins, oauth_state.json,
   audit.log, config.json, memory.db, agents/, notifications.json,
   pending_questions.json, agent_global_grants.json, triggers_killed
   .json — and any future ~/.codec/* state file added by later phases.

3. Block <repo>/skills/.
   PR-1A's trusted-skill manifest hash-pins every built-in. If
   file_write could overwrite one of them, the hash would no longer
   match the manifest, and the load-time AST gate would kick in to
   refuse load — but that's defense-in-depth, not primary defense.
   The primary defense is: never let file_write reach a skill dir.

Pre-existing bug also fixed as a side effect:
   /tmp writes were silently failing.
   The old code hard-coded "/private" in _BLOCKED_ROOTS, and macOS
   realpaths /tmp → /private/tmp. So every write to /tmp tripped
   the /private block and returned "Blocked system path: /private"
   — even though the home/tmp sanity check at line 100-107 was
   intended to allow it. The refactor drops "/private" from the
   blocklist (its dangerous subdirs /private/etc and /private/var
   are still covered via realpath of /etc and /var) and realpath-
   resolves /tmp + $HOME in the sanity check.

Files:
- skills/file_write.py: refactored _is_safe_target (+89 LOC, mostly
  new _BLOCKED_SYSTEM_ROOTS / _codec_blocked_roots / _build_blocked
  _roots helpers + realpath-resolved _BLOCKED_ROOTS_REAL). Backward-
  compat alias _BLOCKED_ROOTS preserved for any external reader.
- skills/.manifest.json: regenerated to reflect new file_write.py
  hash (still 74 skills total, file_write entry updated).
- tests/test_file_write.py: NEW, 21 tests covering refusal of every
  ~/.codec/* sensitive path + repo skills dir + symlink resolution +
  regression checks on ~/Documents, ~/Desktop, /tmp, ~/Projects,
  ~/codec-workspace + existing /etc/.ssh/.env protections.
- docs/audits/PHASE-1-SECURITY.md: D-4 closure footnote.
- docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md: D-4 row → "W1 —
  CLOSED (PR-1C)".

Verification:
- pytest tests/test_file_write.py -v → 21 passed (TDD: written
  first, watched fail with 13 expected failures + 1 surprise pre-
  existing bug, then implemented, then all 21 green)
- pytest tests/test_skill_routes.py tests/test_skill_registry.py
  tests/test_skill_contracts.py tests/test_oauth_provider.py
  tests/test_retry.py → 28 passed (PR-1A + PR-1B suites still
  green)
- python3 tests/test_skill_imports.py → 76 skills parsed, 0 errors
- python3 tools/generate_skill_manifest.py --check → ok
- ruff check skills/file_write.py tests/test_file_write.py → 6
  pre-existing errors (E402 on the skill metadata-before-imports
  convention + 1 unused-import F401), identical to main. No new
  ruff errors introduced.

Out of scope (later in Wave 1 / Wave 2):
- D-5 permission_gate realpath + _PATH_BLOCKLIST_SUBSTRINGS (PR-1D).
- D-17 positive-allowlist for is_dangerous_skill_code (optional
  PR-1E).
- Pre-existing E402 in skill files (broader cleanup — Wave 3 / A-3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Addresses the two scope items from the original PR-1C prompt that the
first commit missed: (3) emit an audit event when file_write refuses
a target, and (4) update AGENTS.md §7 to document the path-blocking
rule for contributors.

skills/file_write.py:
- run() now emits log_event("file_write_blocked", ...) before
  returning the refusal message. Forensic visibility: any MCP-client
  attempt to write to ~/.codec/skills/, ~/.codec/plugins/, or any
  other blocked path lands in ~/.codec/audit.log with the resolved
  target_path, the original requested_path, and the reason string.
  Audit failure is caught locally so it never masks the refusal.

tests/test_file_write.py:
- new test_blocked_write_emits_file_write_blocked_audit_event:
  monkey-patches codec_audit.log_event, calls run() against
  ~/.codec/skills/attempt.py, asserts (a) refusal string returned,
  (b) exactly one file_write_blocked event captured, (c) extra
  dict contains target_path + reason. 22 tests total now.

AGENTS.md:
- new "file_write skill path-blocking (Phase 1 Wave 1, PR-1C —
  closes D-4)" subsection above the existing PR-1B subsection.
  Documents blocked paths (whole ~/.codec/, repo skills/, macOS
  system tree), audit emission contract, realpath bypass-proofing,
  and the operator/contributor note about editing CODEC config files
  via the dashboard PWA or direct editor rather than via file_write.

skills/.manifest.json:
- regenerated for the new file_write.py hash.

Sample audit line that an operator can grep for:

  {"ts": "2026-05-17T...Z",
   "schema": 1,
   "event": "file_write_blocked",
   "source": "codec-skill-file-write",
   "outcome": "error",
   "level": "warning",
   "message": "file_write refused ~/.codec/skills/attempt.py: ...",
   "extra": {
     "target_path": "/Users/<u>/.codec/skills/attempt.py",
     "requested_path": "~/.codec/skills/attempt.py",
     "reason": "Blocked path: /Users/<u>/.codec"
   }}

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@AVADSA25 AVADSA25 force-pushed the fix/pr1c-file-write-block-roots branch from b4c14fe to 48c1c55 Compare May 17, 2026 11:58
@AVADSA25 AVADSA25 merged commit 0065d90 into main May 17, 2026
1 check passed
AVADSA25 pushed a commit that referenced this pull request May 17, 2026
PR-1C (#45) merged to main as squash commit 0065d90. Update the
D-4 closure footnote in docs/audits/PHASE-1-SECURITY.md and the
D-4 row in docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md, replacing
the branch-name placeholder with PR number + commit hash.

Mirrors the citation style applied to D-1 (PR-1A, 48ec5d5) and
D-2/D-3 (PR-1B, ff16664).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AVADSA25 pushed a commit that referenced this pull request May 17, 2026
PR-1C (#45) merged to main as squash commit 0065d90. Update the
D-4 closure footnote in docs/audits/PHASE-1-SECURITY.md and the
D-4 row in docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md, replacing
the branch-name placeholder with PR number + commit hash.

Mirrors the citation style applied to D-1 (PR-1A, 48ec5d5) and
D-2/D-3 (PR-1B, ff16664).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AVADSA25 added a commit that referenced this pull request May 17, 2026
PR-1C (#45) merged to main as squash commit 0065d90. Update the
D-4 closure footnote in docs/audits/PHASE-1-SECURITY.md and the
D-4 row in docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md, replacing
the branch-name placeholder with PR number + commit hash.

Mirrors the citation style applied to D-1 (PR-1A, 48ec5d5) and
D-2/D-3 (PR-1B, ff16664).

Co-authored-by: Mickael Farina <farina.mickael@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AVADSA25 pushed a commit that referenced this pull request May 17, 2026
…5+D-14+D-16)

Closes three Phase 1 Audit D findings in one cohesive change to the
Step 9 agent runtime:

  D-5  CRITICAL  permission_gate accepts path-traversal via fnmatch
  D-14 MEDIUM   _PATH_BLOCKLIST_SUBSTRINGS misses ~/.codec/skills + plugins
  D-16 MEDIUM   blocklist substring match is anchorless

All three are auto-extract / runtime paths that an LLM-drafted plan
could use to chain into D-1 RCE. Each layer closes independently.

codec_agent_runner.py — permission_gate refactor (D-5):
- New helper _path_allowed(action_path, grants) → (bool, reason):
  * reject `..` segments outright (closes the dotdot bypass that
    fnmatch glob-matched against grants like ~/Documents/**)
  * os.path.realpath both sides — symlink-out-of-grant rejected
  * fnmatch replaced with action_real.startswith(grant_real + os.sep)
    Trade-off: a grant like ~/Documents/*.md now accepts any file
    under realpath(~/Documents/) — safety > granularity per audit.
- New helper _emit_gate_blocked(action_path, reason, agent_id):
  emits `permission_gate_blocked` audit event before raising
  PermissionViolation. source=codec-agent-runner, outcome=error,
  level=warning, extra={requested_path, resolved_path, reason,
  agent_id}. Forensic visibility per D-5 closure §3.
- Removed unused `import fnmatch` (no other callers in the module).

codec_agent_plan.py — blocklist extension + segment-aware (D-14+D-16):
- _PATH_BLOCKLIST_SUBSTRINGS extended with 8 new entries (D-14):
    /.codec/skills          /.codec/plugins
    /.codec/oauth_state.json /.codec/audit.log
    /.codec/agents          /.codec/agent_global_grants.json
    /.codec/config.json     /.codec/memory.db
- New helper _path_segments_match(path, pattern) does segment-aware
  matching (D-16): splits both `path` (after expanduser+normpath) and
  `pattern` on `/`, requires consecutive subsequence of segments.
    `~/Documents/notes_ssh/foo.md` no longer matches `/.ssh` (segment
    is `notes_ssh`, not `.ssh`), but `~/.ssh/config` still does.
- New helper _is_path_blocklisted(path) walks the full blocklist.
- extract_user_paths now calls _is_path_blocklisted instead of the
  raw substring `any(b in raw for b in _PATH_BLOCKLIST_SUBSTRINGS)`.

Tests:
- tests/test_agent_runner.py +5 new (143 LOC):
    test_permission_gate_rejects_path_traversal_dotdot
    test_permission_gate_rejects_read_path_traversal
    test_permission_gate_rejects_symlink_outside_grant
    test_permission_gate_accepts_realpath_within_grant
    test_permission_gate_emits_blocked_audit_event
- tests/test_agent_plan.py +10 new (80 LOC, 7 parametrized + 3 misc):
    test_extract_user_paths_blocks_sensitive_codec_dirs (×7)
    test_extract_user_paths_segment_aware_not_false_positive
    test_extract_user_paths_still_blocks_real_ssh_path
    test_extract_user_paths_allows_legitimate_user_paths

Verification:
- pytest tests/test_agent_runner.py tests/test_agent_plan.py
  tests/test_file_write.py tests/test_skill_routes.py
  tests/test_skill_registry.py tests/test_skill_contracts.py
  → 148 passed (full Wave 1 regression + new)
- python3 tests/test_skill_imports.py → 76 parsed, 0 errors
- python3 tools/generate_skill_manifest.py --check → ok
- ruff check codec_agent_runner.py codec_agent_plan.py
  → 5 errors in codec_agent_runner.py + 22 in codec_agent_plan.py,
    all pre-existing on main (F401 unused imports `field`, `time`,
    E402 inline imports for regex, F541 f-string-without-placeholder,
    F821 List/Tuple undefined). The one new error introduced —
    `fnmatch` no longer used in codec_agent_runner.py — has been
    cleaned up (import removed).

Docs:
- AGENTS.md §7 new "Agent permission gate + path blocklist
  (Phase 1 Wave 1, PR-1D — closes D-5 + D-14 + D-16)" subsection
  documenting the four-layer defense (PR-1A + PR-1C + PR-1D blocklist
  + PR-1D runtime gate) against the D-1 RCE chain.
- docs/audits/PHASE-1-SECURITY.md: closure footnotes on D-5, D-14,
  D-16.
- docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md: D-5 row flipped to
  W1 — CLOSED (PR-1D). D-14 and D-16 are MEDIUM (not in the
  CRITICAL findings table) but their D-section footnotes carry the
  closure trail.

Wave 1 status after this PR merges:
- D-1 ✅ closed (PR-1A, #42, 48ec5d5)
- D-2 ✅ closed (PR-1B, #43, ff16664)
- D-3 ✅ closed (PR-1B, #43, ff16664)
- D-4 ✅ closed (PR-1C, #45, 0065d90)
- D-5 ✅ closed (this PR)
- D-14 ✅ closed (this PR — bonus)
- D-16 ✅ closed (this PR — bonus)

All five CRITICAL skill-loading + write-path findings are closed.

Sample audit line emitted on a blocked traversal attempt:

  {"ts": "2026-05-17T...Z",
   "schema": 1,
   "event": "permission_gate_blocked",
   "source": "codec-agent-runner",
   "outcome": "error",
   "level": "warning",
   "message": "permission_gate refused '~/Documents/../../etc/passwd': path_traversal",
   "extra": {
     "requested_path": "~/Documents/../../etc/passwd",
     "resolved_path": "/etc/passwd",
     "reason": "path_traversal",
     "agent_id": ""
   }}

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AVADSA25 added a commit that referenced this pull request May 17, 2026
…5+D-14+D-16) (#47)

Closes three Phase 1 Audit D findings in one cohesive change to the
Step 9 agent runtime:

  D-5  CRITICAL  permission_gate accepts path-traversal via fnmatch
  D-14 MEDIUM   _PATH_BLOCKLIST_SUBSTRINGS misses ~/.codec/skills + plugins
  D-16 MEDIUM   blocklist substring match is anchorless

All three are auto-extract / runtime paths that an LLM-drafted plan
could use to chain into D-1 RCE. Each layer closes independently.

codec_agent_runner.py — permission_gate refactor (D-5):
- New helper _path_allowed(action_path, grants) → (bool, reason):
  * reject `..` segments outright (closes the dotdot bypass that
    fnmatch glob-matched against grants like ~/Documents/**)
  * os.path.realpath both sides — symlink-out-of-grant rejected
  * fnmatch replaced with action_real.startswith(grant_real + os.sep)
    Trade-off: a grant like ~/Documents/*.md now accepts any file
    under realpath(~/Documents/) — safety > granularity per audit.
- New helper _emit_gate_blocked(action_path, reason, agent_id):
  emits `permission_gate_blocked` audit event before raising
  PermissionViolation. source=codec-agent-runner, outcome=error,
  level=warning, extra={requested_path, resolved_path, reason,
  agent_id}. Forensic visibility per D-5 closure §3.
- Removed unused `import fnmatch` (no other callers in the module).

codec_agent_plan.py — blocklist extension + segment-aware (D-14+D-16):
- _PATH_BLOCKLIST_SUBSTRINGS extended with 8 new entries (D-14):
    /.codec/skills          /.codec/plugins
    /.codec/oauth_state.json /.codec/audit.log
    /.codec/agents          /.codec/agent_global_grants.json
    /.codec/config.json     /.codec/memory.db
- New helper _path_segments_match(path, pattern) does segment-aware
  matching (D-16): splits both `path` (after expanduser+normpath) and
  `pattern` on `/`, requires consecutive subsequence of segments.
    `~/Documents/notes_ssh/foo.md` no longer matches `/.ssh` (segment
    is `notes_ssh`, not `.ssh`), but `~/.ssh/config` still does.
- New helper _is_path_blocklisted(path) walks the full blocklist.
- extract_user_paths now calls _is_path_blocklisted instead of the
  raw substring `any(b in raw for b in _PATH_BLOCKLIST_SUBSTRINGS)`.

Tests:
- tests/test_agent_runner.py +5 new (143 LOC):
    test_permission_gate_rejects_path_traversal_dotdot
    test_permission_gate_rejects_read_path_traversal
    test_permission_gate_rejects_symlink_outside_grant
    test_permission_gate_accepts_realpath_within_grant
    test_permission_gate_emits_blocked_audit_event
- tests/test_agent_plan.py +10 new (80 LOC, 7 parametrized + 3 misc):
    test_extract_user_paths_blocks_sensitive_codec_dirs (×7)
    test_extract_user_paths_segment_aware_not_false_positive
    test_extract_user_paths_still_blocks_real_ssh_path
    test_extract_user_paths_allows_legitimate_user_paths

Verification:
- pytest tests/test_agent_runner.py tests/test_agent_plan.py
  tests/test_file_write.py tests/test_skill_routes.py
  tests/test_skill_registry.py tests/test_skill_contracts.py
  → 148 passed (full Wave 1 regression + new)
- python3 tests/test_skill_imports.py → 76 parsed, 0 errors
- python3 tools/generate_skill_manifest.py --check → ok
- ruff check codec_agent_runner.py codec_agent_plan.py
  → 5 errors in codec_agent_runner.py + 22 in codec_agent_plan.py,
    all pre-existing on main (F401 unused imports `field`, `time`,
    E402 inline imports for regex, F541 f-string-without-placeholder,
    F821 List/Tuple undefined). The one new error introduced —
    `fnmatch` no longer used in codec_agent_runner.py — has been
    cleaned up (import removed).

Docs:
- AGENTS.md §7 new "Agent permission gate + path blocklist
  (Phase 1 Wave 1, PR-1D — closes D-5 + D-14 + D-16)" subsection
  documenting the four-layer defense (PR-1A + PR-1C + PR-1D blocklist
  + PR-1D runtime gate) against the D-1 RCE chain.
- docs/audits/PHASE-1-SECURITY.md: closure footnotes on D-5, D-14,
  D-16.
- docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md: D-5 row flipped to
  W1 — CLOSED (PR-1D). D-14 and D-16 are MEDIUM (not in the
  CRITICAL findings table) but their D-section footnotes carry the
  closure trail.

Wave 1 status after this PR merges:
- D-1 ✅ closed (PR-1A, #42, 48ec5d5)
- D-2 ✅ closed (PR-1B, #43, ff16664)
- D-3 ✅ closed (PR-1B, #43, ff16664)
- D-4 ✅ closed (PR-1C, #45, 0065d90)
- D-5 ✅ closed (this PR)
- D-14 ✅ closed (this PR — bonus)
- D-16 ✅ closed (this PR — bonus)

All five CRITICAL skill-loading + write-path findings are closed.

Sample audit line emitted on a blocked traversal attempt:

  {"ts": "2026-05-17T...Z",
   "schema": 1,
   "event": "permission_gate_blocked",
   "source": "codec-agent-runner",
   "outcome": "error",
   "level": "warning",
   "message": "permission_gate refused '~/Documents/../../etc/passwd': path_traversal",
   "extra": {
     "requested_path": "~/Documents/../../etc/passwd",
     "resolved_path": "/etc/passwd",
     "reason": "path_traversal",
     "agent_id": ""
   }}

Co-authored-by: Mickael Farina <farina.mickael@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AVADSA25 added a commit that referenced this pull request May 17, 2026
#48)

PR-1D (#47) merged to main as squash commit fd2b460. Update the
closure footnotes for D-5, D-14, and D-16 in
docs/audits/PHASE-1-SECURITY.md plus the D-5 row in
docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md, replacing the
branch-name placeholders with PR number + commit hash.

Mirrors the citation style applied to:
  D-1   PR-1A #4248ec5d5
  D-2/3 PR-1B #43ff16664
  D-4   PR-1C #450065d90
  D-5   PR-1D #47fd2b460  (this commit)

After this lands, Wave 1 is fully closed with complete citation
trails. All five CRITICAL skill-loading + write-path findings
(D-1, D-2, D-3, D-4, D-5) plus the two bonus mediums (D-14, D-16)
carry merge commit hashes in their audit-doc footnotes.

Co-authored-by: Mickael Farina <farina.mickael@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.

2 participants