@bongos/core 1.19.726 → 1.19.727
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 +176 -491
- package/.devcontainer/Dockerfile +1 -1
- package/.devcontainer/devcontainer.json +2 -2
- package/.devcontainer/healthcheck.sh +1 -1
- package/.devcontainer/init-firewall.sh +1 -1
- package/.devcontainer/post-create.sh +2 -2
- package/.env.local.example +1 -1
- package/Dockerfile +1 -1
- package/docs/copy-inventory.md +1 -1
- package/docs/copy-registry.json +9 -9
- package/docs/module-api-changelog.md +2 -0
- package/docs/recipes/devbox-app-release.md +4 -4
- package/modules/dev-box/app/scripts/dmgbuild-settings.py +5 -5
- package/modules/dev-box/app/scripts/gen-brand.js +1 -1
- package/modules/dev-box/app/src/brand.js +1 -1
- package/modules/dev-box/app/test/unit.mjs +16 -16
- package/modules/dev-box/box-access.js +1 -1
- package/modules/dev-box/routes/box.js +1 -1
- package/modules/discord/discord-approvals.js +1 -1
- package/modules/grading/grader-model.js +1 -1
- package/modules/grading/grader-prompt.js +1 -1
- package/modules/grading/grader-workers/quality.js +1 -1
- package/modules/hall-ui/module.json +1 -1
- package/modules/hall-ui/public/brand-apply.js +1 -1
- package/modules/hall-ui/public/shell.js +1 -1
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/lifecycle/conflict-resolve.js +1 -1
- package/modules/lifecycle/db-analytics.js +1 -1
- package/modules/lifecycle/routes/analytics.js +1 -1
- package/modules/onboarding/approval-broadcast.js +1 -1
- package/modules/onboarding/onboarding.js +1 -1
- package/modules/provisioning/tests/liveness.mjs +1 -1
- package/modules/public-landing/public/index.html +1 -1
- package/modules/public-landing/public/projects-floor.states.json +2 -2
- package/modules/public-landing/public/projects.probes.json +4 -4
- package/modules/public-landing/public/projects.states.json +2 -2
- package/modules/status-ui/module.json +1 -1
- package/modules/status-ui/public/status.js +4 -4
- package/modules/status-ui/public/style.css +1 -1
- package/modules/ui-design/kit/fixtures/cost-summary.json +1 -1
- package/modules/ui-design/kit/fixtures/featured-hub.json +1 -1
- package/modules/ui-design/kit/fixtures/featured.json +1 -1
- package/modules/ui-design/kit/fixtures/leaderboard.json +1 -1
- package/modules/ui-design/kit/fixtures/my-projects.json +1 -1
- package/modules/ui-design/kit/fixtures/progress.json +1 -1
- package/modules/ui-design/kit/fixtures/recent-shipped.json +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/cron/style_guide_audit.sh +1 -1
- package/scripts/discord/lib.js +1 -1
- package/scripts/discord/post.js +1 -1
- package/scripts/gds/audit-authorship.sh +1 -1
- package/scripts/gds/audit-replay.sh +29 -2
- package/scripts/gds/audit-ships.js +1 -1
- package/scripts/gds/backfill-sessions.js +2 -2
- package/scripts/gds/cf-tunnel.js +4 -4
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +6 -6
- package/scripts/gds/cutover-codemod.js +1 -1
- package/scripts/gds/dev-lib.js +2 -2
- package/scripts/gds/diagnose-achievements.js +2 -2
- package/scripts/gds/fitness-checks-identity.js +6 -4
- package/scripts/gds/gen-diagrams.js +1 -1
- package/scripts/gds/leak-scan-allowlist.js +293 -0
- package/scripts/gds/learning-capture.js +1 -1
- package/scripts/gds/linkify-refs.js +2 -2
- package/scripts/gds/local-preview-lib.js +1 -1
- package/scripts/gds/mirror-lineage-check.js +1 -1
- package/scripts/gds/mirror-redact.js +17 -2
- package/scripts/gds/newcomer-floor.templates.json +1 -1
- package/scripts/gds/package-core.js +18 -1
- package/scripts/gds/paste-token.js +1 -1
- package/scripts/gds/provision-units.js +2 -2
- package/scripts/gds/publish-manifest.js +15 -0
- package/scripts/gds/repo-metrics.js +1 -1
- package/scripts/gds/sandbox-stage.js +1 -1
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +1 -1
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +1 -1
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +1 -1
- package/scripts/gds/seed-bongos-tasks.js +2 -2
- package/scripts/gds/seed-gds-v4-tasks.js +1 -1
- package/scripts/gds/seed-medusa-tasks.js +10 -10
- package/scripts/gds/seed-module-upstreaming.js +1 -1
- package/scripts/gds/seed-productization-tasks.js +6 -6
- package/scripts/gds/seed-provisioning-tasks.js +2 -2
- package/scripts/gds/seed-staging.sh +19 -6
- package/scripts/gds/ship-deploy-target.js +1 -1
- package/scripts/gds/ship-flow.js +1 -1
- package/scripts/gds/smoke-gds.js +1 -1
- package/scripts/status-probe.mjs +2 -2
- package/src/bongos/auth-discord.js +2 -2
- package/src/bongos/live-channel.js +1 -1
- package/src/bongos/pool.js +1 -1
- package/src/bongos/routes/auth.js +11 -11
- package/src/bongos/routes/security.js +1 -1
- package/src/bongos/routes.js +1 -1
- package/src/branding.js +1 -1
- package/src/devbox-downloads.js +1 -1
- package/src/instance-config.js +2 -2
- package/src/module-api.js +1 -1
- package/src/module-loader/provenance.js +2 -2
- package/tests/art_stage.mjs +2 -2
- package/tests/auth_anthropic_identity.mjs +1 -1
- package/tests/auth_duplicate_cookie.mjs +1 -1
- package/tests/auth_error_brand.mjs +1 -1
- package/tests/auth_inactive_builder.mjs +1 -1
- package/tests/backup_cli.mjs +4 -4
- package/tests/bongos_box_connect.mjs +2 -2
- package/tests/bongos_downloads.mjs +2 -2
- package/tests/bongos_login.mjs +5 -5
- package/tests/box_connect_e2e.mjs +2 -2
- package/tests/box_dns_repoint.mjs +10 -10
- package/tests/box_host_keys.mjs +1 -1
- package/tests/box_onboard.mjs +9 -9
- package/tests/box_scope_session.mjs +1 -1
- package/tests/box_terminal_ssrf.mjs +7 -7
- package/tests/boxes.mjs +9 -9
- package/tests/branding.mjs +2 -2
- package/tests/bump_version.mjs +2 -2
- package/tests/cf_tunnel.mjs +40 -40
- package/tests/cli_token_reissue.mjs +7 -7
- package/tests/cloud_bongos_pack.mjs +1 -1
- package/tests/currency_label.mjs +8 -8
- package/tests/dev_lib.mjs +4 -4
- package/tests/devbox_dmg_backdrop.mjs +1 -1
- package/tests/devbox_downloads.mjs +2 -2
- package/tests/devbox_icons.mjs +1 -1
- package/tests/discord_approvals.mjs +1 -1
- package/tests/discord_oauth.mjs +2 -2
- package/tests/executed_closure.mjs +6 -1
- package/tests/first_admin_bootstrap.mjs +1 -1
- package/tests/gate_approvals.mjs +5 -5
- package/tests/grade_aggregates_unavailable.mjs +1 -1
- package/tests/hook_user_agents.mjs +2 -2
- package/tests/init.mjs +6 -6
- package/tests/instance_config.mjs +1 -1
- package/tests/instance_manifest.mjs +5 -5
- package/tests/instance_mismatch_guard.mjs +13 -13
- package/tests/leak_scan_allowlist.mjs +94 -0
- package/tests/migrate_root_resolution.mjs +4 -4
- package/tests/mirror_redact.mjs +28 -4
- package/tests/module_catalog.mjs +5 -5
- package/tests/module_submissions.mjs +3 -3
- package/tests/package_core.mjs +1 -1
- package/tests/parallel-load.mjs +6 -6
- package/tests/projects_hub.mjs +2 -2
- package/tests/provision.mjs +50 -50
- package/tests/provisioning_github_app_routes.mjs +1 -1
- package/tests/provisioning_onboard_mode.mjs +2 -2
- package/tests/publish_reconciler.mjs +4 -4
- package/tests/sandbox_stage.mjs +1 -1
- package/tests/scrubber_corpus.mjs +3 -3
- package/tests/search_isolation.mjs +1 -1
- package/tests/session_records.mjs +1 -1
- package/tests/ship_card.mjs +6 -6
- package/tests/ship_ci_deploy.mjs +1 -1
- package/tests/single_host_hall.mjs +9 -9
- package/tests/smoke_preflight.mjs +1 -1
- package/tests/smoke_target.mjs +1 -1
- package/tests/status-probe.mjs +1 -1
- package/tests/status_mirror_sync.mjs +1 -1
- package/docs/adr/0004-amazonprimea-name-and-trademark-acceptance.md +0 -45
- package/docs/design/mocks/_schemes.css +0 -604
- package/docs/design/mocks/component-sheet.html +0 -428
- package/docs/design/mocks/hall/_tokens.css +0 -370
- package/docs/design/mocks/hall/board/v1.html +0 -846
- package/docs/design/mocks/hall/board/v2.html +0 -858
- package/docs/design/mocks/hall/board/v3.html +0 -884
- package/docs/design/mocks/hall/goals/v1.html +0 -1212
- package/docs/design/mocks/hall/goals/v2.html +0 -1192
- package/docs/design/mocks/hall/goals/v3.html +0 -1205
- package/docs/design/mocks/hall/hall-sheet.html +0 -244
- package/docs/design/mocks/hall/reading/v1.html +0 -1125
- package/docs/design/mocks/hall/reading/v2.html +0 -1177
- package/docs/design/mocks/hall/reading/v3.html +0 -1150
- package/docs/design/mocks/hall/settings/v1.html +0 -1238
- package/docs/design/mocks/hall/settings/v2.html +0 -1235
- package/docs/design/mocks/hall/settings/v3.html +0 -1299
- package/docs/design/mocks/hall/task/v1.html +0 -633
- package/docs/design/mocks/hall/task/v2.html +0 -646
- package/docs/design/mocks/hall/task/v3.html +0 -647
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg.json +0 -13
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg.json +0 -13
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg.json +0 -13
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg.json +0 -13
- package/docs/design/mocks/hub/hub-sheet.html +0 -192
- package/docs/design/mocks/hub/mine.html +0 -465
- package/docs/design/mocks/hub/picker.html +0 -421
- package/docs/design/mocks/hub/planets.html +0 -356
- package/docs/design/mocks/hub/v1.html +0 -1209
- package/docs/design/mocks/hub/v2.html +0 -1257
- package/docs/design/mocks/hub/v3.html +0 -1496
- package/docs/design/mocks/hub/wizard.html +0 -421
- package/docs/design/mocks/landing/landing-sheet.html +0 -131
- package/docs/design/mocks/landing/v1.html +0 -956
- package/docs/design/mocks/landing/v2.html +0 -708
- package/docs/design/mocks/landing/v3.html +0 -911
- package/docs/design/mocks/landing-page-mock.html +0 -687
- package/docs/design/mocks/landing-palettes.html +0 -103
- package/docs/design/mocks/projects-hub-mock.html +0 -991
- package/docs/design/mocks/projects-hub-variants.html +0 -146
- package/docs/design/mocks/projects-page-mock.html +0 -167
- package/docs/design/mocks/world/blue-hud/_tokens.css +0 -246
- package/docs/design/mocks/world/blue-hud/hall.html +0 -595
- package/docs/design/mocks/world/blue-hud/landing.html +0 -424
- package/docs/design/mocks/world/blue-hud/status.html +0 -410
- package/docs/design/mocks/world/chrome-blobs/_tokens.css +0 -479
- package/docs/design/mocks/world/chrome-blobs/hall.html +0 -553
- package/docs/design/mocks/world/chrome-blobs/landing-b.html +0 -581
- package/docs/design/mocks/world/chrome-blobs/landing.html +0 -328
- package/docs/design/mocks/world/chrome-blobs/status.html +0 -416
- package/docs/design/mocks/world/natgeo-editorial/_tokens.css +0 -377
- package/docs/design/mocks/world/natgeo-editorial/hall.html +0 -649
- package/docs/design/mocks/world/natgeo-editorial/landing-b.html +0 -743
- package/docs/design/mocks/world/natgeo-editorial/landing.html +0 -475
- package/docs/design/mocks/world/natgeo-editorial/status.html +0 -437
- package/docs/design/mocks/world/organic-cards/_tokens.css +0 -462
- package/docs/design/mocks/world/organic-cards/hall.html +0 -625
- package/docs/design/mocks/world/organic-cards/landing-b.html +0 -736
- package/docs/design/mocks/world/organic-cards/landing.html +0 -420
- package/docs/design/mocks/world/organic-cards/status.html +0 -458
- package/docs/design/mocks/world/world-sheet.html +0 -156
package/scripts/gds/cli-lib.js
CHANGED
|
@@ -49,7 +49,7 @@ const SESSION_READ_PATHS = SESSION_FILENAMES.flatMap((n) => ic.configReadPaths(n
|
|
|
49
49
|
// pack. Override for local dev / staging:
|
|
50
50
|
// CLOUDBONGOS_API_BASE=http://localhost:3000 node scripts/gds/start.js
|
|
51
51
|
// No CLI script hard-codes another instance's URL (task 1002201; pre-ADR-0139 the
|
|
52
|
-
//
|
|
52
|
+
// founder instance's apex was baked in literally at four sites here). The session
|
|
53
53
|
// leg was added at task 1002427 — this const previously stopped at the branding
|
|
54
54
|
// default, so module-level consumers (reauth's Settings URL, the hall links
|
|
55
55
|
// hallBase() prints) named the checkout's own instance instead of the one the
|
|
@@ -59,7 +59,7 @@ const API_BASE = ic.resolveApiBase();
|
|
|
59
59
|
// User-Agent for the GDS CLI's HTTP calls — the branding-derived neutral platform UA
|
|
60
60
|
// from the single source, src/branding.js userAgent() (`<repo.name|envPrefix>-gds/1.0`,
|
|
61
61
|
// neutral fallback `cloud-bongos-gds/1.0`), matching the `cloud-bongos-` convention the
|
|
62
|
-
// hooks use (tests/hook_user_agents.mjs). Was
|
|
62
|
+
// hooks use (tests/hook_user_agents.mjs). Was a hardcoded '<founder-instance>-gds-cli/1.0'
|
|
63
63
|
// at both fetch sites — task 1002234. Resolved once so apiCall + cliClient never diverge
|
|
64
64
|
// and branding is read a single time; falls back neutrally if branding is unreadable.
|
|
65
65
|
const CLI_USER_AGENT = (() => {
|
|
@@ -126,7 +126,7 @@ function costPlusOnlyNote(fromServer) {
|
|
|
126
126
|
// hallBase(base) — the instance's builders' hall root for a given API base, e.g.
|
|
127
127
|
// https://cloudbongos.com/builders. Derived from the RESOLVED base (env → session →
|
|
128
128
|
// branding default), never a hardcoded host — so a CLI-printed link always points at
|
|
129
|
-
// the instance the command is actually talking to, not the founder's
|
|
129
|
+
// the instance the command is actually talking to, not the founder instance's box
|
|
130
130
|
// (task 1002233; the printed-link twin of the task-1002201 default-base fix). Falls
|
|
131
131
|
// back to `${base}/builders` when base isn't a parseable absolute URL.
|
|
132
132
|
function hallBase(base = API_BASE) {
|
|
@@ -581,7 +581,7 @@ function printBoxScopeGuidanceAndExit() {
|
|
|
581
581
|
// for ITS OWN origin, the CLI fell back to whatever instance the machine last
|
|
582
582
|
// signed into (session.api_base, else the prod default) and acted THERE. On
|
|
583
583
|
// 2026-07-08 a fresh `demo` clone (config/branding → demo.cloudbongos.com), with
|
|
584
|
-
// no demo session on the machine, borrowed the founder's
|
|
584
|
+
// no demo session on the machine, borrowed the founder instance's session
|
|
585
585
|
// and offered to write a task into the wrong LIVE backlog. A wrong-instance write
|
|
586
586
|
// is a data-integrity hazard, so we fail CLOSED: if the session we'd authenticate
|
|
587
587
|
// with belongs to a different instance than THIS repo is configured for, refuse
|
|
@@ -704,7 +704,7 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
|
|
|
704
704
|
// that instance's origin to session.api_base, so every later command targets it.
|
|
705
705
|
// Precedence mirrors readSessionToken: an explicit <PREFIX>_API_BASE env wins
|
|
706
706
|
// (local dev / staging override), then the logged-in instance, then prod. OTB
|
|
707
|
-
// sessions already store api_base
|
|
707
|
+
// sessions already store the founder instance's api_base, so this is a no-op
|
|
708
708
|
// for the OTB instance and only bites once someone logs in to a different one.
|
|
709
709
|
const base = ic.resolveApiBase({ session: session || null });
|
|
710
710
|
const headers = {
|
|
@@ -755,7 +755,7 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
|
|
|
755
755
|
// • throwOnError:false — every call resolves to { status, ok, data } (never
|
|
756
756
|
// throws on HTTP status; network errors still reject), so the long-standing
|
|
757
757
|
// apiCall contract holds and status-inspecting callers port 1:1.
|
|
758
|
-
// • fetch — a wrapper that keeps apiCall's behavior: the
|
|
758
|
+
// • fetch — a wrapper that keeps apiCall's behavior: the founder CLI
|
|
759
759
|
// User-Agent, plus the 401 and box_scope 403 auto-exit guidance.
|
|
760
760
|
// Named methods (api.tasks.getTasksId({ id })) replace hand-built apiCall paths;
|
|
761
761
|
// api.request(method, path, opts) is the escape hatch for non-spec routes.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Its relative core require()s — `require('../../src/module-api')` (host modules, F1),
|
|
8
8
|
// `require('./src/bongos/…')` (the game entrypoint server.js, F3) — point at the DELETED
|
|
9
9
|
// instance `src/` → boot crash (the 2026-07-10 rehearsal, findings F1+F3:
|
|
10
|
-
// docs/reviews/2026-07-10-
|
|
10
|
+
// docs/reviews/2026-07-10-example-decouple-r89-rehearsal.md). Core modules are UNAFFECTED:
|
|
11
11
|
// they ship INSIDE @bongos/core, where `src/` sits next to `modules/`, so their identical
|
|
12
12
|
// relative requires still resolve. The fix is to rewrite the host files' core requires to the
|
|
13
13
|
// package's EXPORTED subpaths (`@bongos/core/module-api`, `@bongos/core/serve-internal`, …).
|
package/scripts/gds/dev-lib.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// createdb, migrate, set PORT/PGDATABASE/PGHOST/OAuth env, launch the server) with
|
|
10
10
|
// three silent mismatches: the server reads PORT from env and NOT from branding
|
|
11
11
|
// (platform-server.js:44); the server DB name collapses to the OS user while
|
|
12
|
-
// migrate.sh defaults to `
|
|
12
|
+
// migrate.sh defaults to `example` (pool.js:47 vs migrate.sh:52) unless
|
|
13
13
|
// PGDATABASE is set for BOTH; and node-pg defaults to the Linux peer socket
|
|
14
14
|
// /var/run/postgresql (pool.js:46), which does not exist under Homebrew Postgres on
|
|
15
15
|
// a Mac. `bongos dev` collapses all of that into one idempotent command.
|
|
@@ -91,7 +91,7 @@ function sanitizeDbName(s) {
|
|
|
91
91
|
// The DB name BOTH migrate.sh and the server pool will use. Precedence:
|
|
92
92
|
// explicit --db / override -> branding db.database -> the repo dir name ->
|
|
93
93
|
// 'cloudbongos_dev'. Whatever we pick is exported as PGDATABASE for both sides,
|
|
94
|
-
// which is the fix for the pool.js(OS-user) vs migrate.sh(
|
|
94
|
+
// which is the fix for the pool.js(OS-user) vs migrate.sh(example) split.
|
|
95
95
|
function deriveDbName({ brandingDbName, instanceRoot, override } = {}) {
|
|
96
96
|
return (
|
|
97
97
|
sanitizeDbName(override) ||
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
//
|
|
18
18
|
// Usage:
|
|
19
19
|
// # report only (exit 1 if any builder has a missed unlock — a smoke check):
|
|
20
|
-
// ssh
|
|
20
|
+
// ssh <user>@<db-host> 'cd ~/<instance> && node scripts/gds/diagnose-achievements.js'
|
|
21
21
|
// # backfill: actually unlock every missed achievement (idempotent), then report:
|
|
22
|
-
// ssh
|
|
22
|
+
// ssh <user>@<db-host> 'cd ~/<instance> && node scripts/gds/diagnose-achievements.js --apply'
|
|
23
23
|
|
|
24
24
|
const { pool } = require('../../src/bongos/pool');
|
|
25
25
|
const {
|
|
@@ -72,9 +72,11 @@ const IDENTITY_SCAN_EXTRA_FILES = [];
|
|
|
72
72
|
// (`dev.<host>`, the terminal zone). These are infrastructure identifiers,
|
|
73
73
|
// not an instance URL; routing them through branding would be wrong. (Its
|
|
74
74
|
// two genuine API-base defaults WERE fixed in 1002428.)
|
|
75
|
-
// * publish-manifest.js — the literal FILENAMES of
|
|
76
|
-
// (
|
|
77
|
-
//
|
|
75
|
+
// * publish-manifest.js — the literal FILENAMES of three real committed docs
|
|
76
|
+
// (the two decouple recipes, plus the instance's own name/trademark ADR that
|
|
77
|
+
// task 1003869 excluded: its prose redacts cleanly but the FILENAME rides in
|
|
78
|
+
// the published manifest). Renaming the strings without renaming the files
|
|
79
|
+
// would break the publish manifest.
|
|
78
80
|
// * backfill-sessions.js / version-close.js — one-time operator runbook text
|
|
79
81
|
// naming the actual prod DB + a historical local directory. Describing what
|
|
80
82
|
// an operator must literally type is the correct content.
|
|
@@ -112,7 +114,7 @@ const IDENTITY_SCAN_ALLOWED = {
|
|
|
112
114
|
'scripts/gds/seed-staging.sh': 4,
|
|
113
115
|
'scripts/gds/backfill-sessions.js': 2,
|
|
114
116
|
'scripts/gds/box.js': 3,
|
|
115
|
-
'scripts/gds/publish-manifest.js':
|
|
117
|
+
'scripts/gds/publish-manifest.js': 3,
|
|
116
118
|
'scripts/gds/seed-bongos-coreB-tranche1-tasks.js': 1,
|
|
117
119
|
'scripts/gds/seed-bongos-coreB-tranche2-tasks.js': 1,
|
|
118
120
|
'scripts/gds/seed-bongos-goal-hierarchy-tasks.js': 1,
|
|
@@ -53,7 +53,7 @@ const ASSERT_PATH = path.join(DIR, 'assertions.json');
|
|
|
53
53
|
// The conceptual diagrams (03/04) carry instance identity:
|
|
54
54
|
// the currency name, the public host, the world/product name. Read them from
|
|
55
55
|
// the branding contract so a neutral instance renders neutral diagrams and an
|
|
56
|
-
// owner's instance renders its own — no hard-coded OTB/
|
|
56
|
+
// owner's instance renders its own — no hard-coded OTB/Example tokens, no
|
|
57
57
|
// prod IP. Fail-safe to the neutral defaults (the generator must run headless,
|
|
58
58
|
// and a branding validation error must not break diagram regeneration).
|
|
59
59
|
// NOTE: the diagram FILE ids (e.g. 03-drachmae-karma) are structural — the hall
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// scripts/gds/leak-scan-allowlist.js — the NAMED, REASONED exceptions to the
|
|
5
|
+
// prior-instance leak scan (task 1003869, the public-@bongos/core gate).
|
|
6
|
+
//
|
|
7
|
+
// WHY THIS EXISTS. scanArtifactForLeaks (scripts/gds/package-core.js) flags any
|
|
8
|
+
// prior-instance literal or public IP anywhere in the built artifact. Most hits are
|
|
9
|
+
// real and get swept at source. A residue is not: some files must contain the very
|
|
10
|
+
// string the scan looks for, because containing it is their job. A detector has to
|
|
11
|
+
// carry the pattern it detects; a test that proves "a public IP is flagged" has to
|
|
12
|
+
// hand the scanner a public IP. Those cannot be swept — swept, the tests stop
|
|
13
|
+
// testing — so they are recorded HERE, each one named with the reason it stays.
|
|
14
|
+
//
|
|
15
|
+
// THE RULE THIS FILE ENFORCES ON ITSELF. An entry is (path, classes, reason). Never
|
|
16
|
+
// a bare path: allowlisting a whole FILE would let a genuinely new leak ride into an
|
|
17
|
+
// already-listed file unnoticed, which is precisely the failure mode the scan exists
|
|
18
|
+
// to prevent. A literal whose class is not named for that path is still a hard failure.
|
|
19
|
+
//
|
|
20
|
+
// STALENESS IS A FAILURE TOO. unusedEntries() reports entries that matched nothing.
|
|
21
|
+
// An allowlist nobody prunes becomes a list of permissions nobody remembers granting,
|
|
22
|
+
// so the sweep's own test asserts every entry still earns its place.
|
|
23
|
+
|
|
24
|
+
// Addresses that are never ANY instance's topology: public resolvers, IANA's
|
|
25
|
+
// documentation host, and a well-known forge. Global rather than per-file because
|
|
26
|
+
// the safety here comes from the literal itself, not from which file holds it —
|
|
27
|
+
// 8.8.8.8 is Google's resolver in every file in the world. RFC-reserved ranges
|
|
28
|
+
// (5737 TEST-NET, 2544 benchmarking, 3849 v6 doc) are NOT here: those are handled
|
|
29
|
+
// a layer down in mirror-redact.js, which never classes them public to begin with.
|
|
30
|
+
const WELL_KNOWN_PUBLIC_IPS = {
|
|
31
|
+
'8.8.8.8': 'Google Public DNS — the canonical example resolver',
|
|
32
|
+
'8.8.4.4': 'Google Public DNS, secondary',
|
|
33
|
+
'1.1.1.1': 'Cloudflare DNS — the canonical example resolver',
|
|
34
|
+
'1.0.0.1': 'Cloudflare DNS, secondary',
|
|
35
|
+
'9.9.9.9': 'Quad9 DNS — a public resolver used as a probe fixture',
|
|
36
|
+
'4.4.4.4': 'a public resolver address used as a second, distinct fixture',
|
|
37
|
+
'1.2.3.4': 'the conventional placeholder quad — routable, so it exercises the "public" branch, but nobody here owns it',
|
|
38
|
+
'3.8.8.8': 'a near-miss of 8.8.8.8, used to prove the match is exact and not a prefix',
|
|
39
|
+
'93.184.216.34': "IANA's example.com — the documentation host, by definition not ours",
|
|
40
|
+
'140.82.121.4': 'github.com — a public forge, reached by name everywhere else in the tree',
|
|
41
|
+
'2606:4700:4700::1111': 'Cloudflare DNS over IPv6 — the v6 counterpart of 1.1.1.1',
|
|
42
|
+
'2606:2800:220:1:248:1893:25c8:1946': "IANA's example.com over IPv6",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// (path, classes, reason). Scoped to one file and the classes that file legitimately
|
|
46
|
+
// carries; see floorLiteralsFor() below for why an entry names a class, not a string.
|
|
47
|
+
const FILE_ALLOWLIST = [
|
|
48
|
+
{
|
|
49
|
+
path: 'scripts/gds/mirror-redact.js',
|
|
50
|
+
classes: ['public-ip'],
|
|
51
|
+
reason:
|
|
52
|
+
'This file IS the IP detector. Its RFC range comments and boundary-guard doc comments '
|
|
53
|
+
+ 'quote address forms so the next reader can see what each guard rejects. The same '
|
|
54
|
+
+ "self-reference the file's own SELF-LEAK INVARIANT describes for instance literals: it "
|
|
55
|
+
+ 'carries no instance topology, only universal RFC constants.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
path: 'scripts/gds/audit-authorship.sh',
|
|
59
|
+
classes: ['owner', 'token'],
|
|
60
|
+
reason:
|
|
61
|
+
'This IS the authorship detector. Its PATTERN and SPEC_ALLOWLIST_PATTERN must contain the '
|
|
62
|
+
+ 'literals verbatim or they match nothing — the script exists to find exactly these. '
|
|
63
|
+
+ 'Neutralising them here would silently disarm the audit that keeps them out elsewhere.',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
path: 'tests/package_core.mjs',
|
|
67
|
+
classes: ['token', 'owner', 'public-ip'],
|
|
68
|
+
reason:
|
|
69
|
+
"The leak scanner's own test suite. It feeds scanArtifactForLeaks each literal and a "
|
|
70
|
+
+ 'routable address and asserts they are flagged; with neutral fixtures it would assert '
|
|
71
|
+
+ "nothing. The addresses it uses are placeholders (1.2.3.4), never this instance's.",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
path: 'tests/mirror_redact.mjs',
|
|
75
|
+
classes: ['public-ip'],
|
|
76
|
+
reason:
|
|
77
|
+
'The redactor/detector test suite. It must hand firstPublicIP genuinely routable v4 and v6 '
|
|
78
|
+
+ 'addresses to prove the narrowing guards did not blunt the gate, and near-miss forms '
|
|
79
|
+
+ '(leading-zero octets, ::/8 shorthand) to prove they are rejected. Public resolvers only.',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
path: 'tests/public_mirror_export.mjs',
|
|
83
|
+
classes: ['public-ip'],
|
|
84
|
+
reason:
|
|
85
|
+
'Exercises redactBlob end to end, which requires an address that survives to the gate. '
|
|
86
|
+
+ 'Uses the synthetic-instance fixtures declared at the top of that file.',
|
|
87
|
+
},
|
|
88
|
+
// ── the DEFINITIONS the scan itself reads ──────────────────────────────────────────
|
|
89
|
+
{
|
|
90
|
+
path: 'scripts/gds/package-core.js',
|
|
91
|
+
classes: ['token', 'owner'],
|
|
92
|
+
reason:
|
|
93
|
+
'Holds PRIOR_INSTANCE_FLOOR — the single definition of what counts as a prior-instance '
|
|
94
|
+
+ 'literal, which this allowlist and the scan both read. The list cannot be neutral without '
|
|
95
|
+
+ 'the scan matching nothing at all.',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
path: 'scripts/gds/fitness-checks-identity.js',
|
|
99
|
+
classes: ['token'],
|
|
100
|
+
reason:
|
|
101
|
+
'The identity fitness check. Its rule array IS a case-insensitive regex per host-brand '
|
|
102
|
+
+ 'literal — the patterns that stop a brand being re-hardcoded anywhere else in the tree. '
|
|
103
|
+
+ 'Neutralising them disarms the check that keeps the rest of this sweep from regressing. '
|
|
104
|
+
+ '(Described, not quoted: quoting the patterns here put them straight back into a shipped '
|
|
105
|
+
+ 'file, and the check itself caught this file doing exactly that.)',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
path: 'scripts/gds/publish-manifest.js',
|
|
109
|
+
classes: ['token'],
|
|
110
|
+
reason:
|
|
111
|
+
'The exclusion list names real excluded FILES (the instance-decouple recipes). A path is not '
|
|
112
|
+
+ 'prose — renamed, it stops matching and the excluded docs start publishing.',
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// ── the literal IS the test subject ────────────────────────────────────────────────
|
|
116
|
+
// These four PLANT a host-brand literal and assert a guard catches it, or compare a
|
|
117
|
+
// fixture against this instance's real identity. Neutralising the literal does not make
|
|
118
|
+
// the test neutral — it makes it pass while proving nothing, which is worse than the hit
|
|
119
|
+
// it removes. Each was caught by the suite when a first sweep renamed it (task 1003869).
|
|
120
|
+
{
|
|
121
|
+
path: 'tests/fitness.mjs',
|
|
122
|
+
classes: ['token'],
|
|
123
|
+
reason:
|
|
124
|
+
'The identity gate\'s "not a no-op" proof plants a bare host-brand literal in code, a URL '
|
|
125
|
+
+ 'and HTML, and asserts the gate reports all three. A neutral literal is not caught, so the '
|
|
126
|
+
+ 'test would assert the gate works by handing it nothing to find.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
path: 'tests/government_vanilla_boot.mjs',
|
|
130
|
+
classes: ['token'],
|
|
131
|
+
reason:
|
|
132
|
+
'Same shape: it plants a host brand in the NEUTRAL config pack and asserts the identity '
|
|
133
|
+
+ 'guard catches it. The planted string has to be a real host brand or there is nothing to catch.',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
path: 'tests/module_provenance.mjs',
|
|
137
|
+
classes: ['token'],
|
|
138
|
+
reason:
|
|
139
|
+
"Asserts a module whose origin equals THIS instance's name resolves as own-built rather than "
|
|
140
|
+
+ 'core-loaded. The fixture must carry the real identity for the comparison to mean anything; '
|
|
141
|
+
+ 'renamed, it silently classified own-built modules as core.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
path: 'tests/claude_materialize.mjs',
|
|
145
|
+
classes: ['token'],
|
|
146
|
+
reason:
|
|
147
|
+
'Tests the REBRAND rules, whose whole job is rewriting a founder brand to a target. The '
|
|
148
|
+
+ 'literal is the input being rewritten and the assertion is that it does not survive; '
|
|
149
|
+
+ 'neutralising it collides with the example.org targets the same test rebrands TO.',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
path: 'tests/cli_lib_formatters.mjs',
|
|
153
|
+
classes: ['token'],
|
|
154
|
+
reason:
|
|
155
|
+
'A negative assertion — that the formatter does not reintroduce a hardcoded founder host. '
|
|
156
|
+
+ 'The literal is what must be absent, so it has to be named to be checked for.',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
path: 'tests/smoke_target.mjs',
|
|
160
|
+
classes: ['token'],
|
|
161
|
+
reason:
|
|
162
|
+
'Four assert.doesNotMatch calls proving the smoke target resolves to THIS instance and never '
|
|
163
|
+
+ 'the old hardcoded founder fallback. The literal is what must be absent — a test for its '
|
|
164
|
+
+ 'absence has to name it, and neutralised the guard would pass against anything.',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
path: 'tests/lib_sh_resolution.mjs',
|
|
168
|
+
classes: ['token'],
|
|
169
|
+
reason:
|
|
170
|
+
'Carries the host-identity scan pattern it runs over the baked box scripts — a detector '
|
|
171
|
+
+ 'pattern, the same case as fitness-checks-identity.js.',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
path: 'modules/provisioning/module.json',
|
|
175
|
+
classes: ['token'],
|
|
176
|
+
reason:
|
|
177
|
+
'author / origin / maintainer. This is genuine AUTHORSHIP attribution in an AGPL module '
|
|
178
|
+
+ 'manifest, not leaked topology — naming who wrote a module is what a manifest is for. '
|
|
179
|
+
+ 'Left as the owner wrote it: how a contributor is credited is the owner\'s call, not a '
|
|
180
|
+
+ 'sweep\'s. Raise it with them if the core should attribute to the project instead.',
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
// ── migrations: already applied, therefore immutable ───────────────────────────────
|
|
184
|
+
// A migration that has run on every existing instance cannot be edited: the text is the
|
|
185
|
+
// historical record of what happened, and rewriting it desynchronises new installs from
|
|
186
|
+
// old ones without changing a single existing database. Listed file by file ON PURPOSE —
|
|
187
|
+
// a NEW migration naming a host brand is a real defect and must still fail the scan.
|
|
188
|
+
{ path: 'migrations/003_pms.sql', classes: ['token'], reason: 'applied migration — comment names the status page of the instance it was written for' },
|
|
189
|
+
{ path: 'migrations/006_pms_v2.sql', classes: ['token'], reason: 'applied migration — records where instance-scope rows were relocated' },
|
|
190
|
+
{ path: 'migrations/011_backfill_v2_v3_done_when.sql', classes: ['token'], reason: 'applied migration — backfills done_when prose that names the instance' },
|
|
191
|
+
{ path: 'migrations/067_builder_boxes.sql', classes: ['token'], reason: 'applied migration — documents the per-builder DNS shape in use at the time' },
|
|
192
|
+
{ path: 'migrations/093_tasks_sandbox_review.sql', classes: ['token'], reason: 'applied migration — sample sandbox URL in a column comment' },
|
|
193
|
+
{ path: 'migrations/111_medusa_customer0_framing.sql', classes: ['token'], reason: 'applied migration — the customer-zero framing IS its subject' },
|
|
194
|
+
{ path: 'migrations/185_module_upstreaming_goal_wiring.sql', classes: ['token'], reason: 'applied migration — names the historical extract-core/split work items' },
|
|
195
|
+
{ path: 'migrations/core_191_module_submissions.sql', classes: ['owner'], reason: 'applied migration — names the private upstream repo it explains the absence of' },
|
|
196
|
+
|
|
197
|
+
{
|
|
198
|
+
path: 'modules/public-landing/public/projects.html',
|
|
199
|
+
classes: ['public-ip'],
|
|
200
|
+
reason:
|
|
201
|
+
'Dense SVG path coordinates. The inlined forge logo contains runs like "…1.08.58 1.23.82.72 '
|
|
202
|
+
+ '1.21…", which are four dot-separated numbers under 256 and therefore a valid dotted quad by '
|
|
203
|
+
+ 'SHAPE — no guard can separate them from an address without knowing they sit in a path "d" '
|
|
204
|
+
+ 'attribute, the same "why not a shape rule" secret-scrub.js records. The leading-zero guard '
|
|
205
|
+
+ 'catches the variants that have one; this is the residue. No topology here, only geometry.',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
path: 'modules/security/tests/artifact_scan.mjs',
|
|
209
|
+
classes: ['public-ip'],
|
|
210
|
+
reason: "The security module's scanner test — same reason as tests/package_core.mjs.",
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
// SELF-LEAK INVARIANT (the same one scripts/gds/mirror-redact.js holds about itself,
|
|
215
|
+
// and enforced on this file by scripts/gds/audit-authorship.sh). An allowlist that
|
|
216
|
+
// spelled out the literals it governs would reintroduce customer-zero's name into two
|
|
217
|
+
// more shipped files — defeating the sweep that motivated it. So an entry names a
|
|
218
|
+
// CLASS ('owner', 'token') and the concrete strings are resolved at call time from
|
|
219
|
+
// PRIOR_INSTANCE_FLOOR, which is already the one definition package-core scans by.
|
|
220
|
+
// Scoping is unchanged where it matters: an entry still only covers the file it names.
|
|
221
|
+
function floorLiteralsFor(cls) {
|
|
222
|
+
const floor = require('./package-core.js').PRIOR_INSTANCE_FLOOR;
|
|
223
|
+
if (cls === 'owner') return floor.owners.map((s2) => s2.toLowerCase());
|
|
224
|
+
if (cls === 'token') return floor.tokens.map((s2) => s2.toLowerCase());
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Every concrete literal an entry's classes resolve to, plus 'public-ip' verbatim.
|
|
229
|
+
function entryLiterals(entry) {
|
|
230
|
+
const out = [];
|
|
231
|
+
for (const c of entry.classes || []) {
|
|
232
|
+
if (c === 'public-ip') out.push('public-ip');
|
|
233
|
+
else out.push(...floorLiteralsFor(c));
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const SEP = '>>';
|
|
239
|
+
|
|
240
|
+
// The key a hit is looked up by. An IP hit collapses to the label (any well-known
|
|
241
|
+
// address in an allowlisted file is covered by that file's reason); an instance
|
|
242
|
+
// literal is keyed by the literal itself, so listing one never covers another.
|
|
243
|
+
function hitKey(pathRel, label, sample) {
|
|
244
|
+
return `${pathRel}${SEP}${label === 'public-ip' ? 'public-ip' : String(sample).toLowerCase()}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// The reason a hit is tolerated, or null if it is not. `label` distinguishes an IP hit
|
|
248
|
+
// ('public-ip') from an instance-literal hit, matching findLeaks' own labelling.
|
|
249
|
+
function allowReason(pathRel, label, sample) {
|
|
250
|
+
if (label === 'public-ip' && Object.prototype.hasOwnProperty.call(WELL_KNOWN_PUBLIC_IPS, sample)) {
|
|
251
|
+
return `well-known address: ${WELL_KNOWN_PUBLIC_IPS[sample]}`;
|
|
252
|
+
}
|
|
253
|
+
const key = hitKey(pathRel, label, sample);
|
|
254
|
+
for (const e of FILE_ALLOWLIST) {
|
|
255
|
+
for (const l of entryLiterals(e)) {
|
|
256
|
+
if (`${e.path}${SEP}${l.toLowerCase()}` === key) return e.reason;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Split scan hits into the ones a named exception covers and the ones that remain.
|
|
263
|
+
function partitionHits(hits) {
|
|
264
|
+
const allowed = [];
|
|
265
|
+
const remaining = [];
|
|
266
|
+
for (const h of hits || []) {
|
|
267
|
+
const reason = allowReason(h.path, h.label, h.sample);
|
|
268
|
+
if (reason) allowed.push({ ...h, reason }); else remaining.push(h);
|
|
269
|
+
}
|
|
270
|
+
return { allowed, remaining };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Entries that covered nothing in this scan — a stale permission. Reported so the
|
|
274
|
+
// allowlist shrinks as the trunk gets cleaner instead of quietly outliving its reasons.
|
|
275
|
+
function unusedEntries(hits) {
|
|
276
|
+
const seen = new Set((hits || []).map((h) => hitKey(h.path, h.label, h.sample)));
|
|
277
|
+
const stale = [];
|
|
278
|
+
for (const e of FILE_ALLOWLIST) {
|
|
279
|
+
// Staleness is per CLASS, not per resolved literal. A grant of 'token' covers every
|
|
280
|
+
// token in the floor, and a file that legitimately carries one of them but not the
|
|
281
|
+
// other is not a stale permission — reporting it as one would fire on a healthy tree
|
|
282
|
+
// forever, and a warning that never goes away is one nobody reads.
|
|
283
|
+
for (const cls of e.classes || []) {
|
|
284
|
+
const lits = cls === 'public-ip' ? ['public-ip'] : floorLiteralsFor(cls);
|
|
285
|
+
if (!lits.some((l) => seen.has(`${e.path}${SEP}${l.toLowerCase()}`))) {
|
|
286
|
+
stale.push({ path: e.path, literal: cls });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return stale;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports = { WELL_KNOWN_PUBLIC_IPS, FILE_ALLOWLIST, entryLiterals, allowReason, partitionHits, unusedEntries };
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Usage:
|
|
7
7
|
// node scripts/gds/learning-capture.js \
|
|
8
8
|
// --tags "caddy,cloudflare" \
|
|
9
|
-
// --title "Origin cert needs *.
|
|
9
|
+
// --title "Origin cert needs *.example.com SAN" \
|
|
10
10
|
// --body-file path/to/body.md
|
|
11
11
|
//
|
|
12
12
|
// Body source (in priority order): --body-file PATH > --body "text" > stdin.
|
|
@@ -54,7 +54,7 @@ const path = require('node:path');
|
|
|
54
54
|
// WRITTEN INTO COMMITTED markdown, so a per-dev CLOUDBONGOS_API_BASE=localhost
|
|
55
55
|
// override must never leak into a committed doc link (task 1002234; the committed-doc
|
|
56
56
|
// twin of the task-1002233 printed-link fix, which purged the last hardcoded
|
|
57
|
-
// `https://
|
|
57
|
+
// `https://example.com`). When branding is unreadable the base is the
|
|
58
58
|
// root-relative '/builders#/…' — never a hardcoded founder host. Reading branding is
|
|
59
59
|
// a sync file read (no network), safe at module scope for this token-free CI gate.
|
|
60
60
|
// A LOOPBACK origin is treated exactly like an unreadable one — root-relative,
|
|
@@ -178,7 +178,7 @@ const LIST_GLUE_RE = /^[\s,–—-]*(?:and|&|\+|or|to|through)?[\s,–—-]*$/i;
|
|
|
178
178
|
// entity/idea/blocker refs; the idea/blocker resolver links are left alone.
|
|
179
179
|
// HOST-AGNOSTIC (task 1002234 part b): matches any http(s) host OR a root-relative
|
|
180
180
|
// '/builders#/…', so the mislink-cleanup path still recognizes a link a PRIOR run
|
|
181
|
-
// committed under a DIFFERENT host — the legacy `
|
|
181
|
+
// committed under a DIFFERENT host — the legacy `example.com` links, a
|
|
182
182
|
// co-tenant's, or this instance's own — not only the current RESOLVER_BASE host.
|
|
183
183
|
// How many digits a bare "#NNN" may carry — ONE source of truth, because the
|
|
184
184
|
// width had drifted out of date in five places at once (task 1003542).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// scripts/gds/local-preview-lib.js — core of the LOCAL sandbox launcher (task 1056).
|
|
2
2
|
//
|
|
3
3
|
// Gives a builder working on a LOCAL full clone (laptop) the same private game
|
|
4
|
-
// preview a dev box gets at sandbox-<login>.
|
|
4
|
+
// preview a dev box gets at sandbox-<login>.example.com (ADR 0044/0046/0052)
|
|
5
5
|
// — but served at http://localhost:<port> on their OWN machine: no Cloudflare
|
|
6
6
|
// tunnel, no public URL, $0. It runs the ADR 0052 game-only entry
|
|
7
7
|
// (src/preview-server.js), which serves ONLY the playable game (no /api/gds, no
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// consistency + a no-leak check (GDS-V4 · V4.R73 · task 1212, implementing the
|
|
6
6
|
// ADR 0099 "Consequences" recipe).
|
|
7
7
|
//
|
|
8
|
-
// C9 (Cloud Bongos ⇄
|
|
8
|
+
// C9 (Cloud Bongos ⇄ Example decoupling) publishes a redacted, 6-month-
|
|
9
9
|
// delayed public mirror of the private build repo (task 1211, scripts/public-
|
|
10
10
|
// mirror-export.js). This check asserts the two lineages can't silently diverge:
|
|
11
11
|
//
|
|
@@ -50,6 +50,18 @@ const REDACTED_IP = 'REDACTED_IP'; // droplet / public-IP replacement (NOT
|
|
|
50
50
|
// ---------------------------------------------------------------------------
|
|
51
51
|
const IPV4_CANDIDATE_RE = /\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b/g;
|
|
52
52
|
|
|
53
|
+
// A dotted quad with a LEADING-ZERO octet is not an address in any notation a human
|
|
54
|
+
// or a tool writes: `0` stands alone as one digit, and `07` reads as OCTAL, so no log
|
|
55
|
+
// line, config file or doc spells a host that way. What does produce them is packed
|
|
56
|
+
// numeric data — SVG path coordinates. `…5.47 7.59.4.07.5…` in the committed GitHub
|
|
57
|
+
// logo path contains "7.59.4.07", which the candidate regex happily reads as an IP;
|
|
58
|
+
// that alone accounted for ten phantom public-ip leaks across the design mocks and
|
|
59
|
+
// the landing page. The guard reads the raw octet TEXT because the parsed number has
|
|
60
|
+
// already lost the zero, and it costs zero recall — a real leaked IP never has one.
|
|
61
|
+
function hasLeadingZeroOctet(octets) {
|
|
62
|
+
return octets.some((o) => o.length > 1 && o[0] === '0');
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
function isPublicRoutableIPv4(a, b, c, d) {
|
|
54
66
|
for (const o of [a, b, c, d]) if (o < 0 || o > 255) return false; // not a valid dotted-quad
|
|
55
67
|
if (a === 0) return false; // 0.0.0.0/8 "this network"
|
|
@@ -63,6 +75,7 @@ function isPublicRoutableIPv4(a, b, c, d) {
|
|
|
63
75
|
if (a === 192 && b === 0 && c === 2) return false; // 192.0.2.0/24 TEST-NET-1 (RFC 5737)
|
|
64
76
|
if (a === 198 && b === 51 && c === 100) return false; // 198.51.100.0/24 TEST-NET-2
|
|
65
77
|
if (a === 203 && b === 0 && c === 113) return false; // 203.0.113.0/24 TEST-NET-3
|
|
78
|
+
if (a === 198 && (b === 18 || b === 19)) return false; // 198.18.0.0/15 benchmarking (RFC 2544)
|
|
66
79
|
return true;
|
|
67
80
|
}
|
|
68
81
|
|
|
@@ -115,6 +128,7 @@ function isPublicRoutableIPv6(s) {
|
|
|
115
128
|
// assets and left the C9 full-tree mirror gate permanently red (task 1002470).
|
|
116
129
|
if (h < 0x0100) return false; // reserved ::/8
|
|
117
130
|
if (h >= 0xfe80 && h <= 0xfebf) return false; // link-local fe80::/10
|
|
131
|
+
if (h === 0x2001 && g[1] === 0x0db8) return false; // 2001:db8::/32 documentation (RFC 3849)
|
|
118
132
|
if (h >= 0xfc00 && h <= 0xfdff) return false; // ULA fc00::/7
|
|
119
133
|
if (h >= 0xff00) return false; // multicast ff00::/8
|
|
120
134
|
return true; // global unicast (2000::/3, etc.)
|
|
@@ -122,7 +136,7 @@ function isPublicRoutableIPv6(s) {
|
|
|
122
136
|
|
|
123
137
|
function redactPublicIPs(text) {
|
|
124
138
|
let out = text.replace(IPV4_CANDIDATE_RE, (m, a, b, c, d) =>
|
|
125
|
-
isPublicRoutableIPv4(+a, +b, +c, +d) ? REDACTED_IP : m);
|
|
139
|
+
(!hasLeadingZeroOctet([a, b, c, d]) && isPublicRoutableIPv4(+a, +b, +c, +d)) ? REDACTED_IP : m);
|
|
126
140
|
out = out.replace(IPV6_CANDIDATE_RE, (m) => (isPublicRoutableIPv6(m) ? REDACTED_IP : m));
|
|
127
141
|
return out;
|
|
128
142
|
}
|
|
@@ -131,7 +145,7 @@ function firstPublicIP(text) {
|
|
|
131
145
|
IPV4_CANDIDATE_RE.lastIndex = 0;
|
|
132
146
|
let m;
|
|
133
147
|
while ((m = IPV4_CANDIDATE_RE.exec(text))) {
|
|
134
|
-
if (isPublicRoutableIPv4(+m[1], +m[2], +m[3], +m[4])) { IPV4_CANDIDATE_RE.lastIndex = 0; return m[0]; }
|
|
148
|
+
if (!hasLeadingZeroOctet([m[1], m[2], m[3], m[4]]) && isPublicRoutableIPv4(+m[1], +m[2], +m[3], +m[4])) { IPV4_CANDIDATE_RE.lastIndex = 0; return m[0]; }
|
|
135
149
|
}
|
|
136
150
|
IPV6_CANDIDATE_RE.lastIndex = 0;
|
|
137
151
|
while ((m = IPV6_CANDIDATE_RE.exec(text))) {
|
|
@@ -305,6 +319,7 @@ function findSecretLeak(text, extraSecrets = [], opts = undefined) {
|
|
|
305
319
|
module.exports = {
|
|
306
320
|
NEUTRAL_DOMAIN, NEUTRAL_TOKEN, NEUTRAL_OWNER, REDACTED_IP,
|
|
307
321
|
isPublicRoutableIPv4, isPublicRoutableIPv6, parseIPv6, redactPublicIPs, firstPublicIP,
|
|
322
|
+
hasLeadingZeroOctet,
|
|
308
323
|
registrableLabel, escapeRegExp, deriveInstanceLiterals, collectSensitiveLiterals,
|
|
309
324
|
buildRedactionRules, redactText, findLeaks, findSecretLeak,
|
|
310
325
|
};
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"done_when": [
|
|
75
75
|
"You filed 5–8 fresh ideas into the idea_inbox via `node scripts/gds/capture.js` (each prints a `Captured idea #N` confirmation), all in ONE branch (A product, or B internal/UX).",
|
|
76
76
|
"Each idea has a clear title and a body with at least one sentence of detail — for Branch A, one line of in-world lore (who/what it is); for Branch B, what the rough edge is and what 'better' looks like.",
|
|
77
|
-
"Branch A ideas are in the world's voice and consistent with CLAUDE.md §3/§6/§7 (ancient-Greek coast,
|
|
77
|
+
"Branch A ideas are in the world's voice and consistent with CLAUDE.md §3/§6/§7 (ancient-Greek coast, Example, mythic 'discovered secret' tone — not modern/anachronistic).",
|
|
78
78
|
"Each idea body declares its track on a `track: product` or `track: internal` line so triage can route it.",
|
|
79
79
|
"Before filing you ran `GET /api/gds/inbox` and confirmed your ideas are not near-duplicates of open ones.",
|
|
80
80
|
"You touched only the idea_inbox via capture.js — no code, no migrations, no infra, no permission/pipeline files; nothing committed to the repo."
|
|
@@ -87,6 +87,7 @@ const {
|
|
|
87
87
|
} = require(path.join(REPO_ROOT, 'scripts/gds/mirror-redact.js'));
|
|
88
88
|
const { collectEnvSecrets } = require(path.join(REPO_ROOT, 'modules/security/secret-scrub.js'));
|
|
89
89
|
const { redactBlob, parseLsTree } = require(path.join(REPO_ROOT, 'scripts/public-mirror-export.js'));
|
|
90
|
+
const { partitionHits, unusedEntries } = require(path.join(REPO_ROOT, 'scripts/gds/leak-scan-allowlist.js'));
|
|
90
91
|
|
|
91
92
|
const MANIFEST_SCHEMA = 1;
|
|
92
93
|
const ARTIFACT_NAME = 'bongos-core';
|
|
@@ -949,6 +950,22 @@ function main() {
|
|
|
949
950
|
const scanFiles = staged.map((s) => ({ path: s.name.startsWith(TAR_ROOT) ? s.name.slice(TAR_ROOT.length) : s.name, content: s.buf }));
|
|
950
951
|
const scan = scanArtifactForLeaks(scanFiles);
|
|
951
952
|
const strict = process.argv.includes('--leak-scan-strict');
|
|
953
|
+
// Named, reasoned exceptions (task 1003869). A detector must carry the pattern it
|
|
954
|
+
// detects and a "public IPs are flagged" test must be handed a public IP, so those
|
|
955
|
+
// hits are real but not leaks. Each is listed per (file, literal) with its reason in
|
|
956
|
+
// scripts/gds/leak-scan-allowlist.js — never per bare file, which would let a NEW
|
|
957
|
+
// leak ride into an already-listed file unseen.
|
|
958
|
+
const { allowed, remaining } = partitionHits(scan.literalHits);
|
|
959
|
+
if (allowed.length) {
|
|
960
|
+
const byFile = [...new Set(allowed.map((h) => h.path))];
|
|
961
|
+
console.error(`[leak-scan] ${allowed.length} hit(s) across ${byFile.length} file(s) covered by a named exception (scripts/gds/leak-scan-allowlist.js).`);
|
|
962
|
+
}
|
|
963
|
+
const stale = unusedEntries(scan.literalHits);
|
|
964
|
+
if (stale.length) {
|
|
965
|
+
console.error(`[leak-scan] ! ${stale.length} allowlist entr(ies) matched nothing — the trunk got cleaner, so prune them:`);
|
|
966
|
+
for (const e of stale) console.error(` ${e.path} [${e.literal}]`);
|
|
967
|
+
}
|
|
968
|
+
scan.literalHits = remaining;
|
|
952
969
|
if (scan.literalHits.length) {
|
|
953
970
|
const byLit = {};
|
|
954
971
|
for (const h of scan.literalHits) byLit[h.sample] = (byLit[h.sample] || 0) + 1;
|
|
@@ -959,7 +976,7 @@ function main() {
|
|
|
959
976
|
for (const p of files.slice(0, 30)) console.error(` ${p}`);
|
|
960
977
|
if (files.length > 30) console.error(` … +${files.length - 30} more file(s)`);
|
|
961
978
|
if (strict) { console.error('[leak-scan] --leak-scan-strict: refusing to certify a leaky artifact.'); process.exit(1); }
|
|
962
|
-
console.error('[leak-scan] non-fatal for now —
|
|
979
|
+
console.error('[leak-scan] non-fatal for now — the trunk sweep (task 1003869) is clearing the residue; it becomes a hard gate when task 1003870 arms publish.yml. Run --leak-scan-strict to enforce today.');
|
|
963
980
|
} else {
|
|
964
981
|
console.error('[leak-scan] ✓ no prior-instance literals in the built artifact.');
|
|
965
982
|
}
|
|
@@ -35,7 +35,7 @@ const SESSION_PATH = ic.configPath(SESSION_FILENAMES[0]);
|
|
|
35
35
|
// On a RE-auth a session file already exists, and its api_base is the best signal
|
|
36
36
|
// of which instance the builder is on — this file used to hardcode the founder
|
|
37
37
|
// host here (a straggler the task 1002201 sweep missed), so a token minted by
|
|
38
|
-
// another instance was verified against
|
|
38
|
+
// another instance was verified against example.com and rejected 401 with a
|
|
39
39
|
// perfectly valid token (task 1002427).
|
|
40
40
|
const API_BASE = ic.resolveApiBase();
|
|
41
41
|
|
|
@@ -71,8 +71,8 @@ function serviceUnitPath(inst) { return `/etc/systemd/system/${inst.slug}.servic
|
|
|
71
71
|
// ── nightly DB backup: per-instance timer + service (task 2213) ─────────────
|
|
72
72
|
// PRODUCTIZES the manual task-2210 fix: a newly-provisioned instance was born with
|
|
73
73
|
// NO backups (dir absent, GDS_BACKUP_DB unset → the script fell back to the
|
|
74
|
-
// nonexistent `
|
|
75
|
-
// hardcode /home/lars/
|
|
74
|
+
// nonexistent `example` default, and the repo's global gds-db-backup units
|
|
75
|
+
// hardcode /home/lars/example — wrong for the consumer node_modules/@bongos/
|
|
76
76
|
// core layout). These generators emit a PER-INSTANCE unit pair that fixes all three:
|
|
77
77
|
// • name → `<slug>-db-backup.{service,timer}` so co-tenant instances on one box
|
|
78
78
|
// never collide (each dumps its OWN db; the script's retention prune is
|