@bongos/core 1.19.257 → 1.19.258
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.
- package/.bongos-core.json +70 -25
- package/docs/adr/0022-secrets-policy.md +16 -12
- package/docs/file-map.md +1 -0
- package/docs/handoff-template.md +1 -1
- package/docs/recipes/README.md +1 -1
- package/docs/recipes/redteam-patrol.md +48 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/control-manifest.js +123 -0
- package/scripts/gds/fitness-ratchets.js +328 -0
- package/scripts/gds/fitness.js +4 -4
- package/scripts/gds/gen-session-index.js +8 -1
- package/scripts/gds/redteam-patrol.js +308 -0
- package/src/bongos/logger.js +118 -0
- package/src/bongos/middleware/audit.js +4 -3
- package/src/bongos/middleware/error-envelope.js +5 -4
- package/src/bongos/module-scope-map.js +1 -0
- package/src/bongos/serve-internal.js +2 -1
- package/src/module-api.js +3 -1
- package/src/platform-server.js +9 -2
- package/tests/auth_resolve_failure.mjs +1 -1
- package/tests/claude_materialize.mjs +28 -11
- package/tests/control_manifest.mjs +60 -0
- package/tests/fitness_ratchets.mjs +91 -0
- package/tests/logger.mjs +89 -0
- package/tests/publish_reconciler.mjs +142 -0
- package/tests/redteam_patrol.mjs +67 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.258",
|
|
6
|
+
"core_contract": "1.19.258",
|
|
7
|
+
"source_commit": "66f71503310987ea2c91b4e930f3d5c81b3fb018",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-
|
|
9
|
+
"built_at": "2026-08-19T18:01:56.705Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
|
-
"docs_redacted":
|
|
12
|
+
"docs_redacted": 317,
|
|
13
13
|
"agent_docs_stubbed": 22,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1358,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1697,
|
|
20
|
+
"tree_sha256": "66ae54f6968edbafb532b3e566ec10fad20a45ca1c48af7951a8bc3fe1a06cd7",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -527,7 +527,7 @@
|
|
|
527
527
|
{
|
|
528
528
|
"path": "docs/adr/0022-secrets-policy.md",
|
|
529
529
|
"mode": "0000644",
|
|
530
|
-
"sha256": "
|
|
530
|
+
"sha256": "8ea4766c05c27517d19a9ef7343e4083e71604b22b88ed058071dc82399529bb"
|
|
531
531
|
},
|
|
532
532
|
{
|
|
533
533
|
"path": "docs/adr/0023-kind-multipliers-and-idea-bonus.md",
|
|
@@ -1532,12 +1532,12 @@
|
|
|
1532
1532
|
{
|
|
1533
1533
|
"path": "docs/file-map.md",
|
|
1534
1534
|
"mode": "0000644",
|
|
1535
|
-
"sha256": "
|
|
1535
|
+
"sha256": "9762b7f69397c28e103cdb39a9987483e23bd800a827f0d83983b62ef962616b"
|
|
1536
1536
|
},
|
|
1537
1537
|
{
|
|
1538
1538
|
"path": "docs/handoff-template.md",
|
|
1539
1539
|
"mode": "0000644",
|
|
1540
|
-
"sha256": "
|
|
1540
|
+
"sha256": "a60d85b8523b5944d4db8105b60166395ab9c1f931fb92f0f95a825b657fe277"
|
|
1541
1541
|
},
|
|
1542
1542
|
{
|
|
1543
1543
|
"path": "docs/modules-contract.md",
|
|
@@ -1617,7 +1617,7 @@
|
|
|
1617
1617
|
{
|
|
1618
1618
|
"path": "docs/recipes/README.md",
|
|
1619
1619
|
"mode": "0000644",
|
|
1620
|
-
"sha256": "
|
|
1620
|
+
"sha256": "bf3caea65a8b8a117d40544fdf352c235bb120a69444645d62b91f999a9b7731"
|
|
1621
1621
|
},
|
|
1622
1622
|
{
|
|
1623
1623
|
"path": "docs/recipes/audit-replay.md",
|
|
@@ -1729,6 +1729,11 @@
|
|
|
1729
1729
|
"mode": "0000644",
|
|
1730
1730
|
"sha256": "5bfca9628859f1cdd79d741bdd50f3fe415430ee3380a2ac7f46b97ebe082d0e"
|
|
1731
1731
|
},
|
|
1732
|
+
{
|
|
1733
|
+
"path": "docs/recipes/redteam-patrol.md",
|
|
1734
|
+
"mode": "0000644",
|
|
1735
|
+
"sha256": "3dbdc33eb1aa68da5e6569e836845f09fd14953d94794e005af67ce517fb6dad"
|
|
1736
|
+
},
|
|
1732
1737
|
{
|
|
1733
1738
|
"path": "docs/recipes/search-index-ingestion.md",
|
|
1734
1739
|
"mode": "0000644",
|
|
@@ -4442,12 +4447,12 @@
|
|
|
4442
4447
|
{
|
|
4443
4448
|
"path": "package-lock.json",
|
|
4444
4449
|
"mode": "0000644",
|
|
4445
|
-
"sha256": "
|
|
4450
|
+
"sha256": "815b7d1a58cdf1a0d9c7e30a97f70faa146ee6f826501f0c42f1b38529851575"
|
|
4446
4451
|
},
|
|
4447
4452
|
{
|
|
4448
4453
|
"path": "package.json",
|
|
4449
4454
|
"mode": "0000644",
|
|
4450
|
-
"sha256": "
|
|
4455
|
+
"sha256": "dcc8774d55d45595b6e3f3229a278b4bf5e263438bb730a8bdd620edf9c47bbd"
|
|
4451
4456
|
},
|
|
4452
4457
|
{
|
|
4453
4458
|
"path": "public-docs/index.html",
|
|
@@ -4969,6 +4974,11 @@
|
|
|
4969
4974
|
"mode": "0000644",
|
|
4970
4975
|
"sha256": "8ca6ee391837d7e1a45e69fc62c4a051f5e3d6060db507cdcbc7a343e2b03bd3"
|
|
4971
4976
|
},
|
|
4977
|
+
{
|
|
4978
|
+
"path": "scripts/gds/control-manifest.js",
|
|
4979
|
+
"mode": "0000644",
|
|
4980
|
+
"sha256": "0d5b0c5e4bc15ff914d34849607ad2f01d1843d4dafeafdaf4191c2f365300bb"
|
|
4981
|
+
},
|
|
4972
4982
|
{
|
|
4973
4983
|
"path": "scripts/gds/copy-inventory.js",
|
|
4974
4984
|
"mode": "0000644",
|
|
@@ -5079,10 +5089,15 @@
|
|
|
5079
5089
|
"mode": "0000644",
|
|
5080
5090
|
"sha256": "7a7721155c8c7cb169a48e6f279e5d7c0ba0dc4462678a4a2ec41a2686501962"
|
|
5081
5091
|
},
|
|
5092
|
+
{
|
|
5093
|
+
"path": "scripts/gds/fitness-ratchets.js",
|
|
5094
|
+
"mode": "0000644",
|
|
5095
|
+
"sha256": "a27a3873f516abdc52f7f482603779010bbc1af4d6e73c0b86dd34292017bc9a"
|
|
5096
|
+
},
|
|
5082
5097
|
{
|
|
5083
5098
|
"path": "scripts/gds/fitness.js",
|
|
5084
5099
|
"mode": "0000644",
|
|
5085
|
-
"sha256": "
|
|
5100
|
+
"sha256": "81b35af96345cfdca3f30a9d4f333715c4dd431657a9aa73b5794c02ef75686f"
|
|
5086
5101
|
},
|
|
5087
5102
|
{
|
|
5088
5103
|
"path": "scripts/gds/gate-review.js",
|
|
@@ -5152,7 +5167,7 @@
|
|
|
5152
5167
|
{
|
|
5153
5168
|
"path": "scripts/gds/gen-session-index.js",
|
|
5154
5169
|
"mode": "0000644",
|
|
5155
|
-
"sha256": "
|
|
5170
|
+
"sha256": "9b936143ae25442fefb52b98949bc33d245d4b3894d778c51926e9da75c92357"
|
|
5156
5171
|
},
|
|
5157
5172
|
{
|
|
5158
5173
|
"path": "scripts/gds/git-merge-regen.js",
|
|
@@ -5409,6 +5424,11 @@
|
|
|
5409
5424
|
"mode": "0000644",
|
|
5410
5425
|
"sha256": "564996b820fa09ccb95bcef2e5a1b57f22e5b16c4e54b67dfe1a2cd37ff716a4"
|
|
5411
5426
|
},
|
|
5427
|
+
{
|
|
5428
|
+
"path": "scripts/gds/redteam-patrol.js",
|
|
5429
|
+
"mode": "0000644",
|
|
5430
|
+
"sha256": "4e4ce11a67de653550851d9e0b9931253c8b100dd4285f2a39c74ba2f932b23c"
|
|
5431
|
+
},
|
|
5412
5432
|
{
|
|
5413
5433
|
"path": "scripts/gds/regen-instance-docs.js",
|
|
5414
5434
|
"mode": "0000644",
|
|
@@ -5879,6 +5899,11 @@
|
|
|
5879
5899
|
"mode": "0000644",
|
|
5880
5900
|
"sha256": "a932e617ee66dabc49b4087baf6606eeb2074f70ebb3efa0723b1d9c92add290"
|
|
5881
5901
|
},
|
|
5902
|
+
{
|
|
5903
|
+
"path": "src/bongos/logger.js",
|
|
5904
|
+
"mode": "0000644",
|
|
5905
|
+
"sha256": "08a48a47445606c22c1fb5f1e709de5106693eb8b774466186fb5e30352841e8"
|
|
5906
|
+
},
|
|
5882
5907
|
{
|
|
5883
5908
|
"path": "src/bongos/metrics.js",
|
|
5884
5909
|
"mode": "0000644",
|
|
@@ -5887,12 +5912,12 @@
|
|
|
5887
5912
|
{
|
|
5888
5913
|
"path": "src/bongos/middleware/audit.js",
|
|
5889
5914
|
"mode": "0000644",
|
|
5890
|
-
"sha256": "
|
|
5915
|
+
"sha256": "ad2d224b01bba5ce747f8e3ab8704cbbbd67b1bfb6f4d1237bc910e91f7e4287"
|
|
5891
5916
|
},
|
|
5892
5917
|
{
|
|
5893
5918
|
"path": "src/bongos/middleware/error-envelope.js",
|
|
5894
5919
|
"mode": "0000644",
|
|
5895
|
-
"sha256": "
|
|
5920
|
+
"sha256": "920897ba69d929ab5b77bac1605c67d78805ebbbf962d59fec5bdc8b293bd67a"
|
|
5896
5921
|
},
|
|
5897
5922
|
{
|
|
5898
5923
|
"path": "src/bongos/middleware/rate-limit.js",
|
|
@@ -5902,7 +5927,7 @@
|
|
|
5902
5927
|
{
|
|
5903
5928
|
"path": "src/bongos/module-scope-map.js",
|
|
5904
5929
|
"mode": "0000644",
|
|
5905
|
-
"sha256": "
|
|
5930
|
+
"sha256": "c3b5499005ae5150d1e3288437dbfd422e28068a28e3c5783a8c4b3fa2edd09c"
|
|
5906
5931
|
},
|
|
5907
5932
|
{
|
|
5908
5933
|
"path": "src/bongos/module-submissions.js",
|
|
@@ -6037,7 +6062,7 @@
|
|
|
6037
6062
|
{
|
|
6038
6063
|
"path": "src/bongos/serve-internal.js",
|
|
6039
6064
|
"mode": "0000644",
|
|
6040
|
-
"sha256": "
|
|
6065
|
+
"sha256": "2ce782b00c80482ef9b9d3e4a54a634e73c6d956e13e32cc7e3d2ff63e8282cd"
|
|
6041
6066
|
},
|
|
6042
6067
|
{
|
|
6043
6068
|
"path": "src/bongos/stealth-gate.js",
|
|
@@ -6072,7 +6097,7 @@
|
|
|
6072
6097
|
{
|
|
6073
6098
|
"path": "src/module-api.js",
|
|
6074
6099
|
"mode": "0000644",
|
|
6075
|
-
"sha256": "
|
|
6100
|
+
"sha256": "a34bf521314a258ef3c444962bac548fa9aa2e0853dc45531f79586ef52bdeec"
|
|
6076
6101
|
},
|
|
6077
6102
|
{
|
|
6078
6103
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6112,7 +6137,7 @@
|
|
|
6112
6137
|
{
|
|
6113
6138
|
"path": "src/platform-server.js",
|
|
6114
6139
|
"mode": "0000644",
|
|
6115
|
-
"sha256": "
|
|
6140
|
+
"sha256": "704f68cdafb697138b9e012defedeb827dee3ff97191c78bf0e86be0ae5d61cf"
|
|
6116
6141
|
},
|
|
6117
6142
|
{
|
|
6118
6143
|
"path": "src/ui/adapters/claude-design/index.js",
|
|
@@ -6282,7 +6307,7 @@
|
|
|
6282
6307
|
{
|
|
6283
6308
|
"path": "tests/auth_resolve_failure.mjs",
|
|
6284
6309
|
"mode": "0000644",
|
|
6285
|
-
"sha256": "
|
|
6310
|
+
"sha256": "d14529c53ddf42f69c40fd49dc6eb230df4a519e9bc2bfdfab04df277e3dbb9c"
|
|
6286
6311
|
},
|
|
6287
6312
|
{
|
|
6288
6313
|
"path": "tests/auth_session_ttl.mjs",
|
|
@@ -6537,7 +6562,7 @@
|
|
|
6537
6562
|
{
|
|
6538
6563
|
"path": "tests/claude_materialize.mjs",
|
|
6539
6564
|
"mode": "0000644",
|
|
6540
|
-
"sha256": "
|
|
6565
|
+
"sha256": "ad3daf4c6cc3a0f56d8d205077c835397b1d112a0219dabb05a7e8b3a8da9492"
|
|
6541
6566
|
},
|
|
6542
6567
|
{
|
|
6543
6568
|
"path": "tests/clawback_confirmed_backlog.mjs",
|
|
@@ -6604,6 +6629,11 @@
|
|
|
6604
6629
|
"mode": "0000644",
|
|
6605
6630
|
"sha256": "55bd70efd8067e39d6220d14f78c743ce80d8a8ff37988acf00136b5108dc004"
|
|
6606
6631
|
},
|
|
6632
|
+
{
|
|
6633
|
+
"path": "tests/control_manifest.mjs",
|
|
6634
|
+
"mode": "0000644",
|
|
6635
|
+
"sha256": "2465159282594723a49d03ed2ca9dbb10e95ddb7af96d145c45f7d7c2a8b8cee"
|
|
6636
|
+
},
|
|
6607
6637
|
{
|
|
6608
6638
|
"path": "tests/copy_desk_flags_db.mjs",
|
|
6609
6639
|
"mode": "0000644",
|
|
@@ -6889,6 +6919,11 @@
|
|
|
6889
6919
|
"mode": "0000644",
|
|
6890
6920
|
"sha256": "ebb93b7bdd90c0775f71eb949a6e47022570bdb35c901bd4522413fefa750e14"
|
|
6891
6921
|
},
|
|
6922
|
+
{
|
|
6923
|
+
"path": "tests/fitness_ratchets.mjs",
|
|
6924
|
+
"mode": "0000644",
|
|
6925
|
+
"sha256": "781bdee0778645704b5179bf99507e839743158714f799e45d6cc64f8fc30477"
|
|
6926
|
+
},
|
|
6892
6927
|
{
|
|
6893
6928
|
"path": "tests/fitness_two_root.mjs",
|
|
6894
6929
|
"mode": "0000644",
|
|
@@ -7474,6 +7509,11 @@
|
|
|
7474
7509
|
"mode": "0000644",
|
|
7475
7510
|
"sha256": "a01036699653e810c8eb2e4f8bdabe081095e5634ef6ea4f1e4f85b2b5f56523"
|
|
7476
7511
|
},
|
|
7512
|
+
{
|
|
7513
|
+
"path": "tests/logger.mjs",
|
|
7514
|
+
"mode": "0000644",
|
|
7515
|
+
"sha256": "88508517d49ad11a01d2dbc97767c4baa6789753ea42afe76699bc2bab5017d4"
|
|
7516
|
+
},
|
|
7477
7517
|
{
|
|
7478
7518
|
"path": "tests/main_audit.mjs",
|
|
7479
7519
|
"mode": "0000644",
|
|
@@ -7877,7 +7917,7 @@
|
|
|
7877
7917
|
{
|
|
7878
7918
|
"path": "tests/publish_reconciler.mjs",
|
|
7879
7919
|
"mode": "0000644",
|
|
7880
|
-
"sha256": "
|
|
7920
|
+
"sha256": "c61741aebfad2e85e55f0e734b3195d9474f8dd0c7812c3ee1434b05d088c1de"
|
|
7881
7921
|
},
|
|
7882
7922
|
{
|
|
7883
7923
|
"path": "tests/rank_gate.mjs",
|
|
@@ -7914,6 +7954,11 @@
|
|
|
7914
7954
|
"mode": "0000644",
|
|
7915
7955
|
"sha256": "6d82e99c69d23a4dd7a844ffdd7f239ea70c94d9c509e2fc8e1a672cd9f05a28"
|
|
7916
7956
|
},
|
|
7957
|
+
{
|
|
7958
|
+
"path": "tests/redteam_patrol.mjs",
|
|
7959
|
+
"mode": "0000644",
|
|
7960
|
+
"sha256": "bf32ae5c734fc95873433b7c4e251a6b166c167ccc91c4684d061b97681c3357"
|
|
7961
|
+
},
|
|
7917
7962
|
{
|
|
7918
7963
|
"path": "tests/regrade_eligibility.mjs",
|
|
7919
7964
|
"mode": "0000644",
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
# ADR 0022 — Secrets policy
|
|
2
2
|
|
|
3
3
|
**Date:** 2026-05-28
|
|
4
|
-
**Context:** Task [#279](https://example.com/builders#/task/279) (V3.R60), GDS-V3 criterion C8.
|
|
4
|
+
**Context:** Task [#279](https://example.com/builders#/task/279) (V3.R60), GDS-V3 criterion C8. Originally built on a pre-commit hook + CI scan (task [#257](https://example.com/builders#/task/257), V3.R36); those surfaces were lost in the core extraction and rebuilt as the prevent/gate/patrol layers under goal 1000076 (task 1003131). Pairs with [ADR 0020](<redacted>.md) (security model — public stub) and [ADR 0016](<redacted>.md) (trust boundary).
|
|
5
|
+
**Enforcement rebuilt:** 2026-08-19 (task 1003131, goal 1000076) — see the Problem section's box.
|
|
5
6
|
**Status:** Accepted.
|
|
6
7
|
|
|
7
8
|
## Problem
|
|
8
9
|
|
|
9
10
|
A secret committed to a public repo is a credential someone else can use within minutes — even after a force-push to remove it, the value survives in forks, GitHub's commit cache, and any clone made between the commit and the removal. The right model is **defense in depth**: each layer assumes the next one will fail.
|
|
10
11
|
|
|
11
|
-
We
|
|
12
|
+
We have defense in depth (rebuilt under goal 1000076 — see the box below):
|
|
12
13
|
|
|
13
|
-
-
|
|
14
|
-
- `.
|
|
15
|
-
- `.
|
|
14
|
+
- **Prevent (agent-side):** `.claude/hooks/secret-gate.js`, a PreToolUse hook, blocks a secret-shaped Write/Edit/Bash payload before it reaches the tree; `.claude/settings.json` denies reading the env/key/session lanes.
|
|
15
|
+
- **Gate (CI):** `.gitleaks.toml` drives a gitleaks scan of the working tree in the required `unit` workflow on every PR.
|
|
16
|
+
- **Patrol (weekly):** `.claude/scheduled-tasks/redteam-weekly` runs a full-git-**history** gitleaks scan and files any hit as a security report.
|
|
17
|
+
- `.gitignore` lists `.env.local` and the key/PEM shapes.
|
|
16
18
|
- Live credentials resolve from `/etc/example/` on the droplet or `~/.config/otb/env` on builder laptops — neither path is tracked.
|
|
17
19
|
|
|
20
|
+
> **Why this list is now trustworthy — the assurance-of-assurance.** The controls above are not just described here; each has a row in `scripts/gds/control-manifest.js`, a fitness check that reds the merge gate if any control file is deleted or comes unwired (task 1003131). This ADR **used to** name a `.husky/pre-commit` hook, a `.github/workflows/secrets-scan.yml`, and a `docs/security-baseline.md` — **none of which existed in this repo**; they were lost in the core extraction and nothing noticed for releases (the red-team baseline audit's headline finding, [`docs/audits/ai-dev-redteam-baseline.md`](../audits/ai-dev-redteam-baseline.md) §0). A doc describing a control cannot be the assurance; the manifest is.
|
|
21
|
+
|
|
18
22
|
What's missing — and what this ADR adds — is a written **policy** that defines:
|
|
19
23
|
|
|
20
24
|
1. What counts as a secret (so the scan can be tuned).
|
|
@@ -26,7 +30,7 @@ Without this document, every new builder reinvents the answers. Some answers wil
|
|
|
26
30
|
|
|
27
31
|
## Decision
|
|
28
32
|
|
|
29
|
-
This ADR is **the policy**. The
|
|
33
|
+
This ADR is **the policy**. The prevent hook + the CI gitleaks gate + the weekly patrol are the **enforcement** (goal 1000076). `scripts/gds/control-manifest.js` — a fitness check in the `unit` gate — is the **assertion that they still exist and stay wired**. Each layer is independently maintained but consistent with this document; a change that removes an enforcement surface must update the manifest in the same commit, or CI goes red.
|
|
30
34
|
|
|
31
35
|
## What counts as a secret
|
|
32
36
|
|
|
@@ -86,11 +90,11 @@ GDS session tokens rotate automatically — they're 30-day-TTL, refresh-on-use,
|
|
|
86
90
|
|
|
87
91
|
## What to do when the scan finds a hit
|
|
88
92
|
|
|
89
|
-
If `.
|
|
93
|
+
If `.claude/hooks/secret-gate.js` blocks a write, OR the gitleaks step in the `unit` workflow fails on a PR, OR the weekly `redteam-weekly` patrol files a history-secrets report, OR `scripts/gds/control-manifest.js` flags a missing control:
|
|
90
94
|
|
|
91
95
|
1. **Stop**. Do not push, do not merge, do not delete the offending file from the working tree (yet).
|
|
92
96
|
2. **Identify the credential** — what service, what scope, was it ever active in production.
|
|
93
|
-
3. **Rotate the credential first** (steps 1-4 above). Treat the leaked value as compromised even if the scan was a
|
|
97
|
+
3. **Rotate the credential first** (steps 1-4 above). Treat the leaked value as compromised even if the scan was a prevent-hook catch — the same value may have been pasted into a chat or a sibling file.
|
|
94
98
|
4. **Then** rewrite the offending file (remove the credential) and commit. If the credential was already in pushed history, run `git filter-repo --replace-text <pattern>` to scrub it from every commit. This rewrites SHAs; force-push is required; CI scan re-validates afterward.
|
|
95
99
|
5. **Document** — add an entry to [`docs/recipes/ops-gotchas.md`](../recipes/ops-gotchas.md) with the date, what happened, and what changed in the response so the next time it goes faster.
|
|
96
100
|
6. **Forks + clones** — the rewrite doesn't reach existing clones. Anyone with an outdated clone gets a `git fetch` warning about non-fast-forward; they re-clone. Forks that mirror via GitHub's mirror feature need a manual `git push --force` from the upstream-aware operator.
|
|
@@ -109,16 +113,16 @@ The enforcement above keeps secrets out of the **repo**. A second egress exists:
|
|
|
109
113
|
|
|
110
114
|
This scrubber is **transcript-grade** and **recall-first** (task 1003, which also completed the open token-redaction of task 723): a missed secret is a credential compromise, while over-redacting a non-secret in a BFG digest is merely cosmetic, so when in doubt it redacts. Five layers: (1) literal dynamic values — this run's GDS session token + every secret-shaped **env value** (`collectEnvSecrets()`, e.g. the `MAC_*`/`APPLE_*` signing creds, a baked GDS/DO token); (2) multi-line PEM private-key blocks; (3) the in-scope known shapes from this ADR (provider key prefixes, Stripe `sk_`/`rk_`/`whsec_`, our `gds_`/`pms_` tokens, `Authorization`/`Bearer` headers, JWTs, credentialed DSNs, Apple app-specific passwords); (4) recognised-key `key=value` / JSON-field assignments; (5) a **high-entropy catch-all** (`looksHighEntropySecret()`) over base64url/hex runs ≥20 chars — the net for an *unknown* secret shape. The accepted cost of layer 5 is that high-entropy non-secrets (git SHAs, UUIDs, a 40-hex value shape-identical to a GitHub OAuth client secret) are redacted too; the structured BFG metrics that matter (token/turn counts, tool names, error flags) are separate fields and untouched. The planted-secret corpus in `tests/scrubber_corpus.mjs` is the regression gate — every planted secret must be fully redacted, every benign line must survive — and **gates the full-transcript capture task**: capture must not ship until this passes.
|
|
111
115
|
|
|
112
|
-
This scrubber is now a load-bearing enforcement surface alongside the
|
|
116
|
+
This scrubber is now a load-bearing enforcement surface alongside the prevent hook + CI gitleaks gate; a change to the in-scope secret shapes above should update it (and its corpus) too, or the policy and the enforcement will drift.
|
|
113
117
|
|
|
114
118
|
## Alternatives considered
|
|
115
119
|
|
|
116
120
|
- **Use a cloud secrets manager.** Rejected: cost (~$0.40/secret-month at AWS), latency (an extra network hop on cold start), single point of failure (the manager being down blocks deploys). At ~10 secrets and one operator, the filesystem-permissions model is cheaper and not meaningfully less secure.
|
|
117
121
|
- **Encrypt secrets in the repo (SOPS, git-crypt).** Rejected: shifts the problem from "don't commit the secret" to "don't lose the decryption key." The decryption key has to live somewhere; if it lives in the same repo, we've solved nothing; if it lives outside, we've reinvented the current `/etc/example/` model with extra steps.
|
|
118
|
-
- **Skip the
|
|
122
|
+
- **Skip the agent-side prevent hook; rely on CI alone.** Rejected: CI catches the leak after the secret has already been pushed to GitHub (where it's now in their cache, even if the PR closes). The PreToolUse prevent hook is the fast feedback that stops the leak before it ever reaches a commit.
|
|
119
123
|
|
|
120
124
|
## Consequences
|
|
121
125
|
|
|
122
|
-
- New builders go through a one-line orientation: "creds live in /etc/example on the droplet, in ~/.config/otb/env or .env.local on your laptop, nowhere else." If they ask "but what if I want to commit one?" — the hook blocks
|
|
123
|
-
- The
|
|
126
|
+
- New builders go through a one-line orientation: "creds live in /etc/example on the droplet, in ~/.config/otb/env or .env.local on your laptop, nowhere else." If they ask "but what if I want to commit one?" — the prevent hook blocks the write; if they push past it, the CI gitleaks gate blocks the merge; and anything already in history is caught by the weekly patrol.
|
|
127
|
+
- The prevent hook + CI gitleaks gate + weekly patrol are now load-bearing, and `scripts/gds/control-manifest.js` fails CI if any of them is removed or unwired. A change to any of them needs to update this ADR (and the manifest row) too, or the policy and the enforcement will drift — the exact drift this ADR's own rewrite corrected.
|
|
124
128
|
- Rotation is the operator's responsibility (Lars). The team is informed via the chat broadcast; there is no automatic distribution of new secrets to builder laptops. This is a deliberate accept-the-friction choice — automatic distribution requires a secrets manager.
|
package/docs/file-map.md
CHANGED
|
@@ -388,6 +388,7 @@ tests/
|
|
|
388
388
|
<!-- BEGIN GENERATED FILE-MAP .claude/scheduled-tasks/ (scripts/gds/gen-file-map.js — do not hand-edit; notes live in docs/file-map.notes.json) -->
|
|
389
389
|
```
|
|
390
390
|
.claude/scheduled-tasks/
|
|
391
|
+
└── redteam-weekly/SKILL.md ← weekly deterministic red-team patrol (task 1003128): full-history gitleaks scan + dependency drift review (audit delta, cooldown, young pkgs, stale overrides) + action-pin check → files security reports (advisory, ADR 0162)
|
|
391
392
|
```
|
|
392
393
|
<!-- END GENERATED FILE-MAP .claude/scheduled-tasks/ -->
|
|
393
394
|
|
package/docs/handoff-template.md
CHANGED
|
@@ -89,4 +89,4 @@ This template forces:
|
|
|
89
89
|
|
|
90
90
|
## Old entries
|
|
91
91
|
|
|
92
|
-
There is **no per-session compaction chore** anymore. CLAUDE.md §13 shows only a generated, bounded recent-sessions snippet (older entries roll off it automatically) and the full history is the generated
|
|
92
|
+
There is **no per-session compaction chore** anymore. CLAUDE.md §13 shows only a generated, bounded recent-sessions snippet (older entries roll off it automatically) and the full history is the generated `docs/session-log-index.md` — neither is hand-maintained. Trimming very old prose from `docs/session-logs/` into `docs/session-log-archive.md` is optional on-disk housekeeping; it no longer affects the loaded-every-session context cost.
|
package/docs/recipes/README.md
CHANGED
|
@@ -17,7 +17,7 @@ A "recipe" is not a limitation (something the project cannot do). It is a multi-
|
|
|
17
17
|
|
|
18
18
|
- Architectural decisions → [`docs/adr/`](../../docs/adr/)
|
|
19
19
|
- Things the project cannot do → [`limitations/`](../../limitations/)
|
|
20
|
-
- Things stuck on the prompter's input →
|
|
20
|
+
- Things stuck on the prompter's input → the blockers queue (`POST /api/gds/blockers`, reviewed via `/blocker-review`)
|
|
21
21
|
|
|
22
22
|
## Read order while working
|
|
23
23
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# The weekly red-team patrol
|
|
2
|
+
|
|
3
|
+
**What it is.** The patrol layer of goal 1000076 (task 1003128): deterministic
|
|
4
|
+
scanners that run weekly and file findings into the security-reports
|
|
5
|
+
adjudication queue. It complements the two always-on layers — the agent-side
|
|
6
|
+
prevent hooks (task 1003125) and the CI merge gates (tasks 1003126/1003127/1003129)
|
|
7
|
+
— by covering what those can't: **git history**, **the network** (registry
|
|
8
|
+
metadata), and **slow sweeps**.
|
|
9
|
+
|
|
10
|
+
## Running it
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
node scripts/gds/redteam-patrol.js # dry run — print, file nothing
|
|
14
|
+
node scripts/gds/redteam-patrol.js --file # the weekly cron form — files reports
|
|
15
|
+
node scripts/gds/redteam-patrol.js --canary # self-test detection only
|
|
16
|
+
node scripts/gds/redteam-patrol.js --canary --file # prove the full pipe (files + dispute-closes)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The scheduled form lives at [`.claude/scheduled-tasks/redteam-weekly/`](../../.claude/scheduled-tasks/redteam-weekly/SKILL.md)
|
|
20
|
+
(`mode: deterministic`, one step, sink `gds`). Install on a machine per the
|
|
21
|
+
file-map convention: symlink the routine dir under `~/.claude/scheduled-tasks/`.
|
|
22
|
+
Weekly is the cadence — the checks watch drift measured in days (release-age,
|
|
23
|
+
new advisories), not minutes.
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
- **gitleaks** on PATH (`brew install gitleaks`) or `GITLEAKS_BIN=<path>`. The
|
|
28
|
+
history scan degrades LOUDLY (a printed warning, no silent skip) when absent;
|
|
29
|
+
the dependency + pin checks still run.
|
|
30
|
+
- A GDS session (the normal builder token) for `--file`; dry runs need nothing.
|
|
31
|
+
- Network access to the npm registry for the dependency checks — registry
|
|
32
|
+
failures print notes and skip that item (advisory layer: fail-open, loud).
|
|
33
|
+
|
|
34
|
+
## What a finding means
|
|
35
|
+
|
|
36
|
+
Reports land at `target: redteam-patrol/<class>` for Archon adjudication in the
|
|
37
|
+
red-team dashboard — confirm / dispute / link-fix, exactly like a
|
|
38
|
+
`/builder-redteam` filing (they share the pipeline and the bounty table; patrol
|
|
39
|
+
reports are machine-filed, so confirming one pays nobody). The fix is always an
|
|
40
|
+
ordinary task; the patrol never gates and never self-executes (ADR 0162).
|
|
41
|
+
|
|
42
|
+
## Design boundaries
|
|
43
|
+
|
|
44
|
+
Deterministic only — AI review was deliberately deferred (owner decision
|
|
45
|
+
2026-08-18, recorded on the goal). One report per finding class per run keeps
|
|
46
|
+
the queue at 0–3 rows weekly. Severity mapping: npm's `moderate` files as
|
|
47
|
+
`medium` (the bounty table's vocabulary). Caps: 10 itemized findings per report
|
|
48
|
+
body, the rest counted.
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.258",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.258",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.258",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/gds/control-manifest.js — the assurance-of-assurance check (task
|
|
3
|
+
// 1003131, goal 1000076; docs/audits/ai-dev-redteam-baseline.md §0).
|
|
4
|
+
//
|
|
5
|
+
// The audit's HEADLINE finding: ADR 0022 named three enforcement controls
|
|
6
|
+
// (a pre-commit secret hook, a secrets-scan CI workflow, a security-baseline
|
|
7
|
+
// doc) that DID NOT EXIST in this repo — they never survived the core
|
|
8
|
+
// extraction, and nothing noticed for releases. A control that can silently
|
|
9
|
+
// stop existing is not a control.
|
|
10
|
+
//
|
|
11
|
+
// This module is the manifest of controls goal 1000076 shipped, plus the
|
|
12
|
+
// predicate each must satisfy. It runs as a fitness check (fitness.js): delete
|
|
13
|
+
// or unwire any control and the merge gate goes red. The manifest is the ONE
|
|
14
|
+
// list — a control added later without a manifest row, or a manifest row whose
|
|
15
|
+
// file is gone, both fail.
|
|
16
|
+
//
|
|
17
|
+
// Predicates are deliberately shallow (existence + one wiring substring), not
|
|
18
|
+
// deep behavior tests — the control's own unit test proves behavior; THIS proves
|
|
19
|
+
// the control is still present and connected. Cheap, hermetic (fs only), and
|
|
20
|
+
// exactly the check whose absence the audit flagged.
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
28
|
+
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
29
|
+
const exists = (rel) => fs.existsSync(path.join(ROOT, rel));
|
|
30
|
+
|
|
31
|
+
// Each control: a file that must exist, and optionally a `wiredIn` (file, needle)
|
|
32
|
+
// proving it is CONNECTED, not just present on disk. `label` names it in failures.
|
|
33
|
+
const CONTROLS = [
|
|
34
|
+
{
|
|
35
|
+
label: 'prevent: secret-gate PreToolUse hook',
|
|
36
|
+
file: '.claude/hooks/secret-gate.js',
|
|
37
|
+
wiredIn: { file: '.claude/settings.json', needle: 'secret-gate.js' },
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
label: 'prevent: settings deny-rules for env/key lanes',
|
|
41
|
+
file: '.claude/settings.json',
|
|
42
|
+
wiredIn: { file: '.claude/settings.json', needle: '"deny"' },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: 'gate: gitleaks secret scan in the unit workflow',
|
|
46
|
+
file: '.gitleaks.toml',
|
|
47
|
+
wiredIn: { file: '.github/workflows/unit.yml', needle: 'gitleaks' },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: 'gate: dependency audit gate + baseline',
|
|
51
|
+
file: 'scripts/gds/audit-gate.js',
|
|
52
|
+
wiredIn: { file: '.github/workflows/unit.yml', needle: 'audit-gate.js' },
|
|
53
|
+
alsoExists: ['config/audit-baseline.json'],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: 'gate: install-lifecycle scripts disabled',
|
|
57
|
+
file: '.npmrc',
|
|
58
|
+
wiredIn: { file: '.npmrc', needle: 'ignore-scripts=true' },
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: 'gate: quality ratchets registered in fitness',
|
|
62
|
+
file: 'scripts/gds/fitness-ratchets.js',
|
|
63
|
+
wiredIn: { file: 'scripts/gds/fitness.js', needle: 'fitness-ratchets' },
|
|
64
|
+
alsoExists: ['config/fitness-baselines.json'],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: 'gate: knip dead-code ratchet step in the unit workflow',
|
|
68
|
+
file: 'scripts/gds/fitness-ratchets.js',
|
|
69
|
+
wiredIn: { file: '.github/workflows/unit.yml', needle: '--knip' },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
label: 'patrol: weekly red-team routine wired to its scanner',
|
|
73
|
+
file: '.claude/scheduled-tasks/redteam-weekly/SKILL.md',
|
|
74
|
+
wiredIn: { file: '.claude/scheduled-tasks/redteam-weekly/SKILL.md', needle: 'redteam-patrol.js' },
|
|
75
|
+
alsoExists: ['scripts/gds/redteam-patrol.js'],
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// Pure evaluation — exported for the test. deps.exists/deps.read override fs.
|
|
80
|
+
function evaluate(controls = CONTROLS, deps = {}) {
|
|
81
|
+
const ex = deps.exists || exists;
|
|
82
|
+
const rd = deps.read || read;
|
|
83
|
+
const missing = [];
|
|
84
|
+
for (const c of controls) {
|
|
85
|
+
if (!ex(c.file)) { missing.push(`${c.label}: control file missing (${c.file})`); continue; }
|
|
86
|
+
for (const extra of c.alsoExists || []) {
|
|
87
|
+
if (!ex(extra)) missing.push(`${c.label}: companion file missing (${extra})`);
|
|
88
|
+
}
|
|
89
|
+
if (c.wiredIn) {
|
|
90
|
+
let hay = '';
|
|
91
|
+
try { hay = ex(c.wiredIn.file) ? rd(c.wiredIn.file) : ''; } catch { hay = ''; }
|
|
92
|
+
if (!hay.includes(c.wiredIn.needle)) {
|
|
93
|
+
missing.push(`${c.label}: not wired in — "${c.wiredIn.needle}" absent from ${c.wiredIn.file}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return missing;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The fitness.js check.
|
|
101
|
+
function checkControlsExist() {
|
|
102
|
+
const violations = evaluate();
|
|
103
|
+
return {
|
|
104
|
+
name: 'controls assert their own existence — goal 1000076 enforcement manifest (task 1003131)',
|
|
105
|
+
hardFail: violations.length > 0,
|
|
106
|
+
violations,
|
|
107
|
+
warnings: [],
|
|
108
|
+
note: violations.length
|
|
109
|
+
? 'a shipped security/quality control disappeared or came unwired — restore it or update control-manifest.js in the same change'
|
|
110
|
+
: `all ${CONTROLS.length} goal-1000076 controls present and wired (the assurance-of-assurance the ADR 0022 audit found missing)`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function main() {
|
|
115
|
+
const r = checkControlsExist();
|
|
116
|
+
for (const v of r.violations) console.error(`✗ ${v}`);
|
|
117
|
+
console.log(`${r.hardFail ? 'FAIL' : 'PASS'} ${r.note}`);
|
|
118
|
+
process.exit(r.hardFail ? 1 : 0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (require.main === module) main();
|
|
122
|
+
|
|
123
|
+
module.exports = { checkControlsExist, evaluate, CONTROLS };
|