From 5e3efd12abcd9f137d1041adcb43615723476a1a Mon Sep 17 00:00:00 2001 From: Mickael Farina Date: Sun, 17 May 2026 14:19:30 +0200 Subject: [PATCH] docs(audits): record PR-1C merge commit hash in D-4 closure 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 --- docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md | 2 +- docs/audits/PHASE-1-SECURITY.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md b/docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md index 835e270..adaf6f4 100644 --- a/docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md +++ b/docs/audits/PHASE-1-CONSOLIDATED-TRIAGE.md @@ -146,7 +146,7 @@ If you do all 8: you have layered defenses against D-1 / D-2 / D-3 / D-4 / D-7 u | **D-1** | Security | Skill registry lazy-load = RCE for anyone who can drop a `.py` file | **W1 — CLOSED ([#42](https://github.com/AVADSA25/codec/pull/42), `48ec5d5`)** | | **D-2** | Security | `/api/forge` fetches arbitrary URL → LLM → writes skill, no review gate | **W1 — CLOSED ([#43](https://github.com/AVADSA25/codec/pull/43), `ff16664`)** | | **D-3** | Security | `/api/save_skill` writes directly to skills/ with only substring check | **W1 — CLOSED ([#43](https://github.com/AVADSA25/codec/pull/43), `ff16664`)** | -| **D-4** | Security | `file_write` skill (MCP-exposed) can write to `~/.codec/skills/` | **W1 — CLOSED (PR-1C)** | +| **D-4** | Security | `file_write` skill (MCP-exposed) can write to `~/.codec/skills/` | **W1 — CLOSED ([#45](https://github.com/AVADSA25/codec/pull/45), `0065d90`)** | | **D-5** | Security | `permission_gate` accepts path-traversal via `fnmatch` (no realpath) | **W1** | | C-1 | Reliability | `codec.py` daemon ignores SIGINT/SIGTERM; leaks sox + tkinter on every restart | W4 | | C-2 | Reliability | `~/.codec/pwa_response.json` race conditions + no correlation_id | W4 | diff --git a/docs/audits/PHASE-1-SECURITY.md b/docs/audits/PHASE-1-SECURITY.md index 60838f2..c7c623e 100644 --- a/docs/audits/PHASE-1-SECURITY.md +++ b/docs/audits/PHASE-1-SECURITY.md @@ -103,7 +103,7 @@ No human review gate. No `/api/skill/review` staging. `is_dangerous_skill_code`' **Location:** `skills/file_write.py` (`SKILL_MCP_EXPOSE = True`) + `codec_config.py:_HTTP_BLOCKED` (file_write NOT blocked on HTTP) **CWE / OWASP:** CWE-22 Path Traversal / OWASP A01 Broken Access Control / Agentic A02 Tool Misuse -> **Closed by PR-1C** (branch `fix/pr1c-file-write-block-roots`). `skills/file_write.py:_is_safe_target` was refactored to (1) realpath the blocklist at module load so macOS `/etc → /private/etc` aliases are caught regardless of which name the caller uses, (2) block the entire `~/.codec/` tree (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 — every security-sensitive file at once), (3) block `/skills/` so the built-in skill directory can't be tampered with. Pre-existing bug also fixed: `/tmp` writes were silently failing because realpath resolves to `/private/tmp` and the old code hard-coded `/private` as a blocked root; now `/tmp` and `~` are both realpath-resolved in the sanity check. Defense in depth pairs with PR-1A's load-time gate. 21 tests in `tests/test_file_write.py` cover blocked-path refusal (incl. symlink resolution) and regression on legitimate paths (`~/Documents`, `~/Desktop`, `/tmp`, `~/Projects`, `~/codec-workspace`). +> **Closed by PR-1C** ([#45](https://github.com/AVADSA25/codec/pull/45), merged as `0065d90`). `skills/file_write.py:_is_safe_target` was refactored to (1) realpath the blocklist at module load so macOS `/etc → /private/etc` aliases are caught regardless of which name the caller uses, (2) block the entire `~/.codec/` tree (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 — every security-sensitive file at once), (3) block `/skills/` so the built-in skill directory can't be tampered with. Pre-existing bug also fixed: `/tmp` writes were silently failing because realpath resolves to `/private/tmp` and the old code hard-coded `/private` as a blocked root; now `/tmp` and `~` are both realpath-resolved in the sanity check. Defense in depth pairs with PR-1A's load-time gate. 21 tests in `tests/test_file_write.py` cover blocked-path refusal (incl. symlink resolution) and regression on legitimate paths (`~/Documents`, `~/Desktop`, `/tmp`, `~/Projects`, `~/codec-workspace`). **Description:** `file_write` enforces `realpath` resolution + `_BLOCKED_ROOTS` (`/System`, `/Library`, `/etc`, etc.) + `_BLOCKED_FILENAME_PATTERNS` (`.ssh`, `.env`, `secret`, etc.) + `_BLOCKED_EXTS`. But `~/.codec/skills/.py` is: - Under `$HOME` (passes the home check at line 103). - Does not start with any `_BLOCKED_ROOTS` after realpath.