@bongos/core 1.19.254 → 1.19.255
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 +17 -12
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +3 -1
- package/tests/claude_materialize.mjs +14 -3
- package/tests/hook_user_agents.mjs +9 -1
- package/tests/secret_gate.mjs +124 -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.255",
|
|
6
|
+
"core_contract": "1.19.255",
|
|
7
|
+
"source_commit": "85c97e23dbe8dee636e134ee8f5eac4983051c1f",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-19T02:
|
|
9
|
+
"built_at": "2026-08-19T02:17:10.653Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 315,
|
|
13
13
|
"agent_docs_stubbed": 22,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1348,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1685,
|
|
20
|
+
"tree_sha256": "d3086bc5b3687de018836883dc4e7a1af8239ce05719be7c6b2af45a4876619d",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -4437,12 +4437,12 @@
|
|
|
4437
4437
|
{
|
|
4438
4438
|
"path": "package-lock.json",
|
|
4439
4439
|
"mode": "0000644",
|
|
4440
|
-
"sha256": "
|
|
4440
|
+
"sha256": "edf60a53538729a978886acc667ba4b8a81f654924873b138520f54e52ad6bf6"
|
|
4441
4441
|
},
|
|
4442
4442
|
{
|
|
4443
4443
|
"path": "package.json",
|
|
4444
4444
|
"mode": "0000644",
|
|
4445
|
-
"sha256": "
|
|
4445
|
+
"sha256": "aad3432c2f8c51687d6b16e5caf66fd2c7e01901cb9756ccca2fd700fe117021"
|
|
4446
4446
|
},
|
|
4447
4447
|
{
|
|
4448
4448
|
"path": "public-docs/index.html",
|
|
@@ -6062,7 +6062,7 @@
|
|
|
6062
6062
|
{
|
|
6063
6063
|
"path": "src/module-api.js",
|
|
6064
6064
|
"mode": "0000644",
|
|
6065
|
-
"sha256": "
|
|
6065
|
+
"sha256": "b34d89b988d753c21364242217269cce8fb4db8719598bd6f2c458507ffad512"
|
|
6066
6066
|
},
|
|
6067
6067
|
{
|
|
6068
6068
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6522,7 +6522,7 @@
|
|
|
6522
6522
|
{
|
|
6523
6523
|
"path": "tests/claude_materialize.mjs",
|
|
6524
6524
|
"mode": "0000644",
|
|
6525
|
-
"sha256": "
|
|
6525
|
+
"sha256": "6d31d8597e3b1e9e7efde56f9a3839715cfa87378e7555f0fa3b26b09346a569"
|
|
6526
6526
|
},
|
|
6527
6527
|
{
|
|
6528
6528
|
"path": "tests/clawback_confirmed_backlog.mjs",
|
|
@@ -7272,7 +7272,7 @@
|
|
|
7272
7272
|
{
|
|
7273
7273
|
"path": "tests/hook_user_agents.mjs",
|
|
7274
7274
|
"mode": "0000644",
|
|
7275
|
-
"sha256": "
|
|
7275
|
+
"sha256": "ed1e14050ba219a52736c2cd70f4d4333a345fb0018211c85293a995c7f0aac7"
|
|
7276
7276
|
},
|
|
7277
7277
|
{
|
|
7278
7278
|
"path": "tests/host_topology_skips.mjs",
|
|
@@ -8014,6 +8014,11 @@
|
|
|
8014
8014
|
"mode": "0000644",
|
|
8015
8015
|
"sha256": "886bf7096cbdb3b758010eb0ee24e15bd74033a7bff0064b742585440cdeee79"
|
|
8016
8016
|
},
|
|
8017
|
+
{
|
|
8018
|
+
"path": "tests/secret_gate.mjs",
|
|
8019
|
+
"mode": "0000644",
|
|
8020
|
+
"sha256": "20de62b7d869763c703317dae4bc3896cbf4308d1211c8fcbffbfbd32465b1b5"
|
|
8021
|
+
},
|
|
8017
8022
|
{
|
|
8018
8023
|
"path": "tests/security_docs.mjs",
|
|
8019
8024
|
"mode": "0000644",
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.255",
|
|
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.255",
|
|
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.255",
|
|
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",
|
package/src/module-api.js
CHANGED
|
@@ -935,8 +935,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
935
935
|
// landed since 1.19.252 with no explicit bump. run 32205833181. (task 1002620)
|
|
936
936
|
// 1.19.254 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
937
937
|
// landed since 1.19.253 with no explicit bump. run 32207504942. (task 1002620)
|
|
938
|
+
// 1.19.255 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
939
|
+
// landed since 1.19.254 with no explicit bump. run 32207967928. (task 1002620)
|
|
938
940
|
// ---------------------------------------------------------------------------
|
|
939
|
-
const CORE_VERSION = '1.19.
|
|
941
|
+
const CORE_VERSION = '1.19.255'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
940
942
|
|
|
941
943
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
942
944
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -214,7 +214,15 @@ t('materializeClaude: copied hooks carry NO founder origin/config-dir; a disable
|
|
|
214
214
|
writeFileSync(join(dir, 'config', 'branding.json'), JSON.stringify({ domains: { publicOrigin: 'https://demo.cloudbongos.com', buildersOrigin: 'https://demo.cloudbongos.com', statusOrigin: 'https://demo.cloudbongos.com', oauthOrigin: 'https://demo.cloudbongos.com' }, configDir: 'cloudbongos', envPrefix: 'CLOUDBONGOS' }));
|
|
215
215
|
writeFileSync(join(dir, 'config', 'modules.json'), JSON.stringify({ modules: { 'art-pipeline': false } }));
|
|
216
216
|
const res = m.materializeClaude({ coreRoot: ROOT, instanceDir: dir, dryRun: false });
|
|
217
|
-
|
|
217
|
+
// Founder core → demo: a branding delta exists and rules are built. The NEUTRAL
|
|
218
|
+
// core now carries its own dev-loop hooks (task 1003125), so this body runs there
|
|
219
|
+
// too — where no founder delta exists by design and rules are legitimately zero.
|
|
220
|
+
// The founder-specific assertions below self-scope on that signal; the offender
|
|
221
|
+
// walk and the core-skill materialization checks run in both environments.
|
|
222
|
+
const founderDelta = res.rebrandRules > 0;
|
|
223
|
+
if (!founderDelta) {
|
|
224
|
+
assert.equal(res.rebrandRules, 0, 'neutral core: no founder→instance branding delta');
|
|
225
|
+
}
|
|
218
226
|
|
|
219
227
|
// (a) no copied hook may still name the founder origin or config dir.
|
|
220
228
|
const offenders = [];
|
|
@@ -226,8 +234,11 @@ t('materializeClaude: copied hooks carry NO founder origin/config-dir; a disable
|
|
|
226
234
|
assert.deepEqual(offenders, [], `founder origin/config-dir survived in: ${offenders.join(', ')}`);
|
|
227
235
|
|
|
228
236
|
// (b) art-pipeline is off → its otb-* skills are excluded; a core skill remains.
|
|
229
|
-
|
|
230
|
-
|
|
237
|
+
// (The exclusion map, like the rebrand rules, resolves on a founder core.)
|
|
238
|
+
if (founderDelta) {
|
|
239
|
+
assert.ok(res.excludedSkills.includes('otb-tile-generate'), `art-pipeline skill excluded (got ${res.excludedSkills.join(',')})`);
|
|
240
|
+
assert.ok(!existsSync(join(dir, '.claude', 'skills', 'otb-tile-generate')), 'excluded skill not written');
|
|
241
|
+
}
|
|
231
242
|
assert.ok(existsSync(join(dir, '.claude', 'skills', 'builder-ship')), 'a core skill is still materialized');
|
|
232
243
|
rmSync(dir, { recursive: true, force: true });
|
|
233
244
|
});
|
|
@@ -61,7 +61,15 @@ t('every hook User-Agent uses the neutral cloud-bongos- prefix (no founder-brand
|
|
|
61
61
|
if (!ua.startsWith(NEUTRAL_PREFIX)) offenders.push(`${f}: '${ua}'`);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
// Drift canary, scoped: the neutral core may carry hooks that make no HTTP
|
|
65
|
+
// calls at all (e.g. .claude/hooks/secret-gate.js, task 1003125), so demand a
|
|
66
|
+
// regex match only when some hook raw-contains the header name — raw substring
|
|
67
|
+
// present with zero regex matches is the drift this canary exists to catch.
|
|
68
|
+
const rawMentions = hookFiles.some((f) =>
|
|
69
|
+
fs.readFileSync(path.join(HOOKS_DIR, f), 'utf8').includes('User-Agent'));
|
|
70
|
+
if (rawMentions) {
|
|
71
|
+
assert.ok(uaCount > 0, 'hooks mention User-Agent but the regex matched none (regex drift?)');
|
|
72
|
+
}
|
|
65
73
|
assert.equal(
|
|
66
74
|
offenders.length, 0,
|
|
67
75
|
`hook User-Agent(s) not neutral (must start with "${NEUTRAL_PREFIX}"):\n ${offenders.join('\n ')}`
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// tests/secret_gate.mjs — the PreToolUse prevent layer (task 1003125, goal 1000076).
|
|
2
|
+
// The gate's contract: block high-confidence secret shapes in Write/Edit/Bash
|
|
3
|
+
// payloads, allow the files whose job is to hold those shapes, and fail OPEN on
|
|
4
|
+
// anything it does not understand (it is the cooperative layer — ADR 0043 shape;
|
|
5
|
+
// the CI scan and the weekly patrol are the nets behind it).
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { test } from 'node:test';
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
14
|
+
const gate = require(path.join(ROOT, '.claude', 'hooks', 'secret-gate.js'));
|
|
15
|
+
|
|
16
|
+
const ENV_OFF = {}; // no kill-switch
|
|
17
|
+
|
|
18
|
+
// Realistic-shaped fakes (NOT real credentials — lengths and prefixes only).
|
|
19
|
+
const FAKE = {
|
|
20
|
+
anthropic: 'sk-ant-api03-Zx9Yw8Vu7Ts6Rq5Po4Nm3Lk2Jh1Gf0De',
|
|
21
|
+
github: 'ghp_Ab1Cd2Ef3Gh4Ij5Kl6Mn7Op8Qr9St0Uv1Wx2Y',
|
|
22
|
+
aws: 'AKIAIOSFODNN7EXAMPLE',
|
|
23
|
+
slack: 'xoxb-1234567890-abcdefghij',
|
|
24
|
+
stripe: 'sk_live_Ab1Cd2Ef3Gh4Ij5Kl6M',
|
|
25
|
+
pem: '-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaA\n-----END OPENSSH PRIVATE KEY-----',
|
|
26
|
+
dburl: 'postgres://gds:hunter2secret@db.internal:5432/app',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
test('blocks a Write whose content carries a provider-prefixed token', () => {
|
|
30
|
+
for (const [name, value] of Object.entries(FAKE)) {
|
|
31
|
+
const v = gate.decide(
|
|
32
|
+
{ tool_name: 'Write', tool_input: { file_path: 'src/bongos/newfile.js', content: `const k = "${value}";` } },
|
|
33
|
+
ENV_OFF,
|
|
34
|
+
);
|
|
35
|
+
assert.equal(v.block, true, `expected block for ${name}`);
|
|
36
|
+
assert.match(v.reason, /secret-gate: blocked/);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('blocks an Edit new_string and a MultiEdit edit the same way', () => {
|
|
41
|
+
assert.equal(
|
|
42
|
+
gate.decide({ tool_name: 'Edit', tool_input: { file_path: 'server.js', new_string: FAKE.github } }, ENV_OFF).block,
|
|
43
|
+
true,
|
|
44
|
+
);
|
|
45
|
+
assert.equal(
|
|
46
|
+
gate.decide(
|
|
47
|
+
{ tool_name: 'MultiEdit', tool_input: { file_path: 'server.js', edits: [{ new_string: 'clean' }, { new_string: FAKE.aws }] } },
|
|
48
|
+
ENV_OFF,
|
|
49
|
+
).block,
|
|
50
|
+
true,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('blocks a Bash command smuggling a literal token; allows ordinary commands', () => {
|
|
55
|
+
assert.equal(
|
|
56
|
+
gate.decide({ tool_name: 'Bash', tool_input: { command: `echo "${FAKE.anthropic}" >> notes.txt` } }, ENV_OFF).block,
|
|
57
|
+
true,
|
|
58
|
+
);
|
|
59
|
+
for (const cmd of [
|
|
60
|
+
'git commit -m "feat: rotate the key handling (ADR 0022)"',
|
|
61
|
+
'export GH_TOKEN=$(gh auth token -u somebuilder) && git fetch origin',
|
|
62
|
+
'npm test',
|
|
63
|
+
]) {
|
|
64
|
+
assert.equal(gate.decide({ tool_name: 'Bash', tool_input: { command: cmd } }, ENV_OFF).block, false, `false positive on: ${cmd}`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('clean writes pass; short/placeholder shapes below the length floors pass', () => {
|
|
69
|
+
for (const content of [
|
|
70
|
+
'const label = "use your ghp_ token here";', // prefix alone, no 30-char body
|
|
71
|
+
'sk-ant- keys resolve from the env lane',
|
|
72
|
+
'AKIA is the AWS key id prefix',
|
|
73
|
+
'const url = "postgres://localhost:5432/app";', // no embedded password
|
|
74
|
+
]) {
|
|
75
|
+
assert.equal(
|
|
76
|
+
gate.decide({ tool_name: 'Write', tool_input: { file_path: 'docs/notes.md', content } }, ENV_OFF).block,
|
|
77
|
+
false,
|
|
78
|
+
`false positive on: ${content}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('allowlisted paths may hold secret shapes (fixtures, detectors, ADRs, the gate itself)', () => {
|
|
84
|
+
for (const fp of [
|
|
85
|
+
'tests/bfg.mjs',
|
|
86
|
+
`${ROOT}/tests/bfg.mjs`, // absolute form matches too (segment-anchored)
|
|
87
|
+
'docs/adr/0022-secrets-policy.md',
|
|
88
|
+
'modules/security/artifact-scan/detectors/credentials.js',
|
|
89
|
+
'.claude/hooks/secret-gate.js',
|
|
90
|
+
'.gitleaks.toml',
|
|
91
|
+
]) {
|
|
92
|
+
const v = gate.decide({ tool_name: 'Write', tool_input: { file_path: fp, content: FAKE.pem } }, ENV_OFF);
|
|
93
|
+
assert.equal(v.block, false, `allowlist miss for ${fp}`);
|
|
94
|
+
}
|
|
95
|
+
// …but the allowlist is by SEGMENT, not substring: a lookalike dir stays blocked.
|
|
96
|
+
assert.equal(
|
|
97
|
+
gate.decide({ tool_name: 'Write', tool_input: { file_path: 'src/latests/x.js', content: FAKE.pem } }, ENV_OFF).block,
|
|
98
|
+
true,
|
|
99
|
+
'lookalike path must not satisfy the tests/ allowlist',
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('Bash never gets the path allowlist (commands have no file_path)', () => {
|
|
104
|
+
const v = gate.decide(
|
|
105
|
+
{ tool_name: 'Bash', tool_input: { file_path: 'tests/bfg.mjs', command: `echo ${FAKE.github}` } },
|
|
106
|
+
ENV_OFF,
|
|
107
|
+
);
|
|
108
|
+
assert.equal(v.block, true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('kill-switch disables the gate; malformed payloads fail open', () => {
|
|
112
|
+
assert.equal(
|
|
113
|
+
gate.decide({ tool_name: 'Write', tool_input: { file_path: 'x.js', content: FAKE.github } }, { BONGOS_SECRET_GATE_OFF: '1' }).block,
|
|
114
|
+
false,
|
|
115
|
+
);
|
|
116
|
+
assert.equal(gate.decide(null, ENV_OFF).block, false);
|
|
117
|
+
assert.equal(gate.decide({}, ENV_OFF).block, false);
|
|
118
|
+
assert.equal(gate.decide({ tool_name: 'Write' }, ENV_OFF).block, false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('unknown writing tools are scanned via the stringified payload, not skipped', () => {
|
|
122
|
+
const v = gate.decide({ tool_name: 'FutureWriteTool', tool_input: { blob: FAKE.stripe } }, ENV_OFF);
|
|
123
|
+
assert.equal(v.block, true);
|
|
124
|
+
});
|