@bongos/core 1.19.698 → 1.19.699
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 +11 -11
- 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 +48 -7
- package/src/module-api.js +1 -1
- package/tests/currency_label.mjs +83 -0
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
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.699",
|
|
6
|
+
"core_contract": "1.19.699",
|
|
7
|
+
"source_commit": "c2fc577575ff838fa4b7c6ef9b487a61834508da",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-13T03:11:31.535Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 477,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2627,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "6784a09f48fd3badc907d3157a51dfd66a765084f015b78924eb7229bcb7c757",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2802,7 +2802,7 @@
|
|
|
2802
2802
|
{
|
|
2803
2803
|
"path": "docs/module-api-changelog.md",
|
|
2804
2804
|
"mode": "0000644",
|
|
2805
|
-
"sha256": "
|
|
2805
|
+
"sha256": "83f3755afa52e27b8dc1c9cacc65fd57eb20ca11463f1408bb7298279bbf3994"
|
|
2806
2806
|
},
|
|
2807
2807
|
{
|
|
2808
2808
|
"path": "docs/modules-contract.md",
|
|
@@ -7772,12 +7772,12 @@
|
|
|
7772
7772
|
{
|
|
7773
7773
|
"path": "package-lock.json",
|
|
7774
7774
|
"mode": "0000644",
|
|
7775
|
-
"sha256": "
|
|
7775
|
+
"sha256": "7235324939dc1acfb616300365334ebf96e054f83b8083b15cd2f99eeb5a6d71"
|
|
7776
7776
|
},
|
|
7777
7777
|
{
|
|
7778
7778
|
"path": "package.json",
|
|
7779
7779
|
"mode": "0000644",
|
|
7780
|
-
"sha256": "
|
|
7780
|
+
"sha256": "c650680172226a9259dbb3da9cfc459a3c94f3fa5578be520c27d2b71d0a71d9"
|
|
7781
7781
|
},
|
|
7782
7782
|
{
|
|
7783
7783
|
"path": "public-docs/index.html",
|
|
@@ -9512,7 +9512,7 @@
|
|
|
9512
9512
|
{
|
|
9513
9513
|
"path": "src/bongos/serve-internal.js",
|
|
9514
9514
|
"mode": "0000644",
|
|
9515
|
-
"sha256": "
|
|
9515
|
+
"sha256": "61b13299ffdd7f036d5bc3d364135bd8ca4d3470223001b8ff6cc45d858d6107"
|
|
9516
9516
|
},
|
|
9517
9517
|
{
|
|
9518
9518
|
"path": "src/bongos/uptime-poller.js",
|
|
@@ -9542,7 +9542,7 @@
|
|
|
9542
9542
|
{
|
|
9543
9543
|
"path": "src/module-api.js",
|
|
9544
9544
|
"mode": "0000644",
|
|
9545
|
-
"sha256": "
|
|
9545
|
+
"sha256": "f6283921aa6c4af4de772e638a58ad28111bb8e6664a4bb67e15bc86ad1083f3"
|
|
9546
9546
|
},
|
|
9547
9547
|
{
|
|
9548
9548
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10327,7 +10327,7 @@
|
|
|
10327
10327
|
{
|
|
10328
10328
|
"path": "tests/currency_label.mjs",
|
|
10329
10329
|
"mode": "0000644",
|
|
10330
|
-
"sha256": "
|
|
10330
|
+
"sha256": "20994467d64d35647433c320265e360a520391bff974b1a39a918ef66ad7f172"
|
|
10331
10331
|
},
|
|
10332
10332
|
{
|
|
10333
10333
|
"path": "tests/cutover_codemod.mjs",
|
|
@@ -1855,5 +1855,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1855
1855
|
landed since 1.19.696 with no explicit bump. run 34733301156. (task 1002620)
|
|
1856
1856
|
1.19.698 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1857
1857
|
landed since 1.19.697 with no explicit bump. run 34733917938. (task 1002620)
|
|
1858
|
+
1.19.699 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1859
|
+
landed since 1.19.698 with no explicit bump. run 34734848584. (task 1002620)
|
|
1858
1860
|
---------------------------------------------------------------------------
|
|
1859
1861
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.699",
|
|
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.699",
|
|
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.699",
|
|
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",
|
|
@@ -107,25 +107,66 @@ function themeMarkup(theme) {
|
|
|
107
107
|
if (!theme) return '';
|
|
108
108
|
const decls = [];
|
|
109
109
|
for (const [key, cssVar] of Object.entries(COLOR_VARS)) {
|
|
110
|
-
const v = theme.colors?.[key];
|
|
110
|
+
const v = cssSafe(theme.colors?.[key]);
|
|
111
111
|
if (v) decls.push(`${cssVar}:${v};`);
|
|
112
112
|
}
|
|
113
|
-
|
|
114
|
-
if (
|
|
113
|
+
const display = cssSafe(theme.fonts?.display);
|
|
114
|
+
if (display) decls.push(`--font-display:${display};`);
|
|
115
|
+
const body = cssSafe(theme.fonts?.body);
|
|
116
|
+
if (body) decls.push(`--font-body:${body};`);
|
|
115
117
|
let out = '';
|
|
116
118
|
for (const href of theme.fonts?.links || []) {
|
|
117
|
-
out += `<link rel="stylesheet" href="${href}">`;
|
|
119
|
+
out += `<link rel="stylesheet" href="${esc(href)}">`;
|
|
118
120
|
}
|
|
119
121
|
// `backdrop` paints the page background (e.g. the Cloud Bongos sunrise gradient,
|
|
120
122
|
// ADR 0064 / R74) over the flat var(--bg); omitted packs (OTB) keep --bg.
|
|
121
123
|
const css = [];
|
|
122
124
|
if (decls.length) css.push(`:root{${decls.join('')}}`);
|
|
123
|
-
|
|
125
|
+
const backdrop = cssSafe(theme.backdrop);
|
|
126
|
+
if (backdrop) css.push(`body{background:${backdrop};}`);
|
|
124
127
|
if (css.length) out += `<style>${css.join('')}</style>`;
|
|
125
|
-
|
|
128
|
+
// esc() is lossless in an attribute: the HTML parser decodes the entities back
|
|
129
|
+
// before anything reads the URL, so the neutral pack's inline `data:image/svg+xml,
|
|
130
|
+
// <svg …>` favicon resolves to exactly the same bytes it does today.
|
|
131
|
+
if (theme.favicon) out += `<link rel="icon" href="${esc(theme.favicon)}">`;
|
|
126
132
|
return out;
|
|
127
133
|
}
|
|
128
134
|
|
|
135
|
+
// A pack-supplied value on its way into <style> TEXT. esc() is the wrong tool
|
|
136
|
+
// here and would corrupt the output: CSS is not entity-decoded, so a font stack
|
|
137
|
+
// like `"Cinzel", serif` would reach the browser as `"Cinzel", serif`
|
|
138
|
+
// and simply not parse. There is no encoding that both survives CSS and
|
|
139
|
+
// neutralizes markup, so this fails closed instead. The only way out of a <style>
|
|
140
|
+
// element is a literal `</style`, every route to it begins with `<`, and no
|
|
141
|
+
// colour, font stack, or backdrop has a legitimate use for one — so a value
|
|
142
|
+
// carrying `<` is dropped whole rather than emitted half-escaped. Dropping one
|
|
143
|
+
// declaration loses that pack's colour and falls back to style.css's default;
|
|
144
|
+
// emitting it loses the page.
|
|
145
|
+
function cssSafe(v) {
|
|
146
|
+
const s = String(v == null ? '' : v);
|
|
147
|
+
return s.includes('<') ? '' : s;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Serialize a pack value for injection INSIDE a <script> element. JSON.stringify
|
|
151
|
+
// alone is not enough: its output is valid JSON, but the browser reads a script
|
|
152
|
+
// body as raw text FIRST, so a `</script>` anywhere in the pack ends the element
|
|
153
|
+
// early and everything after it lands in the document as markup (the latent
|
|
154
|
+
// self-XSS from an operator-authored pack — idea 348, Hacker dispatch on task
|
|
155
|
+
// 1374). Escaping `<` alone closes it: `</script`, `<script`, and `<!--` all need
|
|
156
|
+
// one. `>` and `&` need no escaping here precisely because a classic script body
|
|
157
|
+
// is NOT entity-decoded — there is nothing for them to start. U+2028/U+2029 are
|
|
158
|
+
// escaped as well: legal in string literals since ES2019, so not a break-out on
|
|
159
|
+
// any browser we serve, but free to escape and still fatal to an older parser.
|
|
160
|
+
// `\u003c` is the same character to the JS parser and never one to the HTML
|
|
161
|
+
// parser, so the value the page actually reads is unchanged. Same fix, same
|
|
162
|
+
// reasoning as modules/provisioning/routes/callback-page.js.
|
|
163
|
+
function jsonForScript(value) {
|
|
164
|
+
return JSON.stringify(value)
|
|
165
|
+
.replace(/</g, '\\u003c')
|
|
166
|
+
.replace(/\u2028/g, '\\u2028')
|
|
167
|
+
.replace(/\u2029/g, '\\u2029');
|
|
168
|
+
}
|
|
169
|
+
|
|
129
170
|
// Inject the client-safe branding pack into a served HTML document (R56 task 1195
|
|
130
171
|
// + R57 task 1196, ADR 0062 §3):
|
|
131
172
|
// * `window.__BRANDING__` — a synchronous global so every hall/status script
|
|
@@ -145,7 +186,7 @@ function injectBrandingGlobal(html, brand = clientBranding(), mods = clientModul
|
|
|
145
186
|
// UI sections for off modules synchronously (no fetch, no race). R60 wires the
|
|
146
187
|
// actual section-hiding; this exposes the data.
|
|
147
188
|
const stamped = applyBrandTokens(html, brand);
|
|
148
|
-
const tag = `${clientBundleScriptTag()}<script>window.__BRANDING__=${
|
|
189
|
+
const tag = `${clientBundleScriptTag()}<script>window.__BRANDING__=${jsonForScript(brand)};window.__MODULES__=${jsonForScript(mods)};</script>${themeMarkup(brand.theme)}`;
|
|
149
190
|
return stamped.includes('</head>') ? stamped.replace('</head>', `${tag}</head>`) : tag + stamped;
|
|
150
191
|
}
|
|
151
192
|
|
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.699'; // 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');
|
package/tests/currency_label.mjs
CHANGED
|
@@ -121,6 +121,89 @@ test('injectBrandingGlobal degrades safely when there is no </head>', () => {
|
|
|
121
121
|
assert.ok(out.includes('<div>no head</div>'));
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
// --- task 1001434 (idea 348): everything the pack contributes is escaped for the
|
|
125
|
+
// context it lands in. The pack is operator-authored, so this is not a defence
|
|
126
|
+
// against a hostile visitor — it is a defence against one stray character in a
|
|
127
|
+
// config file silently turning every served page's <head> into markup.
|
|
128
|
+
|
|
129
|
+
// A pack string carrying `</script>` is the whole bug: the browser finds the end
|
|
130
|
+
// of the script element by scanning raw text, before any JS parsing happens.
|
|
131
|
+
const HOSTILE = {
|
|
132
|
+
identity: { productName: '</script><img src=x onerror=alert(1)>', worldName: 'w' },
|
|
133
|
+
copy: { tagline: 'a < b && c > d' },
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
function brandingJson(out) {
|
|
137
|
+
const m = /window\.__BRANDING__=(.*?);window\.__MODULES__=/s.exec(out);
|
|
138
|
+
assert.ok(m, 'the injected branding assignment must be findable');
|
|
139
|
+
return m[1];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
test('1001434: a `</script>` in the pack cannot close the injected script element', () => {
|
|
143
|
+
const out = injectBrandingGlobal('<head></head>', clientBranding(HOSTILE), {});
|
|
144
|
+
assert.ok(/<script>window\.__BRANDING__=(.*?)<\/script>/s.exec(out), 'the injected block is well-formed');
|
|
145
|
+
// Two closers are correct and expected: the client-bundle <script src=…></script>
|
|
146
|
+
// that leads the tag, and our own terminator. A third would BE the bug — the
|
|
147
|
+
// pack's `</script>` ending the element early. Unfixed, this counts 3.
|
|
148
|
+
assert.equal(out.split('</script>').length - 1, 2, 'no early close');
|
|
149
|
+
assert.ok(!out.includes('<img src=x'), 'the payload never reaches the document as a tag');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('1001434: every `<` in the injected JSON is \\u003c, and the VALUE is unchanged', () => {
|
|
153
|
+
const out = injectBrandingGlobal('<head></head>', clientBranding(HOSTILE), {});
|
|
154
|
+
const json = brandingJson(out);
|
|
155
|
+
assert.ok(!json.includes('<'), 'no raw < survives into the script body');
|
|
156
|
+
assert.ok(json.includes('\\u003c'), 'it is escaped, not stripped');
|
|
157
|
+
// Escaping must be lossless: < is the same character to the JSON parser,
|
|
158
|
+
// so the page reads exactly what the operator configured.
|
|
159
|
+
const parsed = JSON.parse(json);
|
|
160
|
+
assert.equal(parsed.identity.productName, '</script><img src=x onerror=alert(1)>');
|
|
161
|
+
assert.equal(parsed.copy.tagline, 'a < b && c > d', '> and & are left alone — a script body is not entity-decoded');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('1001434: __MODULES__ is escaped too — it rides the same script tag', () => {
|
|
165
|
+
const out = injectBrandingGlobal('<head></head>', clientBranding({}), { enabled: { '</script>': true } });
|
|
166
|
+
assert.equal(out.split('</script>').length - 1, 2, 'the bundle tag and our terminator, and nothing else');
|
|
167
|
+
const m = /window\.__MODULES__=(.*?);<\/script>/s.exec(out);
|
|
168
|
+
assert.ok(m && !m[1].includes('<'));
|
|
169
|
+
assert.deepEqual(JSON.parse(m[1]), { enabled: { '</script>': true } });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('1001434: a theme value carrying `<` is DROPPED rather than breaking out of <style>', () => {
|
|
173
|
+
const m = themeMarkup({
|
|
174
|
+
colors: { bg: '#fff', ink: '</style><script>alert(1)</script>' },
|
|
175
|
+
backdrop: '</style>x',
|
|
176
|
+
fonts: { display: '"Cinzel", serif' },
|
|
177
|
+
});
|
|
178
|
+
assert.ok(!m.includes('<script>'), 'no escape from the style element');
|
|
179
|
+
assert.equal(m.split('</style>').length - 1, 1, 'exactly one style terminator — our own');
|
|
180
|
+
assert.ok(m.includes('--bg:#fff;'), 'the clean declarations still ship');
|
|
181
|
+
assert.ok(!m.includes('--ink:'), 'the poisoned one is dropped whole, not half-escaped');
|
|
182
|
+
assert.ok(!m.includes('body{background:'), 'a poisoned backdrop is dropped too');
|
|
183
|
+
// CSS is not entity-decoded, so esc() here would be a bug of its own: the font
|
|
184
|
+
// stack has to keep its real quotes.
|
|
185
|
+
assert.ok(m.includes('--font-display:"Cinzel", serif;'), 'quotes in a font stack survive intact');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('1001434: a pack cannot break out of the favicon or font-link href attribute', () => {
|
|
189
|
+
const m = themeMarkup({ favicon: 'x"><script>alert(1)</script>', fonts: { links: ['y"><script>alert(2)</script>'] } });
|
|
190
|
+
assert.ok(!m.includes('<script>'), 'neither href can escape its attribute');
|
|
191
|
+
assert.ok(m.includes('"'), 'the quote is entity-escaped, which the parser undoes on read');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('1001434: escaping the favicon is lossless — the neutral inline-SVG data URI still resolves', () => {
|
|
195
|
+
const favicon = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><circle r='8'/></svg>";
|
|
196
|
+
const m = themeMarkup({ favicon });
|
|
197
|
+
const href = /<link rel="icon" href="([^"]*)">/.exec(m);
|
|
198
|
+
assert.ok(href, 'the attribute is still well-formed');
|
|
199
|
+
// Decode the way an HTML parser does; the operator's bytes must come back.
|
|
200
|
+
const decoded = href[1]
|
|
201
|
+
.replace(/</g, '<').replace(/>/g, '>')
|
|
202
|
+
.replace(/"/g, '"').replace(/'/g, "'")
|
|
203
|
+
.replace(/&/g, '&');
|
|
204
|
+
assert.equal(decoded, favicon, 'the browser reads exactly the URI the pack configured');
|
|
205
|
+
});
|
|
206
|
+
|
|
124
207
|
// --- BV1.R75: module hall-widget script injection (ADR 0062 §4) ---
|
|
125
208
|
|
|
126
209
|
const HALL = '<body><script src="/builders/builders.js?v=2026-06-27-r75" defer></script></body>';
|