@bongos/core 1.19.754 → 1.19.755
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/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/serve-internal.js +14 -0
- package/src/module-api.js +1 -1
- package/tests/module_asset_subdomain.mjs +155 -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.755",
|
|
6
|
+
"core_contract": "1.19.755",
|
|
7
|
+
"source_commit": "1cc8ad5ec0be2921b0e5cf12464689c86b5c36a6",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-16T02:11:51.276Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 483,
|
|
13
13
|
"agent_docs_stubbed": 25,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2112,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2620,
|
|
20
|
+
"tree_sha256": "4960c3809b0d1bd4756c52793ef2167cfc1b70844cd2250f3e35723cd275bc50",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2512,7 +2512,7 @@
|
|
|
2512
2512
|
{
|
|
2513
2513
|
"path": "docs/module-api-changelog.md",
|
|
2514
2514
|
"mode": "0000644",
|
|
2515
|
-
"sha256": "
|
|
2515
|
+
"sha256": "744adc56bff99972f3104823c66db347b9aaef9aee42d06f12bff8da8e559a7d"
|
|
2516
2516
|
},
|
|
2517
2517
|
{
|
|
2518
2518
|
"path": "docs/modules-contract.md",
|
|
@@ -7567,12 +7567,12 @@
|
|
|
7567
7567
|
{
|
|
7568
7568
|
"path": "package-lock.json",
|
|
7569
7569
|
"mode": "0000644",
|
|
7570
|
-
"sha256": "
|
|
7570
|
+
"sha256": "d2721993cfbc4a656aab1c93434a55dd7a440a3bb63048893ed6b43c2b1bbeec"
|
|
7571
7571
|
},
|
|
7572
7572
|
{
|
|
7573
7573
|
"path": "package.json",
|
|
7574
7574
|
"mode": "0000644",
|
|
7575
|
-
"sha256": "
|
|
7575
|
+
"sha256": "23815bafaed5c20104d344967253d871d91b0df5524e5fba825119a316a0784a"
|
|
7576
7576
|
},
|
|
7577
7577
|
{
|
|
7578
7578
|
"path": "public-docs/index.html",
|
|
@@ -9342,7 +9342,7 @@
|
|
|
9342
9342
|
{
|
|
9343
9343
|
"path": "src/bongos/serve-internal.js",
|
|
9344
9344
|
"mode": "0000644",
|
|
9345
|
-
"sha256": "
|
|
9345
|
+
"sha256": "9edf07966de8d137ba6d987557bb4a9fefeedd19e640f10302dbfd340e0343f9"
|
|
9346
9346
|
},
|
|
9347
9347
|
{
|
|
9348
9348
|
"path": "src/bongos/uptime-poller.js",
|
|
@@ -9372,7 +9372,7 @@
|
|
|
9372
9372
|
{
|
|
9373
9373
|
"path": "src/module-api.js",
|
|
9374
9374
|
"mode": "0000644",
|
|
9375
|
-
"sha256": "
|
|
9375
|
+
"sha256": "690fb3d76f0d06fcc41490f58b4a273c42ecfd65c8e9a2acb7cff0e85885f5a4"
|
|
9376
9376
|
},
|
|
9377
9377
|
{
|
|
9378
9378
|
"path": "src/module-loader/catalog.js",
|
|
@@ -11619,6 +11619,11 @@
|
|
|
11619
11619
|
"mode": "0000644",
|
|
11620
11620
|
"sha256": "40c396fda3a618bf9fa2d2ae3d8d30b50e62c4210199d70b5e29cb82a046d037"
|
|
11621
11621
|
},
|
|
11622
|
+
{
|
|
11623
|
+
"path": "tests/module_asset_subdomain.mjs",
|
|
11624
|
+
"mode": "0000644",
|
|
11625
|
+
"sha256": "420e17095b054f87be386913077966d456ae687b030b476e1bc33b15ae35ed9f"
|
|
11626
|
+
},
|
|
11622
11627
|
{
|
|
11623
11628
|
"path": "tests/module_catalog.mjs",
|
|
11624
11629
|
"mode": "0000644",
|
|
@@ -1982,5 +1982,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1982
1982
|
landed since 1.19.752 with no explicit bump. run 35036151976. (task 1002620)
|
|
1983
1983
|
1.19.754 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1984
1984
|
landed since 1.19.753 with no explicit bump. run 35039836268. (task 1002620)
|
|
1985
|
+
1.19.755 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1986
|
+
landed since 1.19.754 with no explicit bump. run 35047010813. (task 1002620)
|
|
1985
1987
|
---------------------------------------------------------------------------
|
|
1986
1988
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.755",
|
|
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.755",
|
|
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.755",
|
|
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",
|
|
@@ -826,6 +826,20 @@ function mountInternalSurfaces(app) {
|
|
|
826
826
|
app.use((req, res, next) => {
|
|
827
827
|
if (!isBuildersHost(req)) return next();
|
|
828
828
|
if (req.path.startsWith('/api/') || req.path === '/healthz') return next();
|
|
829
|
+
// Module browser assets are addressed at the ROOT on every host — the URL the
|
|
830
|
+
// loader's hallWidgetScripts projection emits is an absolute /modules/<key>/…
|
|
831
|
+
// and injectModuleHallWidgets puts that same tag in the hall index on BOTH
|
|
832
|
+
// hosts (task 1002848). Without this line the shim rewrites it to
|
|
833
|
+
// /builders/modules/… before the MODULE_ASSET_RE handler below can match, so
|
|
834
|
+
// the tag is injected on the subdomain and the asset 404s there — injection
|
|
835
|
+
// works on both hosts, serving works only on the apex. The handler keeps all
|
|
836
|
+
// its own defences (the isModuleEnabled gate, the traversal check); this only
|
|
837
|
+
// lets the request reach it.
|
|
838
|
+
//
|
|
839
|
+
// The general hazard: ANY apex-rooted route registered BELOW this shim is
|
|
840
|
+
// eaten the same way. Today that is this one and the /docs family (idea 529
|
|
841
|
+
// → task 1002861); /downloads/* dodges it by being registered ABOVE instead.
|
|
842
|
+
if (req.path.startsWith('/modules/')) return next();
|
|
829
843
|
if (req.path === '/builders' || req.path.startsWith('/builders/')) return next();
|
|
830
844
|
// Prefix with /builders so the handlers below match. req.url carries the
|
|
831
845
|
// query string; preserve it. '/' → '/builders/', '/watch' → '/builders/watch'.
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.755'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// tests/module_asset_subdomain.mjs — module browser assets must route identically
|
|
2
|
+
// on the apex and on builders.<apex> (task 1002848).
|
|
3
|
+
//
|
|
4
|
+
// THE BUG. A module-contributed hall widget's <script src="/modules/<key>/public/
|
|
5
|
+
// hall-widget.js"> is injected into the hall index on BOTH hosts by
|
|
6
|
+
// injectModuleHallWidgets, but only SERVED on the apex. The builders-subdomain
|
|
7
|
+
// rewrite shim in serve-internal.js runs as app.use middleware, ahead of the
|
|
8
|
+
// MODULE_ASSET_RE route, and rewrites every path on that host onto the /builders/*
|
|
9
|
+
// tree — exempting only /api/, /healthz and paths already under /builders. So
|
|
10
|
+
// /modules/demo/public/hall-widget.js became /builders/modules/... , stopped
|
|
11
|
+
// matching ^/modules/, and fell through to a 404.
|
|
12
|
+
//
|
|
13
|
+
// WHY THIS TEST HAS TO EXIST. The seam is a live no-op today: no module ships a
|
|
14
|
+
// hall-widget.js, so nothing on either host currently requests one, and a
|
|
15
|
+
// regression here would be completely invisible until the first module that does
|
|
16
|
+
// ship one — at which point it would look like a bug in that module. The test
|
|
17
|
+
// therefore drives a module asset that DOES exist (hall-ui's own public/ dir) and
|
|
18
|
+
// asserts the two hosts agree, which is the property the fix is really about.
|
|
19
|
+
//
|
|
20
|
+
// serve-internal is DB-free (the pg pool connects lazily), so we mount it on a
|
|
21
|
+
// throwaway express app and hit it over loopback — same pattern as
|
|
22
|
+
// tests/docs_openapi_route.mjs.
|
|
23
|
+
//
|
|
24
|
+
// USE node:http, NOT fetch. `Host` is a forbidden header name in the fetch spec,
|
|
25
|
+
// so `fetch(url, { headers: { host } })` SILENTLY drops the override and sends the
|
|
26
|
+
// real one (127.0.0.1:<port>). A host-routing test written with fetch therefore
|
|
27
|
+
// compares the apex against itself, passes, and keeps passing with the fix
|
|
28
|
+
// reverted — which is exactly what the first draft of this file did until the
|
|
29
|
+
// mutation probe caught it. node:http honours an explicit Host in options.headers.
|
|
30
|
+
//
|
|
31
|
+
// Run: node --test tests/module_asset_subdomain.mjs
|
|
32
|
+
|
|
33
|
+
import { test } from 'node:test';
|
|
34
|
+
import assert from 'node:assert/strict';
|
|
35
|
+
import { createRequire } from 'node:module';
|
|
36
|
+
import http from 'node:http';
|
|
37
|
+
|
|
38
|
+
const require = createRequire(import.meta.url);
|
|
39
|
+
|
|
40
|
+
// Any host starting with "builders." is the subdomain; anything else is the apex.
|
|
41
|
+
const APEX = 'example.com';
|
|
42
|
+
const SUBDOMAIN = 'builders.example.com';
|
|
43
|
+
|
|
44
|
+
// A file the hall-ui module really ships, so a 200 proves the request reached the
|
|
45
|
+
// MODULE_ASSET_RE handler AND the handler found bytes — not merely that it matched.
|
|
46
|
+
const REAL_ASSET = '/modules/hall-ui/public/atlas.css';
|
|
47
|
+
|
|
48
|
+
async function withServer(fn) {
|
|
49
|
+
const express = require('express');
|
|
50
|
+
const { mountInternalSurfaces } = require('../src/bongos/serve-internal.js');
|
|
51
|
+
const app = express();
|
|
52
|
+
mountInternalSurfaces(app);
|
|
53
|
+
const server = await new Promise((resolve) => {
|
|
54
|
+
const s = app.listen(0, () => resolve(s));
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
await fn(server.address().port);
|
|
58
|
+
} finally {
|
|
59
|
+
await new Promise((r) => server.close(r));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// One loopback request with a REAL Host header. Never follows redirects — a 301 is
|
|
64
|
+
// itself a routing decision we want to see.
|
|
65
|
+
function get(port, path, host) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const req = http.request({ host: '127.0.0.1', port, path, headers: { Host: host } }, (res) => {
|
|
68
|
+
let body = '';
|
|
69
|
+
res.setEncoding('utf8');
|
|
70
|
+
res.on('data', (c) => { body += c; });
|
|
71
|
+
res.on('end', () => resolve({ status: res.statusCode, body, headers: res.headers }));
|
|
72
|
+
});
|
|
73
|
+
req.on('error', reject);
|
|
74
|
+
req.end();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The harness is only meaningful if the Host header actually reaches the app, and
|
|
79
|
+
// the fetch trap above proves that is not self-evident. Assert it directly, so a
|
|
80
|
+
// future change that breaks host propagation fails HERE with a clear reason rather
|
|
81
|
+
// than turning every test below into a silent apex-vs-apex tautology.
|
|
82
|
+
test('the harness really varies the Host — without this the rest is vacuous', async () => {
|
|
83
|
+
await withServer(async (port) => {
|
|
84
|
+
const { isBuildersHost } = require('../src/bongos/serve-internal.js');
|
|
85
|
+
assert.equal(isBuildersHost({ headers: { host: SUBDOMAIN } }), true);
|
|
86
|
+
assert.equal(isBuildersHost({ headers: { host: APEX } }), false);
|
|
87
|
+
// and end-to-end: both hosts 301 /builders/, but to DIFFERENT places — the apex
|
|
88
|
+
// sends you to the subdomain, the subdomain strips the redundant prefix. Equal
|
|
89
|
+
// targets would mean the Host never reached the app.
|
|
90
|
+
const sub = await get(port, '/builders/', SUBDOMAIN);
|
|
91
|
+
const apex = await get(port, '/builders/', APEX);
|
|
92
|
+
assert.notEqual(sub.headers.location, apex.headers.location,
|
|
93
|
+
'the two hosts must route differently, or the Host header is being dropped');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('a real module asset serves on the apex AND on builders.<apex>', async () => {
|
|
98
|
+
await withServer(async (port) => {
|
|
99
|
+
const apex = await get(port, REAL_ASSET, APEX);
|
|
100
|
+
const sub = await get(port, REAL_ASSET, SUBDOMAIN);
|
|
101
|
+
assert.equal(apex.status, 200, 'precondition: the asset serves on the apex');
|
|
102
|
+
assert.equal(sub.status, 200,
|
|
103
|
+
'the subdomain must serve it too — the rewrite shim used to turn this into /builders/modules/... and 404');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('the two hosts return the same body, not just the same status', async () => {
|
|
108
|
+
await withServer(async (port) => {
|
|
109
|
+
const apex = await get(port, REAL_ASSET, APEX);
|
|
110
|
+
const sub = await get(port, REAL_ASSET, SUBDOMAIN);
|
|
111
|
+
assert.ok(apex.body.length > 0, 'precondition: the asset is not empty');
|
|
112
|
+
assert.equal(sub.body, apex.body, 'the subdomain must serve the same bytes, not a rewritten page');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('an unknown module 404s on BOTH hosts — the exemption grants no new reach', async () => {
|
|
117
|
+
await withServer(async (port) => {
|
|
118
|
+
const p = '/modules/no-such-module/public/x.css';
|
|
119
|
+
assert.equal((await get(port, p, APEX)).status, 404);
|
|
120
|
+
assert.equal((await get(port, p, SUBDOMAIN)).status, 404,
|
|
121
|
+
'the shim exemption must not turn an unknown module into anything but a 404');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('a missing file inside a REAL module 404s on both hosts', async () => {
|
|
126
|
+
await withServer(async (port) => {
|
|
127
|
+
const p = '/modules/hall-ui/public/definitely-not-here.css';
|
|
128
|
+
assert.equal((await get(port, p, APEX)).status, 404);
|
|
129
|
+
assert.equal((await get(port, p, SUBDOMAIN)).status, 404);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('path traversal out of the module dir is refused on the subdomain too', async () => {
|
|
134
|
+
await withServer(async (port) => {
|
|
135
|
+
// The handler normalizes and rejects a rel path escaping modules/<key>/public.
|
|
136
|
+
// Worth pinning on the SUBDOMAIN specifically: that request now reaches the
|
|
137
|
+
// handler where it previously never did, so this is newly-exercised ground.
|
|
138
|
+
for (const p of ['/modules/hall-ui/public/../../../package.json',
|
|
139
|
+
'/modules/hall-ui/public/..%2f..%2f..%2fpackage.json']) {
|
|
140
|
+
const r = await get(port, p, SUBDOMAIN);
|
|
141
|
+
assert.notEqual(r.status, 200, `traversal must not serve a file outside the module dir (${p})`);
|
|
142
|
+
assert.ok(!/"name":\s*"@bongos/.test(r.body), 'package.json bytes must never appear in the body');
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('the exemption is scoped to /modules/ — a lookalike path is still rewritten', async () => {
|
|
148
|
+
await withServer(async (port) => {
|
|
149
|
+
// If the exemption had been written as a bare `includes('modules')` or without
|
|
150
|
+
// the trailing slash, this path would escape the shim too.
|
|
151
|
+
const r = await get(port, '/moduleslike', SUBDOMAIN);
|
|
152
|
+
assert.notEqual(r.status, 200,
|
|
153
|
+
'a path merely starting with "modules" but outside /modules/ is not a module asset');
|
|
154
|
+
});
|
|
155
|
+
});
|