@event4u/agent-config 9.4.0 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +35 -0
  3. package/dist/agent-src/commands/agents/audit.md +2 -3
  4. package/dist/agent-src/commands/agents/optimize.md +2 -3
  5. package/dist/agent-src/commands/agents/user/review.md +4 -5
  6. package/dist/agent-src/commands/agents/user/show.md +4 -5
  7. package/dist/agent-src/commands/agents/user/update.md +4 -5
  8. package/dist/agent-src/commands/agents/user.md +2 -3
  9. package/dist/agent-src/commands/analytics/prune.md +2 -3
  10. package/dist/agent-src/commands/analytics/show.md +2 -3
  11. package/dist/agent-src/commands/analyze/decision.md +2 -3
  12. package/dist/agent-src/commands/analyze/incident.md +2 -3
  13. package/dist/agent-src/commands/analyze/near-miss.md +2 -3
  14. package/dist/agent-src/commands/analyze/postmortem.md +2 -3
  15. package/dist/agent-src/commands/analyze/premortem.md +2 -3
  16. package/dist/agent-src/commands/analyze/reference-repo.md +2 -3
  17. package/dist/agent-src/commands/brand/identity.md +2 -3
  18. package/dist/agent-src/commands/brand/review.md +2 -3
  19. package/dist/agent-src/commands/brand/strategy.md +2 -3
  20. package/dist/agent-src/commands/brand/tokens.md +2 -3
  21. package/dist/agent-src/commands/brand/voice.md +2 -3
  22. package/dist/agent-src/commands/bug/fix.md +2 -3
  23. package/dist/agent-src/commands/bug/investigate.md +2 -3
  24. package/dist/agent-src/commands/challenge-me/vision.md +2 -3
  25. package/dist/agent-src/commands/challenge-me/with-docs.md +2 -3
  26. package/dist/agent-src/commands/chat-history/import.md +2 -3
  27. package/dist/agent-src/commands/commit/in-chunks.md +2 -3
  28. package/dist/agent-src/commands/context/create.md +2 -3
  29. package/dist/agent-src/commands/context/refactor.md +2 -3
  30. package/dist/agent-src/commands/cost/report.md +2 -3
  31. package/dist/agent-src/commands/council/analysis.md +2 -3
  32. package/dist/agent-src/commands/council/debate.md +2 -3
  33. package/dist/agent-src/commands/council/design.md +2 -3
  34. package/dist/agent-src/commands/council/optimize.md +2 -3
  35. package/dist/agent-src/commands/council/pr.md +2 -3
  36. package/dist/agent-src/commands/feature/dev.md +2 -3
  37. package/dist/agent-src/commands/feature/explore.md +2 -3
  38. package/dist/agent-src/commands/feature/plan.md +2 -3
  39. package/dist/agent-src/commands/feature/refactor.md +2 -3
  40. package/dist/agent-src/commands/feature/roadmap.md +2 -3
  41. package/dist/agent-src/commands/fix/ci.md +2 -3
  42. package/dist/agent-src/commands/fix/comments.md +2 -3
  43. package/dist/agent-src/commands/fix/pr-comments.md +2 -3
  44. package/dist/agent-src/commands/fix/quality.md +2 -3
  45. package/dist/agent-src/commands/fix/route.md +2 -3
  46. package/dist/agent-src/commands/fix/seeder.md +2 -3
  47. package/dist/agent-src/commands/ghostwriter/delete.md +2 -3
  48. package/dist/agent-src/commands/ghostwriter/fetch.md +2 -3
  49. package/dist/agent-src/commands/ghostwriter/list.md +2 -3
  50. package/dist/agent-src/commands/ghostwriter/show.md +2 -3
  51. package/dist/agent-src/commands/ghostwriter/write.md +2 -3
  52. package/dist/agent-src/commands/image/analyse.md +2 -3
  53. package/dist/agent-src/commands/image/create.md +2 -3
  54. package/dist/agent-src/commands/image/verify.md +2 -3
  55. package/dist/agent-src/commands/knowledge/cross-repo.md +2 -3
  56. package/dist/agent-src/commands/knowledge/forget.md +2 -3
  57. package/dist/agent-src/commands/knowledge/ingest.md +2 -3
  58. package/dist/agent-src/commands/knowledge/list.md +2 -3
  59. package/dist/agent-src/commands/memory/add.md +2 -3
  60. package/dist/agent-src/commands/memory/learn-low-impact.md +2 -3
  61. package/dist/agent-src/commands/module/create.md +2 -3
  62. package/dist/agent-src/commands/module/explore.md +2 -3
  63. package/dist/agent-src/commands/optimize/agents-dir.md +2 -3
  64. package/dist/agent-src/commands/optimize/project.md +2 -3
  65. package/dist/agent-src/commands/optimize/prompt.md +2 -3
  66. package/dist/agent-src/commands/optimize/skills.md +2 -3
  67. package/dist/agent-src/commands/override/create.md +2 -3
  68. package/dist/agent-src/commands/override/manage.md +2 -3
  69. package/dist/agent-src/commands/post-as/ghostwriter.md +2 -3
  70. package/dist/agent-src/commands/post-as/me.md +2 -3
  71. package/dist/agent-src/commands/pr/create/description-only.md +2 -3
  72. package/dist/agent-src/commands/pr/create.md +1 -1
  73. package/dist/agent-src/commands/profile/activate.md +2 -3
  74. package/dist/agent-src/commands/profile/deactivate.md +2 -3
  75. package/dist/agent-src/commands/profile/show.md +2 -3
  76. package/dist/agent-src/commands/project/analyze.md +2 -3
  77. package/dist/agent-src/commands/project/health.md +2 -3
  78. package/dist/agent-src/commands/research/deep.md +2 -3
  79. package/dist/agent-src/commands/research/report.md +2 -3
  80. package/dist/agent-src/commands/review/changes.md +2 -3
  81. package/dist/agent-src/commands/review/routing.md +2 -3
  82. package/dist/agent-src/commands/roadmap/ai-council.md +2 -3
  83. package/dist/agent-src/commands/roadmap/create.md +2 -3
  84. package/dist/agent-src/commands/roadmap/materialize.md +2 -3
  85. package/dist/agent-src/commands/roadmap/process-full.md +2 -3
  86. package/dist/agent-src/commands/roadmap/process-phase.md +2 -3
  87. package/dist/agent-src/commands/roadmap/process-step.md +2 -3
  88. package/dist/agent-src/commands/skill/preview.md +2 -3
  89. package/dist/agent-src/commands/skills/discover.md +2 -3
  90. package/dist/agent-src/commands/sync/gitignore/fix.md +3 -4
  91. package/dist/agent-src/commands/tdd/green.md +2 -3
  92. package/dist/agent-src/commands/tdd/red.md +2 -3
  93. package/dist/agent-src/commands/tdd/refactor.md +2 -3
  94. package/dist/agent-src/commands/team/adversarial.md +2 -3
  95. package/dist/agent-src/commands/team/review.md +2 -3
  96. package/dist/agent-src/commands/team-knowledge/bootstrap.md +2 -3
  97. package/dist/agent-src/commands/team-knowledge/consolidate.md +2 -3
  98. package/dist/agent-src/commands/tests/create.md +2 -3
  99. package/dist/agent-src/commands/tests/e2e-heal.md +2 -3
  100. package/dist/agent-src/commands/tests/e2e-plan.md +2 -3
  101. package/dist/agent-src/commands/tests/execute.md +2 -3
  102. package/dist/agent-src/commands/video/from-script.md +2 -3
  103. package/dist/agent-src/commands/video/from-song.md +2 -3
  104. package/dist/agent-src/commands/video/scene.md +2 -3
  105. package/dist/agent-src/commands/video/stitch.md +2 -3
  106. package/dist/agent-src/commands/video/storyboard.md +2 -3
  107. package/dist/agent-src/commands/worktree/cleanup.md +2 -3
  108. package/dist/agent-src/commands/worktree/create.md +2 -3
  109. package/dist/agent-src/commands/worktree/status.md +2 -3
  110. package/dist/agent-src/commands/worktree/verify.md +2 -3
  111. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +10 -0
  112. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  113. package/dist/discovery/deprecation-report.md +1 -1
  114. package/dist/discovery/discovery-manifest.json +110 -110
  115. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  116. package/dist/discovery/discovery-manifest.summary.md +1 -1
  117. package/dist/discovery/orphan-report.md +1 -1
  118. package/dist/discovery/packs.json +2 -2
  119. package/dist/discovery/trust-report.md +1 -1
  120. package/dist/discovery/workspaces.json +2 -2
  121. package/dist/mcp/registry-manifest.json +1 -1
  122. package/docs/CLAIMS.md +7 -0
  123. package/docs/benchmark.md +40 -0
  124. package/docs/decisions/ADR-123-runtime-security-scope-and-spawn-hardening.md +111 -0
  125. package/docs/decisions/INDEX.md +1 -0
  126. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +26 -0
  127. package/docs/proof.md +2 -1
  128. package/docs/threat-model.md +2 -1
  129. package/package.json +1 -1
  130. package/src/scripts/_lib/spawn_env.ts +100 -0
  131. package/src/scripts/ai_council/clients.ts +6 -0
  132. package/src/scripts/bench_defect_finding.ts +348 -0
  133. package/src/scripts/hot_context_hook.ts +2 -0
  134. package/src/scripts/roadmap_progress_hook.ts +2 -0
@@ -1 +1 @@
1
- a59f1fc1e9fccbdf2e6b8b99257e83439d96bb9b0c2ad694f2cfece80671657c discovery-manifest.json
1
+ 225524bcfd872e6503022848fee38c77b6aa3553443fdb0656c447117cc476d1 discovery-manifest.json
@@ -1,6 +1,6 @@
1
1
  # Discovery Manifest — Summary
2
2
 
3
- - Generated: `2026-07-20T09:32:39Z`
3
+ - Generated: `2026-07-20T21:31:59Z`
4
4
  - Scanner: `c24312e116f8`
5
5
  - Artefacts: **575**
6
6
  - Unassigned: **0**
@@ -1,6 +1,6 @@
1
1
  # Discovery — Orphan Report
2
2
 
3
- - Generated: `2026-07-20T09:32:39Z`
3
+ - Generated: `2026-07-20T21:31:59Z`
4
4
  - Orphan artefacts: **0**
5
5
 
6
6
  > An orphan is an artefact whose declared pack has no other members.
@@ -1,6 +1,6 @@
1
1
  {
2
- "checksum": "sha256:16d037e41d10dcc83bb73c58b96e6aa3377ce5feb56a8d23aee0ef58e8a97c5c",
3
- "generated_at": "2026-07-20T09:32:39Z",
2
+ "checksum": "sha256:5d153d76966c915aef5ae07b2d82aee0f7f456e65aef5528ebfe6e3355445769",
3
+ "generated_at": "2026-07-20T21:31:59Z",
4
4
  "packs": [
5
5
  {
6
6
  "always_on": false,
@@ -1,6 +1,6 @@
1
1
  # Discovery — Trust Report
2
2
 
3
- - Generated: `2026-07-20T09:32:39Z`
3
+ - Generated: `2026-07-20T21:31:59Z`
4
4
  - Workspaces tracked: **10**
5
5
  - Human-review-required artefacts: **3**
6
6
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "checksum": "sha256:16d037e41d10dcc83bb73c58b96e6aa3377ce5feb56a8d23aee0ef58e8a97c5c",
3
- "generated_at": "2026-07-20T09:32:39Z",
2
+ "checksum": "sha256:5d153d76966c915aef5ae07b2d82aee0f7f456e65aef5528ebfe6e3355445769",
3
+ "generated_at": "2026-07-20T21:31:59Z",
4
4
  "scanner_version": "c24312e116f8",
5
5
  "workspaces": [
6
6
  {
@@ -9,7 +9,7 @@
9
9
  "homepage": "https://event4u-app.github.io/agent-config",
10
10
  "name": "@event4u/agent-config",
11
11
  "repository": "https://github.com/event4u-app/agent-config",
12
- "version": "9.4.0"
12
+ "version": "9.6.0"
13
13
  },
14
14
  "registries": [
15
15
  {
package/docs/CLAIMS.md CHANGED
@@ -245,6 +245,13 @@ visible, not hidden.
245
245
  - status: backed
246
246
  - last_verified: 2026-07-12
247
247
 
248
+ ### claim: team-defect-finding-null
249
+ - claim: On the pre-registered 12-fixture defect-finding corpus (three arms, deterministic file-level recall, codex reviewer gpt-5.5), the cross-model team-review arm produced NO recall lift over single-model self-review — all three arms recalled every planted defect (Δ = 0, H1 not met). Honest null, ceiling-limited (recall 1.00 everywhere: the seeded defects are too obvious to discriminate the arms on recall); the only non-null signal is a single self-review false positive on the controversial-but-correct control vs 0 for team/council. No cross-model quality/lift claim binds; team mode stays workflow-value-only. Re-open: a judge-survivable-subtlety corpus or a new model generation.
250
+ - kind: quant
251
+ - evidence: internal/bench/reports/defect-finding.json#honest_null
252
+ - status: backed
253
+ - last_verified: 2026-07-20
254
+
248
255
  ### claim: adversarial-council-finding-coverage
249
256
  - claim: On the residual defect pool (planted defects that survive a single strong cross-model judge), an adversarial panel of >=2 distinct-vendor skeptics finds materially more residual defects than that single judge — relative residual-recall lift >= +25% AND absolute >= +8 percentage points — at a false-positive rate on a controversial-but-correct control no worse than the single-judge baseline (within noise). Scope: finding coverage, NOT decision quality (the separate, unbacked council-vs-solo-baseline question). Pre-registered; honest-null (either threshold missed or FP worse) keeps the surface off by default permanently.
250
257
  - kind: quant
package/docs/benchmark.md CHANGED
@@ -582,3 +582,43 @@ controversial-but-correct control.
582
582
  judge-survivable-subtlety corpus with a published distribution, then (2) a
583
583
  maintainer-gated paid cross-vendor run. Honest-null on that run keeps the
584
584
  surface default-off permanently, like recursive-verification.
585
+
586
+
587
+ ## Defect-finding: team vs self-review vs council (2026-07-20) — HONEST NULL (ceiling-limited) {#honest-null-defect}
588
+
589
+ Pre-registered 12-fixture corpus (`internal/bench/corpora/defect-finding.yaml`:
590
+ 10 seeded-defect diffs across logic / off-by-one / race / missing-empty-state /
591
+ security-smell, + 2 controls), three arms, deterministic file-level recall
592
+ against ground truth (blind rubric judge deferred — the primary metric is
593
+ deterministic). Codex reviewer pinned `gpt-5.5`. Total billable $0.083 (arm b
594
+ codex = ChatGPT subscription, $0).
595
+
596
+ | arm | recall | correctness | design | false-positives |
597
+ |---|--:|--:|--:|--:|
598
+ | self-review (single model) | 1.00 | 1.00 | 1.00 | 1 |
599
+ | team (cross-model, codex) | 1.00 | 1.00 | 1.00 | 0 |
600
+ | council (neutral breadth) | 1.00 | 1.00 | 1.00 | 0 |
601
+
602
+ **Verdict: HONEST NULL.** H1 (cross-model team > single-model self-review on
603
+ correctness recall, Δ ≥ +0.20) is **not met** — Δ = 0: all three arms recalled
604
+ every planted defect. H2 (council ≈ team on design, within 0.10) is met (Δ = 0).
605
+ H3 (≤ 1 false positive/arm) is met.
606
+
607
+ **Corpus-validity caveat (the honest bound):** recall 1.00 across every arm is a
608
+ **ceiling effect** — these seeded defects are catchable by any strong model, so
609
+ the corpus cannot discriminate the arms on recall. This is the same limitation
610
+ the adversarial-verification-council section names: a corpus of obvious,
611
+ model-differentiating defects measures parity, not the judge-survivable
612
+ subtleties where a cross-model lens might actually differ. The one non-null
613
+ signal is precision, not recall: single-model self-review produced 1 false
614
+ positive on the controversial-but-correct control (`df-ctl-01`, a
615
+ behaviour-preserving `clamp` refactor) where team and council produced 0 — a
616
+ hint of a multi-arm precision edge, within the pre-registered H3 bound, too
617
+ small to claim.
618
+
619
+ **Disposition (Phase 5 Step 5): evidence-closed as NULL.** No cross-model
620
+ defect-finding *quality/lift* claim binds; team mode stays documented as
621
+ **workflow value only**. Re-open conditions: (a) a curated
622
+ judge-survivable-subtlety corpus that breaks the recall ceiling, or (b) a new
623
+ model generation. The worker-via-bundle delegate (roadmap Phase 3 Step 4) stays
624
+ deferred — it re-opened only on a measured review lift, which did not occur.
@@ -0,0 +1,111 @@
1
+ ---
2
+ adr: 123
3
+ status: accepted
4
+ date: 2026-07-20
5
+ decision: runtime-security-scope-and-spawn-hardening
6
+ supersedes: —
7
+ superseded_by: —
8
+ phase: road-to-runtime-security-hardening
9
+ type: structural
10
+ ---
11
+
12
+ # ADR-123 — Runtime-security scope: harden subprocess spawn-env, keep behavioural enforcement out of scope
13
+
14
+ - **Status:** Accepted (2026-07-20)
15
+ - **Closes:** `agents/roadmaps/archive/road-to-runtime-security-hardening.md`
16
+ - **Related:** `docs/threat-model.md` row (d) (lethal-trifecta); `src/rules/tool-safety.md` (Least Agency); `src/rules/lethal-trifecta-guard.md`; `src/rules/untrusted-input-defense.md`.
17
+
18
+ ## Context
19
+
20
+ A user-supplied source-level analysis compared agent-config's security stack
21
+ against an external reference plugin (referred to only as **Source A** per
22
+ `source-confidentiality`; © the third party, all-rights-reserved — concept
23
+ adoption only, no code/regex/prompt port). It surfaced two distinct things that
24
+ were repeatedly conflated:
25
+
26
+ 1. **A real shipped bug.** `src/scripts/ai_council/clients.ts::_runSubprocess`
27
+ built its `spawnSync` options with no `env:` key, so every spawned provider
28
+ CLI (`codex` / `claude` / `gemini`) — and any `git` those CLIs invoke
29
+ internally — inherited the full parent `process.env`. An attacker-influenced
30
+ `GIT_EXTERNAL_DIFF` / `core.pager` / `NODE_OPTIONS` / `LD_PRELOAD` /
31
+ `DYLD_INSERT_LIBRARIES` in the parent environment is remote code execution
32
+ (CWE-426 untrusted search path, CWE-88 argument injection). This is our own
33
+ transport violating the Least-Agency its `tool-safety` rule preaches.
34
+
35
+ 2. **A proposed scope reversal.** The analysis argued that because
36
+ `injection_scan_hook.ts` already scans **inbound** tool responses, the
37
+ threat-model's "runtime enforcement out of scope" decision (row (d)) is
38
+ already void, so we should also scan **outbound** — the content the agent
39
+ writes — with a pattern guard, a turn-diff baseline, and eventually an LLM
40
+ diff-review.
41
+
42
+ A two-round council (2026-07-20, anthropic/claude-sonnet-4-5 + openai/gpt-4o)
43
+ was decisive: round 1 leaned toward building the outbound guard; **round 2 both
44
+ members pivoted skeptical** on the load-bearing distinction below.
45
+
46
+ ## Decision
47
+
48
+ **1. Fix the spawn-env RCE (accepted, shipped in this roadmap).** A shared
49
+ `src/scripts/_lib/spawn_env.ts::hardenedSpawnEnv(overrides?)` scrubs the
50
+ code-execution-injection env families **by family, not by allowlist** — the
51
+ provider CLIs legitimately need arbitrary env (API keys, config paths), so an
52
+ allowlist would break them and rot; deny-by-family removes the known vectors
53
+ (dynamic-loader `LD_*`/`DYLD_*`/`GCONV_PATH`, git `*_COMMAND`/`GIT_EXTERNAL_DIFF`/
54
+ `GIT_SSH_COMMAND`/`PAGER`, runtime hooks `NODE_OPTIONS`/`BASH_ENV`/`ENV`/
55
+ `PYTHON*`/`PERL5*`/`RUBYOPT`, and `IFS`) and preserves everything else. It is
56
+ wired into the council transport and the two consumer-runtime hook spawn sites
57
+ (`roadmap_progress_hook`, `hot_context_hook`), and guarded by a falsifiable
58
+ regression test (proven RED without the fix — 8 vectors leaked into the child —
59
+ GREEN with it).
60
+
61
+ **2. Input sanitization ≠ runtime behavioural enforcement — row (d) stands.**
62
+ The inbound `injection_scan_hook` is **self-defence**: it hardens AC's own
63
+ handling of data it *receives* from external tools. It does **not** commit AC to
64
+ *governing what the agent writes*. An outbound command guard requires
65
+ interpreting intent from content (`rm -rf ./build` vs `rm -rf /`) — that is a
66
+ runtime **enforcement** layer, a categorically different mechanism, and the one
67
+ threat-model row (d) places out of scope. AC ships the *rules*
68
+ (`lethal-trifecta-guard`, `untrusted-input-defense`, `tool-safety`); the
69
+ consumer's tool-execution layer is the enforcement boundary. The inbound/outbound
70
+ "asymmetry" is a defensible separation of concerns, not security theatre.
71
+
72
+ **3. The outbound guard / turn-diff baseline / LLM diff-review are deferred, not
73
+ rejected.** They remain `## Blockers` on the roadmap with a `revisit-if`: a real
74
+ outbound-injection incident, a consumer demand signal, or utilization evidence —
75
+ at which point they must also pass the six-question complexity budget
76
+ (`artifact-drafting-protocol-mechanics` § Complexity budget), given PR #983 just
77
+ consolidated surface and `surface-consolidation-restraint` freezes new modes
78
+ pending the first external adopter. This ADR records the deferral so the
79
+ decision is surfaced, not silently dropped (`decision-revisit-gate`).
80
+
81
+ **4. Rejected:** a consumer-supplied-pattern ReDoS/trust-framing layer (Source A
82
+ P5) — `security_lint.ts` has no consumer-pattern surface, so guarding it would
83
+ build a defence for a feature that does not exist. Global env/pattern allowlists
84
+ are also rejected: per-file pragma containment (`security_lint.ts`) only.
85
+
86
+ ## Consequences
87
+
88
+ - The council transport and consumer-runtime hooks can no longer be turned into
89
+ an RCE vector by a poisoned parent environment; the fix is falsifiably tested.
90
+ - `docs/threat-model.md` gains a row for the (now-mitigated)
91
+ subprocess-env-inheritance vector, and row (d)'s "no additional mitigation
92
+ planned" cell cites this ADR for the input-sanitization-vs-enforcement
93
+ distinction and the deferred-guard decision.
94
+ - AC's security posture stays **authoring-time rules + input hygiene + our own
95
+ Least-Agency spawn discipline** — it does not become a runtime sandbox
96
+ supervising every agent tool call.
97
+ - Maintainer-only CI/git-helper spawn sites (~15) are a deferred mechanical
98
+ sweep (`maintainer-ci-spawn-sweep` blocker): they run in trusted CI with lower
99
+ attacker-control of the environment.
100
+
101
+ ## Alternatives considered
102
+
103
+ - **Complete the runtime layer (build the outbound guard now).** Council round 1
104
+ position. Rejected in round 2: conflates input sanitization with behavioural
105
+ enforcement; adds net-new enforcement surface with a false-positive burden,
106
+ right after a consolidation roadmap, with no demand signal.
107
+ - **Delete `injection_scan_hook.ts` for symmetry.** Architecturally consistent
108
+ but operationally worse — inbound sanitization is legitimate self-defence and
109
+ costs nothing to keep.
110
+ - **Env allowlist instead of deny-by-family.** Rejected — would strip env the
111
+ provider CLIs need and rot as new good vars appear.
@@ -126,6 +126,7 @@ _Auto-generated by `scripts/adr/regenerate_index.py`. Do not edit._
126
126
  | [ADR-120](ADR-120-council-chairman-mode.md) | Council Chairman Mode | accepted | 2026-07-12 | — |
127
127
  | [ADR-121](ADR-121-knowledge-sensitivity-classes.md) | Knowledge Sensitivity Classes | accepted | 2026-07-12 | — |
128
128
  | [ADR-122](ADR-122-adversarial-verification-council.md) | Adversarial Verification Council | accepted | 2026-07-14 | — |
129
+ | [ADR-123](ADR-123-runtime-security-scope-and-spawn-hardening.md) | Runtime Security Scope And Spawn Hardening | accepted | 2026-07-20 | — |
129
130
  | [ADR-200](ADR-200-python-to-typescript-migration.md) | Python To Typescript Migration | accepted | 2026-06-11 | — |
130
131
 
131
132
  ## Unnumbered (legacy)
@@ -70,6 +70,32 @@ those artifacts:
70
70
  - Batch mode never skips the Research pass itself — it relocates and
71
71
  batches it. `artifact_protocol: skip` does not exist.
72
72
 
73
+ ## Complexity budget — the six questions before a new artefact
74
+
75
+ Folded here from the 9.4.0 review (Prio 5), which named the failure mode:
76
+ "das Paket kann fast jede Kritik mit einem weiteren Mechanismus beantworten"
77
+ (Problem → Regel → Lint → Hook → Report → Roadmap → Claim → Test = governance
78
+ inflation). This REPLACES the ad-hoc "should this exist" prose — it is a
79
+ checklist, not a new gate/rule (adding a gate to enforce "add less" would be
80
+ the very inflation it guards against). Before drafting any new skill / rule /
81
+ command / hook / gate, answer all six; a "no" on **replaces** or **removable**
82
+ is a strong signal to fold into an existing artefact instead:
83
+
84
+ 1. **Replaces?** — what existing mechanism does this retire or subsume? (If
85
+ nothing, justify why the surface must grow.)
86
+ 2. **Overlaps?** — does it duplicate a trigger / responsibility another
87
+ artefact already owns?
88
+ 3. **Discoverable?** — will a user actually find it, or does it just raise the
89
+ catalog count?
90
+ 4. **Measurable?** — is there a signal that would show it earning its place
91
+ (or a null that would retire it)?
92
+ 5. **Removable?** — can it be demoted / de-eligibled / deleted later without a
93
+ migration?
94
+ 6. **Who debugs it?** — is the maintenance owner real, given bus-factor?
95
+
96
+ The load-bearing question the review elevates: not "how do we prevent this
97
+ error in future?" but **"which existing mechanism gets removed or replaced?"**
98
+
73
99
  ## See also
74
100
 
75
101
  - `artifact-drafting-protocol` (rule) — Iron Law, triggers, golden rules.
package/docs/proof.md CHANGED
@@ -48,9 +48,10 @@ evidence pointer, or `task check-claims` fails the build.
48
48
  | Every artifact the package ships — source AND the condensed projection that reaches consumers — is machine-scanned in CI for hidden-Unicode, mixed-script-confusable, and instruction-smuggling payloads (the rules-file-backdoor class); a finding blocks the release before `npm publish`, not just the merge. | qual | `.github/workflows/publish-npm.yml#lint_agent_security` | ✅ |
49
49
  | 278 skills. | quant | `src/scripts/check_artefact_count_messaging.ts#Artefact-count messaging gate` | ✅ |
50
50
  | Removes only its own keys from a shared host config (matched by JSON-pointer + SHA-256), never a neighbour tool's entries. | qual | `docs/contracts/install-layout.md#JSON-pointer` | ✅ |
51
+ | On the pre-registered 12-fixture defect-finding corpus (three arms, deterministic file-level recall, codex reviewer gpt-5.5), the cross-model team-review arm produced NO recall lift over single-model self-review — all three arms recalled every planted defect (Δ = 0, H1 not met). Honest null, ceiling-limited (recall 1.00 everywhere: the seeded defects are too obvious to discriminate the arms on recall); the only non-null signal is a single self-review false positive on the controversial-but-correct control vs 0 for team/council. No cross-model quality/lift claim binds; team mode stays workflow-value-only. Re-open: a judge-survivable-subtlety corpus or a new model generation. | quant | `internal/bench/reports/defect-finding.json#honest_null` | ✅ |
51
52
  | On the A3 orchestration eval (deterministic verify, measured token deltas), the production-validator subagent returned the correct verdict on both fixtures — NOT READY with the exact `file:line` citation on a planted hollow implementation, READY with zero spurious findings on the clean control — while consuming ~45k fewer tokens than the inline-host baseline on each task. Scope: two planted fixtures on a Claude Code host, not a broad hit-rate. | quant | `internal/bench/orchestration/pv-a3-results.md#token_delta_provenance: measured` | ✅ |
52
53
 
53
- **23 backed claim(s)** — all evidence pointers resolve in CI.
54
+ **24 backed claim(s)** — all evidence pointers resolve in CI.
54
55
 
55
56
  Artefact counts in public prose (skills, commands, governed rules,
56
57
  guidelines, personas) are **generated from source and CI-drift-checked**:
@@ -13,9 +13,10 @@ and holds no user data. Threats are about **artifact integrity** and
13
13
  | a | **Malicious skill / rule via PR** — a contributed `.md` file smuggles prompt-injection or data-exfiltration payloads into the shipped artifact tree | Adversarial PR contributor embeds: (1) invisible Unicode (bidi / zero-width / Tag-block) to hide instructions, (2) role-takeover imperatives, (3) lethal-trifecta pattern in a new skill (private-data + untrusted-content + egress on one autonomous path) | `lint_hidden_unicode.py` — blocks Trojan-Source and invisible-char attacks; `lint_instruction_smuggling.py` — blocks disclosure-suppression and injection-tag prose; `lint_skill_frontmatter_safety.py` — blocks wildcard `allowed_tools`, `bypassPermissions`, unsafe `execution.type: automated`; PR review gate | No automated SARIF upload to GitHub Code Scanning; lethal-trifecta detection is architectural (rule) not linted structurally beyond the frontmatter check | Wire `lint_agent_security.py --sarif` output to GitHub Advanced Security on every PR (`lint_workflow_security.py` — P2); add structural trifecta check to frontmatter linter |
14
14
  | b | **Supply-chain compromise of AC's own deps** — a malicious version of an npm or Python dependency is pulled during CI / install and executes arbitrary code or modifies shipped artifacts | Dependency confusion / typosquatting attack on `package.json` or `requirements.txt` deps; compromised upstream package | `package-lock.json` + `requirements.txt` pin exact versions; CI installs from lockfile (`npm ci`) | No documented dep-audit policy; no Dependabot / Renovate automation; no integrity check (`npm audit` / `pip-audit`) in CI gate | Add `npm audit --audit-level=high` + `pip-audit` as a required CI step; document quarterly manual review cadence; pin Python deps in a lockfile (`pip-compile`) |
15
15
  | c | **Hostile consumer-repo environment** — AC's installer or hooks execute inside a repo whose `.git/hooks`, `.env`, or workspace files are attacker-controlled | Consumer repo pre-installs malicious Git hooks that intercept the AC installer or post-install hook; `.env` leaks tokens that AC hooks read; untrusted files are passed to AC linters | AC installer writes its own hooks under its namespace; AC hooks run as the user's shell — they do not read `.env` by design; `untrusted-input-defense` rule governs agent behaviour at runtime | No sandboxed execution for AC hooks; installer does not verify consumer hook integrity before writing | Document that AC hooks must not read repo-local env files; add installer pre-flight that warns if foreign hooks exist in `.git/hooks/`; scope AC hook writes to explicit namespaced filenames |
16
- | d | **Lethal-trifecta surface** — a skill combining private-data access + untrusted-content ingestion + external communication on one autonomous path enables confused-deputy / data-exfiltration attacks | Prompt-injected content inside a fetched doc instructs an agent to read a local secret and POST it to an attacker-controlled endpoint | `src/rules/lethal-trifecta-guard.md` (Iron Law: break one leg or gate egress behind human-in-the-loop); `src/rules/untrusted-input-defense.md` (untrusted content is data, never instructions; spotlighting); `lint_skill_frontmatter_safety.py` (flags over-broad tool grants) | These are authoring-time controls — no runtime enforcement layer exists | No additional mitigation planned (runtime layer out of scope for AC); `lethal-trifecta-guard.md` and `untrusted-input-defense.md` are the canonical reference — do not re-describe here |
16
+ | d | **Lethal-trifecta surface** — a skill combining private-data access + untrusted-content ingestion + external communication on one autonomous path enables confused-deputy / data-exfiltration attacks | Prompt-injected content inside a fetched doc instructs an agent to read a local secret and POST it to an attacker-controlled endpoint | `src/rules/lethal-trifecta-guard.md` (Iron Law: break one leg or gate egress behind human-in-the-loop); `src/rules/untrusted-input-defense.md` (untrusted content is data, never instructions; spotlighting); `lint_skill_frontmatter_safety.py` (flags over-broad tool grants) | These are authoring-time controls — no runtime *behavioural-enforcement* layer exists (input sanitization ≠ enforcement — see ADR-123) | Runtime behavioural enforcement stays out of scope for AC (`ADR-123-runtime-security-scope-and-spawn-hardening`): AC ships the rules, the consumer's tool-execution layer enforces them; the outbound content-scan guard is deferred with a `revisit-if`, not silently dropped. `lethal-trifecta-guard.md` and `untrusted-input-defense.md` are the canonical reference — do not re-describe here |
17
17
  | e | **Malicious metadata / frontmatter injection** — a PR embeds hostile YAML in a skill's frontmatter (e.g. injecting a trigger keyword that causes a dangerous skill to fire in unintended contexts, or adding `permissionMode: bypassPermissions`) | PR contributor adds `permissionMode: bypassPermissions` or a wildcard `allowed_tools: ["*"]` entry to an existing skill's frontmatter; or crafts a trigger keyword to hijack routing | `lint_skill_frontmatter_safety.py` — HIGH-severity block on `bypassPermissions`, wildcard `allowed_tools`, unsafe `execution.type: automated` without safety floor; `lint_hidden_unicode.py` — catches invisible chars inside frontmatter YAML values; PR review gate; `scripts/validate_frontmatter.py` schema validation | Schema validation does not verify trigger-keyword semantic safety (a plausible keyword that causes over-broad firing is not caught) | Extend `validate_frontmatter.py` to flag trigger keywords that match security-sensitive path prefixes (e.g. a skill triggering on `"auth"` when it has `execution.type: automated`) |
18
18
  | f | **Skill self-update — safety-floor rewrite** — an agent is social-engineered (via chat or a PR) into weakening a kernel rule, safety floor, or MCP/tool allowlist | A PR removes the Iron Law block from `non-destructive-by-default.md`; or a chat prompt instructs the agent to edit `security-sensitive-stop.md` to narrow its self-modification clause; or a PR modifies `scope-control` to remove the Hard Floor | `src/rules/security-sensitive-stop.md` — "self-modification via chat" clause: requests to weaken safety floors / kernel rules / MCP allowlists must route through `scope-control` gates; `src/rules/scope-control.md` — kernel-rule edits require own PR + ≥ 24 h soak; `dist/agent-src/` is read-only (source-of-truth rule); `scripts/check_condensation.py` detects Iron-Law section deletion or downgrade | No automated per-rule diff check that fires specifically when a kernel rule's Iron Law section is removed; relies on PR reviewer recognising the file class | Add a CI check (`scripts/check_kernel_rule_integrity.py` — P3+) that asserts Iron Law fenced-code blocks survive unchanged in all files listed in `docs/contracts/kernel-membership.md` |
19
+ | g | **Subprocess spawn-env inheritance** — AC's own transport spawns an external CLI (the AI-council provider CLIs; consumer-runtime hooks spawn `git`) with the full parent `process.env`, so an attacker-influenced env var in the parent becomes RCE when the child (or a `git` it invokes internally) runs | A poisoned `GIT_EXTERNAL_DIFF` / `core.pager` / `GIT_SSH_COMMAND`, a dynamic-loader `LD_PRELOAD` / `DYLD_INSERT_LIBRARIES` / `GCONV_PATH`, or a runtime hook `NODE_OPTIONS` / `BASH_ENV` / `PYTHONSTARTUP` present in the parent env executes attacker code inside the spawned process (CWE-426, CWE-88, CWE-427) | `src/scripts/_lib/spawn_env.ts::hardenedSpawnEnv()` scrubs the code-execution-injection env families (deny-by-family: loader `LD_*`/`DYLD_*`/`GCONV_PATH`, git `*_COMMAND`/`GIT_EXTERNAL_DIFF`/`GIT_SSH_COMMAND`/`GIT_PAGER`/`PAGER`, runtime hooks `NODE_OPTIONS`/`BASH_ENV`/`ENV`/`PYTHON*`/`PERL5*`/`RUBYOPT`, `IFS`) and preserves the rest; wired into `clients.ts::_runSubprocess`, `roadmap_progress_hook.ts`, `hot_context_hook.ts`; falsifiable regression test in `tests/scripts/ai_council/spawn_env.test.ts` (ADR-123) | Maintainer-only CI/git-helper spawn sites (~15) still inherit env — lower risk (trusted CI, low attacker-control) | Mechanical sweep applying `hardenedSpawnEnv` to the remaining CI/git-helper spawn sites (deferred — `maintainer-ci-spawn-sweep` blocker) |
19
20
 
20
21
  ## Review cadence
21
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "9.4.0",
3
+ "version": "9.6.0",
4
4
  "description": "Universal AI Agent OS — audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Hardened environment for spawned subprocesses
3
+ * (road-to-runtime-security-hardening Phase 1).
4
+ *
5
+ * agent-config shells out to external CLIs (the AI-council transport spawns
6
+ * `codex` / `claude` / `gemini`; hooks spawn `git`). By default `spawnSync`
7
+ * inherits the full parent `process.env`, so any attacker-influenced env var in
8
+ * the parent becomes a code-execution vector the moment the child (or a `git`
9
+ * it invokes internally) runs:
10
+ *
11
+ * - dynamic-loader preload/search-path (CWE-426 untrusted search path):
12
+ * LD_PRELOAD, LD_LIBRARY_PATH, DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH,
13
+ * GCONV_PATH — load attacker code into any spawned ELF/Mach-O.
14
+ * - git command-injection (CWE-88): GIT_EXTERNAL_DIFF, GIT_SSH,
15
+ * GIT_SSH_COMMAND, GIT_PROXY_COMMAND, any GIT_*_COMMAND, GIT_PAGER, PAGER —
16
+ * git runs these as shell commands (`core.pager` / external-diff RCE).
17
+ * - runtime auto-exec hooks: NODE_OPTIONS (`--require attacker.js`),
18
+ * BASH_ENV / ENV (sourced by non-interactive shells), PYTHONPATH /
19
+ * PYTHONSTARTUP / PYTHONINSPECT, PERL5OPT / PERL5LIB, RUBYOPT.
20
+ * - IFS — classic shell field-splitting hijack.
21
+ *
22
+ * This is the Least-Agency the `tool-safety` rule preaches, applied to our own
23
+ * transport. We scrub by *family* (prefix + exact name), NOT via an allowlist:
24
+ * the CLIs legitimately need arbitrary env (ANTHROPIC_API_KEY, XDG config
25
+ * paths, proxy settings), so an allowlist would break them and rot as new
26
+ * good vars appear. Deny-by-family removes the known execution vectors and
27
+ * preserves everything else. This is standard, publicly-documented OWASP/CWE
28
+ * knowledge — independently implemented here.
29
+ */
30
+
31
+ /** Exact env-var names removed unconditionally. */
32
+ const DENY_EXACT: ReadonlySet<string> = new Set([
33
+ // dynamic loader
34
+ 'LD_PRELOAD',
35
+ 'LD_LIBRARY_PATH',
36
+ 'LD_AUDIT',
37
+ 'DYLD_INSERT_LIBRARIES',
38
+ 'DYLD_LIBRARY_PATH',
39
+ 'DYLD_FRAMEWORK_PATH',
40
+ 'GCONV_PATH',
41
+ // git command-injection
42
+ 'GIT_EXTERNAL_DIFF',
43
+ 'GIT_SSH',
44
+ 'GIT_SSH_COMMAND',
45
+ 'GIT_PROXY_COMMAND',
46
+ 'GIT_PAGER',
47
+ 'PAGER',
48
+ // runtime auto-exec hooks
49
+ 'NODE_OPTIONS',
50
+ 'BASH_ENV',
51
+ 'ENV',
52
+ 'PYTHONPATH',
53
+ 'PYTHONSTARTUP',
54
+ 'PYTHONINSPECT',
55
+ 'PERL5OPT',
56
+ 'PERL5LIB',
57
+ 'RUBYOPT',
58
+ // shell field-splitting
59
+ 'IFS',
60
+ ]);
61
+
62
+ /**
63
+ * Prefix families removed by pattern. `LD_` / `DYLD_` cover loader variants we
64
+ * did not enumerate; `GIT_*_COMMAND` covers every git config-command hook
65
+ * (e.g. GIT_ASKPASS is intentionally NOT here — it is a path, not a shell
66
+ * command, and legitimate credential helpers need it; the *_COMMAND family is
67
+ * the shell-executed one).
68
+ */
69
+ function isDeniedByFamily(name: string): boolean {
70
+ if (name.startsWith('LD_') || name.startsWith('DYLD_')) return true;
71
+ if (name.startsWith('GIT_') && name.endsWith('_COMMAND')) return true;
72
+ return false;
73
+ }
74
+
75
+ /**
76
+ * Return a copy of the current environment with code-execution-injection
77
+ * vectors scrubbed, then `overrides` applied. Pass the result as
78
+ * `spawnSync`/`spawn`'s `env:` option.
79
+ *
80
+ * @param overrides values to set AFTER scrubbing (e.g. a scoped PATH or a
81
+ * package-root marker). Overrides win over both the inherited env and the
82
+ * deny-list, so a caller can deliberately re-supply a scrubbed var if it
83
+ * genuinely needs one — that is an explicit, reviewable decision at the call
84
+ * site, never an ambient inheritance.
85
+ */
86
+ export function hardenedSpawnEnv(
87
+ overrides: Record<string, string> = {},
88
+ ): NodeJS.ProcessEnv {
89
+ const out: NodeJS.ProcessEnv = {};
90
+ for (const [key, value] of Object.entries(process.env)) {
91
+ if (value === undefined) continue;
92
+ if (DENY_EXACT.has(key)) continue;
93
+ if (isDeniedByFamily(key)) continue;
94
+ out[key] = value;
95
+ }
96
+ for (const [key, value] of Object.entries(overrides)) {
97
+ out[key] = value;
98
+ }
99
+ return out;
100
+ }
@@ -44,6 +44,7 @@ import { spawnSync } from 'node:child_process';
44
44
  import * as fs from 'node:fs';
45
45
  import * as path from 'node:path';
46
46
 
47
+ import { hardenedSpawnEnv } from '../_lib/spawn_env.js';
47
48
  import * as user_global_paths from '../_lib/user_global_paths.js';
48
49
  import { appendEvent } from './events_log.js';
49
50
 
@@ -1101,6 +1102,11 @@ export abstract class CliClient extends ExternalAIClient {
1101
1102
  const spawnOpts: Parameters<typeof spawnSync>[2] = {
1102
1103
  encoding: 'utf-8',
1103
1104
  timeout: Math.round(this.timeout_seconds * 1000),
1105
+ // Least-Agency: scrub code-execution-injection env vectors
1106
+ // (loader preload, git *_COMMAND, NODE_OPTIONS, …) so an
1107
+ // attacker-influenced parent env cannot RCE via the spawned CLI
1108
+ // or a `git` it invokes internally.
1109
+ env: hardenedSpawnEnv(),
1104
1110
  };
1105
1111
  if (stdinPayload !== null) {
1106
1112
  spawnOpts.input = stdinPayload;