Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5822869056 | ||
|
|
66e4c4d0f9 | ||
|
|
c34ac99882 | ||
|
|
bb7f25adc1 |
@@ -78,6 +78,8 @@ rules:
|
||||
| BDR-055 | 2026-07-07 | job5: delete memory-commit/doc-commit `pending` verbs — v2 hook rejected (BDR-037), J4-17 closed MOOT | accepted |
|
||||
| BDR-056 | 2026-07-07 | job6: deps policy = latest gated by integration, not KEEP-PINNED by default | accepted |
|
||||
| BDR-057 | 2026-07-07 | job7: secrets by reference not by value; redact at capture, not just at rest | accepted |
|
||||
| BDR-058 | 2026-07-07 | job8: darwin-skill reinstall full pinned tree, detached HEAD (skills CLI single-file-fetch gap) | accepted |
|
||||
| BDR-059 | 2026-07-07 | job8: explicit ask-gate for all 4 magic MCP tools, empty allow stays empty | accepted |
|
||||
|
||||
---
|
||||
|
||||
@@ -889,3 +891,21 @@ rules:
|
||||
- **Alternatives rejected**: scrub-only (chosen as the fallback in job7's own instructions if reference-by-value support were absent) — verified Claude Code DOES support `${VAR}` expansion in `mcpServers` config (user + project scope, `env`/`command`/`args`/`url`/`headers` fields — code.claude.com/docs/en/mcp.md), so the reference form was available and preferred; global `export MAGIC_API_KEY` in `~/.bashrc` — works but broadens the secret's exposure to every subprocess of every shell session, defeating the point of the redaction hook (rejected by user in favor of the scoped wrapper).
|
||||
- **Reference**: `lib/toggle-external.sh:191-192`, `hooks/rtk-rewrite.sh`, `README.md` "Adding an MCP server that needs a secret", `.gitleaks.toml`, `lib/gitflow.sh` `_gitflow_emit_pre_commit`, `Makefile` `scan-secrets`; commits `b9300c3`/`3340c7d`/`17bdd08`/`5d5b386`. Linked to [[BDR-026]] (canonical vault this closes a leak vector against), [[LRN-108]] (the `claude mcp add --env` trap).
|
||||
- **Caveat — contradicts job6's own finding same day**: job6's journal (2026-07-07, earlier same day) states "`${VAR}` env-expansion confirmed unsupported at `~/.claude.json` user scope after 2 rounds of sourced doc lookup". job7's doc lookup (claude-code-guide agent, same day) found it IS supported at user scope, citing code.claude.com/docs/en/mcp.md + a v2.1.161 changelog entry. Not reconciled — could be a version bump between the two lookups, or job6's research being wrong. The `${MAGIC_API_KEY}` rewrite is live (`claude mcp list` recognizes the reference and reports the var missing, which requires the CLI to have at least PARSED the `${...}` syntax) but full end-to-end confirmation (restart terminal + Claude Code, verify magic MCP reconnects) is still a residual the user needs to do — see BDR-057's own commit message.
|
||||
|
||||
## BDR-058 — job8: darwin-skill reinstall full pinned tree, detached HEAD
|
||||
|
||||
- **Date**: 2026-07-07
|
||||
- **Status**: accepted
|
||||
- **Decision**: darwin-skill non-functional past SKILL.md text — `references/`, `scripts/`, `templates/` absent, referenced but never fetched. Root cause: `~/.agents/.skill-lock.json` `skillPath: "SKILL.md"` — installer (`skills` CLI, vercel-labs/skills) fetches ONLY that one file, not sibling dirs. Upstream repo HEAD (`7c7b7909b630dc3b5cbb91bd4bcb1b10bfb1f894`) matches lockfile hash exactly — zero drift, zero tamper, SKILL.md byte-identical old vs new. Fix: cloned upstream at that SHA, copied full tree into `~/.agents/skills/darwin-skill/`, verified all 5 referenced paths present, HEAD detached (no branch tracking, no silent advance on a stray `git pull`). Old single-file dir backed up to `~/.agents/skills/.job8-backups/darwin-skill.single-file.<ts>` first.
|
||||
- **Why**: user picked reinstall-pinned over remove/keep-broken (job8 audit §4 item 4, 3-way choice). Unverifiable skill can't be trusted; user wants the optimizer kept, not removed.
|
||||
- **Alternatives rejected**: remove entry (kills wanted function); keep as-is (fails job8's own audit bar — unverifiable); flat-copy without `.git` (matches other 34 dormant skills' convention but drops verifiable pin — kept `.git` detached instead, darwin-skill now 2nd real SHA-pin in the whole trust chain after gstack, job8 report §5).
|
||||
- **Reference**: `~/.agents/skills/darwin-skill/` (detached HEAD `7c7b790`), `~/.agents/.skill-lock.json` (untouched, hash still accurate), backup at `~/.agents/skills/.job8-backups/`. Outside this repo — no commit here covers the file placement itself, this entry is the record. Git-commit whole-`.claude/skills`-tree scope (job8 C.2, `SKILL.md:115/201`) NOT restricted — 3rd-party pinned code, patching it breaks the pin; accepted as documented risk, human-checkpoint-gated per job8 report. Linked to [[LRN-109]].
|
||||
|
||||
## BDR-059 — job8: explicit ask-gate for all 4 magic MCP tools, empty allow stays empty
|
||||
|
||||
- **Date**: 2026-07-07
|
||||
- **Status**: accepted
|
||||
- **Decision**: `settings.json` `permissions.ask` now explicitly lists all 4 `mcp__magic__*` tools (`21st_magic_component_builder`, `21st_magic_component_refiner`, `21st_magic_component_inspiration`, `logo_search`). `permissions.allow` gets ZERO magic entries — no allowlist tightening, the job8 report's "frictionless" diff (allowlist logo_search + inspiration) was explicitly rejected. Confirmation required on every magic call, no exceptions, no auto-exec ever, no wildcard.
|
||||
- **Why**: job8 §3/§4 found zero real `mcp__magic__*` invocations ever (transcript census) and one SUSPECT finding (`21st_magic_component_builder` unauthenticated callback-injection channel, [[LRN-110]]). Prior state relied on undocumented absence-means-ask fallthrough — user wants the gate EXPLICIT so it can't silently regress if `permissions.allow` ever gets a careless wildcard or the default-mode semantics change.
|
||||
- **Alternatives rejected**: leave everything absent (report's own recommended default) — works today but is silent/undocumented, exactly the posture the user wanted to close; allowlist `logo_search` + `21st_magic_component_inspiration` for frictionless design work (job8 report §3 "frictionless" diff) — explicitly declined, real usage is zero so friction costs nothing.
|
||||
- **Reference**: `settings.json` `permissions.ask`, commit `bb7f25a`. Linked to [[LRN-110]] (component_builder risk), [[LRN-111]] (empty-allowlist validity when usage is zero).
|
||||
|
||||
@@ -355,3 +355,6 @@ rules:
|
||||
- User GO full execution, prerequisites confirmed upfront (gstack #2047 human review → pull complet + reapply local fix; MAGIC_API_KEY rotated). Sequenced by risk, one upgrade = one commit = one gate, chore/job6-deps-upgrade, no finish.
|
||||
- job6 EXECUTED: ctx7 0.5.3→0.5.4 (zero repo diff), graphifyy binary 0.9.6→0.9.8 (hook-guard rewrite of config-protected `.claude/settings.json` traced to source, diff shown, user declined adoption), gsd-pi 2.64.0→3.0.0 (`b4896c9` — 3.0.0 confirmed format-incompatible with status-reporter's ROADMAP.md parser via a real scratch-dir test milestone; ADR-013 cutover, DB-authoritative, no ROADMAP.md at all; user chose patch-now, parser rewired to `gsd headless query` JSON, smoke-tested both cases), gstack submodule 070722a→11de390 (`2813e55` — full pull per verdict, #1911 fail-open guards + PII/telemetry/data-loss fixes; local playwright patch (BDR-029) backed up then discarded then correctly reapplied via the documented bump function, landed one minor ahead since upstream moved meanwhile; /careful + /freeze smoke-tested blocking live), supply-chain docs (`00c97bc` — pipx-only graphifyy rule, semgrep p/* runtime-pack caveat; MCP magic version pin declined by user, `${VAR}` env-expansion confirmed unsupported at `~/.claude.json` user scope after 2 rounds of sourced doc lookup — BDR-026 pattern doesn't transfer there, regenerated live config instead via toggle-external.sh to pick up the rotated key). `make test` 90/90 green + `doctor.sh` 0 errors throughout. Incident: mid-session Bash tool universally unresponsive again post-`/tmp` exhaustion (same class as job4's), user cleared it, resumed from confirmed git state. [[EVAL-020]], [[BDR-056]] (deps policy reversal: latest gated by integration, not KEEP-PINNED default). Branch unmerged, human gate — orphan `~/skills-lock.json` (F-S1) also deleted, non-repo file, no commit.
|
||||
- job7 secrets backstops shipped, `chore/job7-secrets`, 4 commits (A/B/C/D), `make test` 96/96 green throughout. **A**: MAGIC_API_KEY's sole writer confirmed (`lib/toggle-external.sh:191`, no other). Doc lookup found `${VAR}` expansion IS supported at `~/.claude.json` user scope — contradicts job6's own same-day finding, not reconciled (see [[BDR-057]] caveat). Rewrote to `--env 'API_KEY=${MAGIC_API_KEY}'` + scoped `~/.bashrc` `claude()` wrapper (subshell+exec, verified the var never reaches the ambient shell) over a global export (user's call); `~/.claude.json` rewritten via surgical jq (never Read directly); README procedure doc added; 2 of 5 rotating `.claude.json.backup.*` still had the plaintext mid-fix, scrubbed. **B**: `hooks/rtk-rewrite.sh` now redacts bare `printenv`/`env` dumps (the GITEA leak's actual vector). Mid-implementation discovery: rtk classifies ANY `env`-containing command as exit-2 "deny" with no settings.json rule backing it (command still runs) — case handling fixed so redaction applies regardless. **C**: `.gitleaks.toml` (3 job7 false-positive classes + `.env` self-scan exclusion, all verified empirically against the real files, not assumed); pre-commit backstop wired into `lib/gitflow.sh` after the root/merge guard, ANY branch; `make scan-secrets` (repo + `~/.claude`, `--redact` confirmed to scrub the JSON report itself, not just logs). gitleaks 8.30.1: `protect` no longer in `--help` — used documented `git --staged`. **D** (GO-gated): rm'd transcript `960bd2cf` + `paste-cache/7d48f52c7499c1a7.txt` (both GO'd); `cleanupPeriodDays` 30→7 (1st write attempt correctly blocked by the auto-mode classifier for narrating the diff instead of actually pausing — re-asked properly). `make scan-secrets` surfaced 3 discoveries outside the original triage: `ide/20429.lock` (live, not touched), transcript `f1c9c474-...jsonl` (8 hits, left open — no option chosen). Residuals: MAGIC_API_KEY rotation still pending user action; magic MCP end-to-end reconnect needs a terminal+Claude Code restart; live `claude mcp add` test correctly blocked (self-modification, unrequested). [[BDR-057]], [[LRN-108]].
|
||||
- job8 third-party security audit shipped read-only: `.audit/job8-report.md` — magic MCP/plugins/gstack/external skills/trust chain, 9 explorers + verifier batches, 11 CONFIRMED/5 CORRECTED/0 REFUTED. Surfaces C (ui-ux-pro-max) + D (other plugins) finished inline, single-observer, no verifier pass — Fable-5 spend limit hit mid-run.
|
||||
- User GO on all 4 items: A allowlist stays empty, ask-gate explicit; B covered by A (no STOP); C reinstall pinned (not remove/keep-broken); D no action. Executor = this session, `chore/job8-hardening`, no finish.
|
||||
- job8 EXECUTED: 3 commits. **A**: `settings.json` `permissions.ask` += 4 `mcp__magic__*` tools, isolated from 2 unrelated pre-existing edits (model/skipWorkflowUsageWarning) already sitting uncommitted before this session started — those restored uncommitted after, not part of this branch's history [[BDR-059]]. **B**: confirmed `component_builder` in scope of A's gate, no STOP needed; documented the callback-injection risk in README's MCP section + [[LRN-110]] — third-party package code, not patched. **C**: confirmed referenced files (`references/`, `scripts/`, `templates/`) 100% absent from `~/.agents/skills/darwin-skill/` (only `SKILL.md` present) — root-caused to the `skills` CLI's `skillPath` install field fetching a single file, not the repo tree [[LRN-109]]. Upstream HEAD matched the already-recorded lockfile hash exactly (zero drift). Reinstalled full tree at that pinned SHA, `.git` kept but detached (2nd real SHA-pin after gstack) [[BDR-058]]. Backup of old single-file dir kept. Git-commit whole-`.claude/skills`-tree scope NOT restricted (3rd-party pinned code, patching breaks the pin) — documented as accepted risk instead. 3 Bash permission denials mid-C (rsync x2, cp+rm) before a plain `cp` succeeded — `rm -r*`/`rm -rf*` are hard-denied even for scratch/temp paths, no prompt possible; switched approach rather than retrying identically. **D**: confirmed untouched. `make test` green throughout (incl. a live `path_present(darwin-skill)` fs check). Smoke gate: real `mcp__magic__logo_search` call in-session, user confirmed the ask prompt fired and was manually approved — no auto-exec. [[LRN-111]]. Branch unmerged, human gate. **Not re-verified this cycle** (job8 report's own caveat, carried forward): surfaces C/D (ui-ux-pro-max, other plugins) were single-observer CLEAN findings with no adversarial pass — re-audit next cycle if darwin/magic scope comes up again.
|
||||
|
||||
@@ -122,6 +122,9 @@ rules:
|
||||
| LRN-102 | 2026-07-05 | deliverable text placed BEFORE a tool call may never render — only the turn's FINAL text is guaranteed displayed; a checklist printed above AskUserQuestion was invisible to the user | any flow whose deliverable is conversational text (checklist, commands, report): end the turn with it, blocking questions come before, never after |
|
||||
| LRN-105 | 2026-07-06 | explorer subagent ran a build tool (`graphify .`) mid read-only audit despite prose instructions to only Read/Grep/Bash-read — the runtime observed a config-protection sentinel deny message and self-corrected only after an explicit main-session correction, not from the original prompt | dispatching any "read-only audit" subagent whose toolset includes Bash: state "do not execute build/generator/mutating commands" explicitly, don't rely on "read-only" framing alone to constrain tool CHOICE |
|
||||
| LRN-106 | 2026-07-06 | job3-B1 froze a fixture + repointed run-reconcile.sh's T2 off the live registry, declared "unblocked", 20/20 green — job4 (next audit, same file, same day) found T3+T5 in the SAME FILE still read the live registry, same fragility, untouched | fixing one instance of a "reads live state it shouldn't" finding: grep the WHOLE file (not just the cited line) for the same pattern before declaring the class closed |
|
||||
| LRN-109 | 2026-07-07 | job8: `skills` CLI (vercel-labs/skills) fetches only `skillPath` (often just SKILL.md), not sibling refs/scripts/templates the skill text references — darwin-skill install gap, not drift/tamper | installing/auditing any skill via the `skills` CLI whose SKILL.md references relative paths — verify those paths exist post-install, don't trust `skillFolderHash` alone |
|
||||
| LRN-110 | 2026-07-07 | job8: `21st_magic_component_builder` (magic MCP) opens unauth'd 127.0.0.1 callback server, CORS `*`, no token check, 10min window — any local POST lands verbatim in the tool result the model consumes = local prompt-injection channel | any MCP tool that opens a local callback/listener server to receive async results — check auth + origin scoping on the listener, not just the outbound call |
|
||||
| LRN-111 | 2026-07-07 | job8: empty permissions.allow for a risky MCP tool is a VALID posture (not a gap) when transcript census shows zero real invocations — pre-authorizing unused surface buys nothing, ask-gate costs nothing | deciding whether to allowlist any tool/command — check real usage before assuming "no entry = todo" |
|
||||
|
||||
---
|
||||
|
||||
@@ -1102,3 +1105,21 @@ rules:
|
||||
- **context**: `lib/toggle-external.sh:191` had this exact double-quoted form since BDR-025/026; it materialized the key into `~/.claude.json` (2026-07-02 incident) and kept re-leaking into every native auto-backup taken afterward (5-file rotating ring buffer, plaintext each time) until fixed at the source.
|
||||
- **future application**: adding ANY MCP server with a secret via `claude mcp add --env`, single-quote the value using `${VAR}` syntax, never double-quote/bash-expand it. The var still has to exist in the environment of the process that starts `claude` — don't solve that with a blanket `export` in `~/.bashrc` (broadens exposure to every subprocess); scope it with a wrapper function that sources the secret into a subshell before `exec`ing the real binary (see `~/.bashrc`'s `claude()` function, [[BDR-057]]).
|
||||
- **cousin**: [[BDR-026]] (canonical vault + copies), [[BDR-057]] (secrets-by-reference decision this trap motivated), [[LRN-107]] (same job family, don't-copy-the-value discipline).
|
||||
|
||||
## LRN-109 — `skills` CLI (vercel-labs/skills) fetches only `skillPath`, not sibling refs/scripts/templates
|
||||
|
||||
- **context**: job8 audit flagged darwin-skill NOT-CLEAN — SKILL.md references `references/*.md`, `scripts/*.mjs`, `templates/*.html`, all absent on disk. Traced to `~/.agents/.skill-lock.json`: `skillPath: "SKILL.md"` — installer fetched that ONE file, never the sibling dirs the skill text points to. Upstream repo (public clone, verified) had them all at the exact commit already recorded (`skillFolderHash` matches) — not drift, an installer-scope gap.
|
||||
- **future application**: any skill installed via `skills` CLI whose SKILL.md references relative paths needs a post-install check those paths exist on disk — `skillFolderHash` only hashes what WAS fetched, says nothing about what's missing. If absent: clone source repo at the recorded hash, copy full tree in, keep `.git` detached (cheap real pin, beats trusting the CLI's opaque hash alone).
|
||||
- **cousin**: [[BDR-058]] (this job's fix), darwin-skill's OVERSCOPED git-commit finding (job8 report — 3rd-party code, not patched, accepted risk under human-checkpoint gating, twin of [[LRN-105]]'s no-execute mandate for OUR read-only audits).
|
||||
|
||||
## LRN-110 — magic MCP `component_builder`'s local callback server = unauthenticated prompt-injection channel
|
||||
|
||||
- **context**: job8 audit read `dist/utils/callback-server.js:36` (+ `create-ui.js:35-38`) in the installed `@21st-dev/magic` package. `21st_magic_component_builder` opens a plain HTTP server on `127.0.0.1:9221+`, `Access-Control-Allow-Origin: *`, no token/origin check, staying open up to 10 minutes per call. Whatever body a POST to `/data` carries gets injected VERBATIM into the tool result the model then consumes — any local process or an open browser tab on the same machine can win the race against the legitimate browser hand-back.
|
||||
- **future application**: this is in the third-party package's code, not our config — don't try to patch a vendored/npx-installed dependency. The only real lever is on OUR side of the boundary: never allowlist a tool with this shape, keep it `ask`-gated so a human sees every invocation (see [[BDR-059]]). Applies to any MCP tool whose implementation opens a listener to receive async results, not just this one — check the listener's auth/origin scoping when auditing MCP server code, the tool's *description* text tells you nothing about it.
|
||||
- **cousin**: [[BDR-059]] (the settings fix), [[LRN-111]] (why the allowlist stays empty), job8 report §2 surface 1 finding A#0.
|
||||
|
||||
## LRN-111 — empty allowlist is a valid, deliberate posture when real usage is zero, not a leftover gap
|
||||
|
||||
- **context**: job8 census (grepping real `"name":"mcp__…"` tool_use blocks across `~/.claude/projects`, not text mentions) found ~910 mentions of `mcp__magic__*` but ZERO real invocations, ever. `permissions.allow`/`permissions.ask` had no `mcp__*` entries at all before this job — job6 flagged that as "ZERO scoping", easy to misread as an oversight to fix by adding an allowlist.
|
||||
- **future application**: before treating "no entry for tool X" as a gap needing an allowlist, check real usage first (grep tool_use blocks, not prose mentions). If usage is zero, pre-authorizing costs nothing to skip and buys nothing to add — the honest fix is making the ask-gate EXPLICIT (so it can't regress silently), not granting allow access nobody needs yet. Only add allow entries when real, measured, recurring usage justifies removing the friction.
|
||||
- **cousin**: [[BDR-059]], [[LRN-110]], [[LRN-088]] (same family: measure before assuming an absence is a defect).
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# TODO
|
||||
|
||||
## 2026-07-07 — job8 third-party security hardening (chore/job8-hardening)
|
||||
Genèse : `.audit/job8-report.md` (magic MCP/plugins/gstack/external skills/trust
|
||||
chain, read-only). A/B/C/D exécutés (3 commits), branche non mergée, gate humain.
|
||||
|
||||
- [x] A — `permissions.ask` += 4 `mcp__magic__*` tools, allow reste vide (BDR-059)
|
||||
- [x] B — component_builder couvert par A ; risque documenté README + LRN-110
|
||||
- [x] C — darwin-skill réinstallé pinné (tree complet, HEAD détaché SHA
|
||||
7c7b790), git-commit large-scope documenté comme risque accepté (pas de
|
||||
patch sur code tiers pinné) — BDR-058, LRN-109
|
||||
- [x] D — pr-review-toolkit / example-skills inchangés, confirmé
|
||||
|
||||
- [ ] Re-audit surfaces C/D (ui-ux-pro-max, autres plugins) — single-observer
|
||||
CLEAN sans passe verifier (Fable-5 épuisé mi-job8), à re-vérifier au
|
||||
prochain cycle d'audit sécurité si le scope magic/darwin revient.
|
||||
- [ ] MAGIC_API_KEY rotation toujours en attente (résiduel job7, non job8)
|
||||
|
||||
## 2026-07-07 — job7 secrets: triage backstops (chore/job7-secrets)
|
||||
Genèse : `.audit/job7/ALL-REDACTED.json` (triage secrets multi-repo + ~/.claude).
|
||||
GITEA_TOKEN déjà rotaté (transcript 960bd2cf). MAGIC rotation prévue après (A).
|
||||
|
||||
@@ -229,6 +229,21 @@ There is no `claude mcp add` flag that writes the reference form for you —
|
||||
the `${VAR}` syntax has to be typed by hand (or via a wrapper script), same as
|
||||
above.
|
||||
|
||||
### magic MCP (`@21st-dev/magic`) — known callback-injection risk
|
||||
|
||||
`21st_magic_component_builder` opens an **unauthenticated** local callback
|
||||
server (`127.0.0.1:9221+`, `Access-Control-Allow-Origin: *`, no token/origin
|
||||
check) for up to 10 minutes per call; any local process or open browser tab
|
||||
can `POST` to it and that body is injected **verbatim** into the tool result
|
||||
the model consumes (job8 audit, `dist/utils/callback-server.js:36`). This is
|
||||
in the third-party package's code, not this repo's config — **we don't patch
|
||||
it**. The mitigation lives entirely on our side: `settings.json`
|
||||
`permissions.ask` explicitly lists all 4 `mcp__magic__*` tools ([[BDR-059]]),
|
||||
so every call — builder included — requires a live confirmation and can
|
||||
never auto-execute. Don't allowlist
|
||||
`21st_magic_component_builder` or `21st_magic_component_refiner` (arbitrary
|
||||
absolute-path read → vendor exfil, same audit) under any circumstance.
|
||||
|
||||
---
|
||||
|
||||
## Diagnostic and maintenance
|
||||
|
||||
+5
-1
@@ -226,7 +226,11 @@
|
||||
"Bash(mv *)",
|
||||
"Bash(git stash pop*)",
|
||||
"Bash(git stash drop*)",
|
||||
"Bash(git stash clear)"
|
||||
"Bash(git stash clear)",
|
||||
"mcp__magic__21st_magic_component_builder",
|
||||
"mcp__magic__21st_magic_component_refiner",
|
||||
"mcp__magic__21st_magic_component_inspiration",
|
||||
"mcp__magic__logo_search"
|
||||
],
|
||||
"defaultMode": "auto",
|
||||
"disableBypassPermissionsMode": "disable",
|
||||
|
||||
Reference in New Issue
Block a user