cursedbelt-server 4.3.0 โ†’ 4.5.0

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.
@@ -0,0 +1,173 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import pkg from '../package.json';
5
+
6
+ /**
7
+ * A public subpath must not STATICALLY drag an OPTIONAL peer, because a static import is
8
+ * paid at import time by every consumer โ€” including the ones that will never call it.
9
+ *
10
+ * ## ๐Ÿ”ด This is the same defect for the THIRD time, and the first two are why the rule is
11
+ * a check rather than a sentence
12
+ *
13
+ * `cursedbelt/src/barrelsReachNoOptionalPeer.spec.ts` carries the full history; the short
14
+ * version is that `apps/collections` once imported a TYPE from `cursedbelt/server/storage`
15
+ * and paid for it with `error: Cannot find package 'otplib'` and four dead route suites,
16
+ * because the barrel reached `streamToken.ts` โ†’ the `../auth` index โ†’ `totp.ts` โ†’ an
17
+ * optional peer. The app installed a one-time-password library to satisfy a claim shape.
18
+ *
19
+ * When the server tier split into this package (task 148), the RUNTIME-FIXTURE half came
20
+ * with it as `leafSubpathsImportNothing.spec.ts` โ€” but that spec proves what a declared
21
+ * ZERO-RUNTIME LEAF pulls in, and a barrel is neither. So the barrel half stayed behind in
22
+ * `cursedbelt`, pointed at `./react`, and **this package shipped with no barrel check at
23
+ * all.**
24
+ *
25
+ * ๐Ÿ”ด It cost exactly what the first two cost, measured 2026-09-18 against the PUBLISHED
26
+ * `cursedbelt-server@4.3.0` tarball in a clean directory:
27
+ *
28
+ * $ bun add cursedbelt-server && bun -e 'import "cursedbelt-server/d1"'
29
+ * error: Cannot find package 'kysely' from โ€ฆ/src/server/d1/kysely.ts
30
+ *
31
+ * `./d1`'s index re-exported `./kysely`, which statically imports real VALUES from
32
+ * `kysely` (`Kysely`, `SqliteAdapter`, `SqliteQueryCompiler` โ€” not types, so they cannot be
33
+ * erased). `kysely` is an optional peer. So the D1 seam โ€” the thing the whole fleet is
34
+ * meant to port ONTO โ€” could not be imported by any app that had not already installed a
35
+ * query builder, which by `../db/kysely.ts`'s own header is most of them: *"Business /
36
+ * `data JSON` tables stay on raw `db.query()` โ€” do NOT add them here"*. The seam was
37
+ * unusable by precisely the apps it was built for.
38
+ *
39
+ * The fix was to give `createD1Kysely` its own subpath (`./d1/kysely`) and take it out of
40
+ * the barrel. This spec is what stops the fourth occurrence.
41
+ *
42
+ * ## What it measures
43
+ *
44
+ * Every subpath in the `exports` map is bundled with bare imports left external, and the
45
+ * remaining static specifiers are read out of the emitted JS. A DYNAMIC `await
46
+ * import('sharp')` inside the function that needs it matches neither branch of
47
+ * {@link STATIC_SPECIFIER} and must not โ€” that is the lazy shape this file exists to
48
+ * permit, not forbid.
49
+ *
50
+ * ## Verified failing before it was trusted (2026-09-18)
51
+ *
52
+ * ยท re-adding `export โ€ฆ from './kysely'` to `src/server/d1/index.ts`
53
+ * โ†’ red: "./d1 statically drags optional peer(s): kysely"
54
+ * ยท adding `import 'plainjob';` to `src/server/d1/local.ts` โ€” two hops out of the
55
+ * barrel, which grepping the index file cannot see
56
+ * โ†’ red identically. That is the branch that matters.
57
+ * ยท removing `'./jobs'` from {@link MAY_DRAG} โ†’ red, proving the allowlist is load-bearing
58
+ * rather than decorative.
59
+ */
60
+
61
+ const REPO = fileURLToPath(new URL('..', import.meta.url));
62
+
63
+ /**
64
+ * The subpaths whose whole PURPOSE is the optional peer they pull, mapped to what they are
65
+ * allowed to pull and why. An entry here is a promise that the peer is the point of the
66
+ * subpath โ€” not a place to park a new drag.
67
+ *
68
+ * ๐Ÿ”ด Measured 2026-09-18: these three are the ONLY subpaths in the map that reach an
69
+ * optional peer. Everything else is clean, which is what makes this list an allowlist
70
+ * rather than a baseline โ€” it can only shrink.
71
+ */
72
+ const MAY_DRAG: Record<string, readonly string[]> = {
73
+ // The whole-package barrel. It exists to re-export everything, so it necessarily reaches
74
+ // what the specific subpaths reach. An app importing `cursedbelt-server` whole is asking
75
+ // for that; an app importing `cursedbelt-server/d1` is not, and that is the distinction
76
+ // this spec protects.
77
+ '.': ['kysely', 'kysely-bun-sqlite', 'otplib', 'plainjob'],
78
+ // `createD1Kysely` IS the kysely adapter. Split out of `./d1` on 2026-09-18 precisely so
79
+ // the seam itself stops paying for it โ€” see the header.
80
+ './d1/kysely': ['kysely'],
81
+ // The job queue is plainjob. Nothing else here is.
82
+ './jobs': ['plainjob'],
83
+ };
84
+
85
+ const OPTIONAL_PEERS = new Set(
86
+ Object.entries(
87
+ (pkg as { peerDependenciesMeta?: Record<string, { optional?: boolean }> }).peerDependenciesMeta ?? {},
88
+ )
89
+ .filter(([, meta]) => meta?.optional === true)
90
+ .map(([name]) => name),
91
+ );
92
+
93
+ /**
94
+ * `import x from "pkg"` ยท `export โ€ฆ from "pkg"` ยท the side-effect-only `import "pkg";`.
95
+ *
96
+ * Anchored on the `from` clause rather than on a line starting with `import`, so a
97
+ * multi-line named import still counts โ€” bun emits `import {\n โ€ฆ \n} from "pkg";` and a
98
+ * line-anchored pattern silently misses it. A dynamic `import("pkg")` matches neither
99
+ * branch, which is deliberate.
100
+ */
101
+ const STATIC_SPECIFIER = /\bfrom\s*["']([^"'\n]+)["']|^\s*import\s*["']([^"'\n]+)["'];?\s*$/gm;
102
+
103
+ /** `@scope/name/deep` โ†’ `@scope/name`, `pkg/deep` โ†’ `pkg`. */
104
+ const packageOf = (specifier: string): string | undefined => {
105
+ const parts = specifier.split('/');
106
+ return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
107
+ };
108
+
109
+ /**
110
+ * Bundle one entry and return the bare packages it still imports.
111
+ *
112
+ * ๐Ÿ”ด A bundle that did not happen must never read as a subpath that pulls nothing, so a
113
+ * non-zero exit or an empty bundle THROWS rather than returning an empty set. That is the
114
+ * one way a check like this dies quietly.
115
+ */
116
+ const staticExternalsOf = (entry: string, subpath: string): Set<string> => {
117
+ const outdir = `${process.env.TMPDIR ?? '/tmp'}/cursedbelt-server-barrel-scan/${subpath.replace(/[^a-z0-9]+/gi, '-')}`;
118
+ const build = Bun.spawnSync(['bun', 'build', entry, '--target=bun', '--packages=external', '--outdir', outdir], {
119
+ cwd: REPO,
120
+ stdout: 'pipe',
121
+ stderr: 'pipe',
122
+ });
123
+ const emitted = [...new Bun.Glob('**/*.js').scanSync({ cwd: outdir, onlyFiles: true })];
124
+ const bundle = emitted.map((f) => readFileSync(`${outdir}/${f}`, 'utf8')).join('\n');
125
+ if (build.exitCode !== 0 || bundle.trim() === '') {
126
+ throw new Error(
127
+ `could not bundle ${subpath} (${entry}, exit ${build.exitCode}) โ€” a check that cannot measure is not a passing check:\n${build.stderr.toString()}`,
128
+ );
129
+ }
130
+ const found = new Set<string>();
131
+ for (const match of bundle.matchAll(STATIC_SPECIFIER)) {
132
+ const specifier = match[1] ?? match[2];
133
+ if (specifier === undefined || specifier.startsWith('.') || specifier.startsWith('bun:')) continue;
134
+ const name = packageOf(specifier);
135
+ if (name !== undefined) found.add(name);
136
+ }
137
+ return found;
138
+ };
139
+
140
+ /** Every subpath with a `source` entry โ€” the ones whose real graph can be measured. */
141
+ const SUBPATHS = Object.entries(pkg.exports as Record<string, { source?: string }>)
142
+ .filter(([, e]) => typeof e?.source === 'string' && /\.tsx?$/.test(e.source))
143
+ .map(([subpath, e]) => ({ subpath, entry: (e as { source: string }).source }));
144
+
145
+ describe('no public subpath statically drags an optional peer', () => {
146
+ it('measures a non-trivial number of subpaths, so a broken exports map cannot pass vacuously', () => {
147
+ expect(SUBPATHS.length).toBeGreaterThan(20);
148
+ expect(OPTIONAL_PEERS.size).toBeGreaterThan(0);
149
+ });
150
+
151
+ for (const { subpath, entry } of SUBPATHS) {
152
+ const allowed = MAY_DRAG[subpath] ?? [];
153
+ it(`${subpath} drags ${allowed.length === 0 ? 'no optional peer' : allowed.join(' + ') + ' and nothing more'}`, () => {
154
+ const dragged = [...staticExternalsOf(entry, subpath)].filter((n) => OPTIONAL_PEERS.has(n)).sort();
155
+ const unexpected = dragged.filter((n) => !allowed.includes(n));
156
+ expect(
157
+ unexpected,
158
+ `${subpath} statically drags optional peer(s): ${unexpected.join(', ')}\n` +
159
+ ` Every app importing '${pkg.name}${subpath.slice(1)}' must now install them or crash on import.\n` +
160
+ ` Give the adapter its own subpath and take it out of this barrel โ€” see ./d1/kysely, which is\n` +
161
+ ` exactly this fix applied on 2026-09-18 after the published 4.3.0 could not be imported at all.`,
162
+ ).toEqual([]);
163
+
164
+ // The allowlist may only shrink: an entry that no longer drags what it promised is
165
+ // a stale exception, and a stale exception is how a list like this stops meaning
166
+ // anything.
167
+ const stale = allowed.filter((n) => !dragged.includes(n));
168
+ expect(stale, `${subpath} is allowed to drag ${stale.join(', ')} but no longer does โ€” remove the exception`).toEqual(
169
+ [],
170
+ );
171
+ });
172
+ }
173
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `cursedbelt-server/analytics` โ€” where the fleet's web-analytics beacon is DEFINED, and the
3
+ * invariant that decides which responses may carry it.
4
+ *
5
+ * Two files, and the pairing is the point:
6
+ *
7
+ * ยท `webAnalytics.ts` is the one copy of the tag. An app's public shell pastes it; nothing
8
+ * generates ten of them.
9
+ * ยท `injectedScripts.ts` is the general rule โ€” nothing that arrives in a response's HTML may
10
+ * be refused by the CSP on that same response โ€” which is what makes "the wall never carries
11
+ * the beacon" a CHECK rather than a sentence. `../master-lock/beaconNeverReachesTheWall.spec.ts`
12
+ * is that check.
13
+ *
14
+ * Both are pure string work with no imports at all, so this subpath costs a consumer nothing
15
+ * and drags no peer.
16
+ */
17
+ export {
18
+ type InjectedScript,
19
+ foreignScriptSources,
20
+ scriptsRefusedByCsp,
21
+ } from "./injectedScripts";
22
+ export {
23
+ WEB_ANALYTICS_BEACON_ORIGIN,
24
+ WEB_ANALYTICS_BEACON_SRC,
25
+ WEB_ANALYTICS_SITE_TAG,
26
+ WEB_ANALYTICS_SITE_TOKEN,
27
+ WEB_ANALYTICS_TAG,
28
+ htmlCarriesWebAnalytics,
29
+ webAnalyticsTag,
30
+ } from "./webAnalytics";
@@ -0,0 +1,144 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { foreignScriptSources, scriptsRefusedByCsp } from "./injectedScripts";
3
+ import { WEB_ANALYTICS_BEACON_ORIGIN, WEB_ANALYTICS_TAG } from "./webAnalytics";
4
+
5
+ const PAGE = "https://collections.cursedalchemy.com/";
6
+
7
+ /**
8
+ * The tag Cloudflare's Automatic Setup appended, byte-for-byte as the zone emitted it on
9
+ * 2026-09-18 โ€” pinned path, integrity hash, `crossorigin` and all. It is kept in its
10
+ * AUTOMATIC spelling on purpose: this is the shape that was arriving unasked-for, and a
11
+ * checker that only recognised the manual snippet would go quiet the moment somebody turned
12
+ * auto-install back on.
13
+ */
14
+ const BEACON =
15
+ `<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495"` +
16
+ ` integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg=="` +
17
+ ` data-cf-beacon='{"version":"2024.11.0","token":"bd88d63678b64bc89f9702f48846bc88","r":1,"spa":2}' crossorigin="anonymous"></script>`;
18
+
19
+ /** A real app shell: one inline script, one same-origin module. Neither is foreign. */
20
+ const SHELL = `<!doctype html><html><head>
21
+ <script>try { document.documentElement.classList.toggle("dark", true); } catch (e) {}</script>
22
+ <script type="module" crossorigin src="/assets/index-edXXbfEe.js"></script>
23
+ </head><body><div id="root"></div></body></html>`;
24
+
25
+ describe("foreignScriptSources", () => {
26
+ test("an app's own shell has none โ€” inline and same-origin are not findings", () => {
27
+ expect(foreignScriptSources(SHELL, PAGE)).toEqual([]);
28
+ });
29
+
30
+ test("finds the beacon the edge appended, single-quoted attribute and all", () => {
31
+ const found = foreignScriptSources(SHELL.replace("</head>", `${BEACON}</head>`), PAGE);
32
+ expect(found).toHaveLength(1);
33
+ expect(found[0]?.origin).toBe(WEB_ANALYTICS_BEACON_ORIGIN);
34
+ });
35
+
36
+ test("the MANUAL snippet is found too, and names the same origin", () => {
37
+ const found = foreignScriptSources(SHELL.replace("</head>", `${WEB_ANALYTICS_TAG}</head>`), PAGE);
38
+ expect(found).toHaveLength(1);
39
+ expect(found[0]?.origin).toBe(WEB_ANALYTICS_BEACON_ORIGIN);
40
+ });
41
+
42
+ test("an absolute URL back to this app's own origin is not foreign", () => {
43
+ const html = `<script src="https://collections.cursedalchemy.com/assets/x.js"></script>`;
44
+ expect(foreignScriptSources(html, PAGE)).toEqual([]);
45
+ });
46
+
47
+ test("data: and blob: sources are skipped โ€” no host was injected", () => {
48
+ const html = `<script src="data:text/javascript,void 0"></script><script src=blob:https://x/y></script>`;
49
+ expect(foreignScriptSources(html, PAGE)).toEqual([]);
50
+ });
51
+
52
+ test("two tags naming the same foreign origin are reported once per src", () => {
53
+ const html = `${BEACON}${BEACON}<script src="https://static.cloudflareinsights.com/other.js"></script>`;
54
+ expect(foreignScriptSources(html, PAGE)).toHaveLength(2);
55
+ });
56
+ });
57
+
58
+ describe("scriptsRefusedByCsp", () => {
59
+ const beacon = foreignScriptSources(BEACON, PAGE);
60
+
61
+ /**
62
+ * ๐Ÿ”ด The failure path this whole file exists for โ€” the owner's console line, turned into a
63
+ * refusal. `script-src 'self'` with something injecting is exactly what he pasted.
64
+ */
65
+ test("script-src 'self' refuses the injected beacon and names its origin", () => {
66
+ const refused = scriptsRefusedByCsp(
67
+ "default-src 'none'; style-src 'self'; script-src 'self'; connect-src 'self'; " +
68
+ "img-src data:; form-action 'none'; base-uri 'none'; frame-ancestors 'none'",
69
+ beacon,
70
+ PAGE,
71
+ );
72
+ expect(refused.map((s) => s.origin)).toEqual([WEB_ANALYTICS_BEACON_ORIGIN]);
73
+ });
74
+
75
+ test("allowing the host in script-src clears it", () => {
76
+ const csp = "default-src 'none'; script-src 'self' https://static.cloudflareinsights.com";
77
+ expect(scriptsRefusedByCsp(csp, beacon, PAGE)).toEqual([]);
78
+ });
79
+
80
+ test("a wildcard subdomain source clears it", () => {
81
+ expect(scriptsRefusedByCsp("script-src 'self' *.cloudflareinsights.com", beacon, PAGE)).toEqual(
82
+ [],
83
+ );
84
+ });
85
+
86
+ test("no CSP header at all refuses nothing โ€” today's public shell", () => {
87
+ expect(scriptsRefusedByCsp(null, beacon, PAGE)).toEqual([]);
88
+ expect(scriptsRefusedByCsp("", beacon, PAGE)).toEqual([]);
89
+ });
90
+
91
+ test("a CSP with no script directive and no default-src refuses nothing", () => {
92
+ expect(scriptsRefusedByCsp("frame-ancestors 'none'; base-uri 'none'", beacon, PAGE)).toEqual([]);
93
+ });
94
+
95
+ test("default-src is the fallback when script-src is absent", () => {
96
+ expect(scriptsRefusedByCsp("default-src 'self'", beacon, PAGE)).toHaveLength(1);
97
+ expect(scriptsRefusedByCsp("default-src https:", beacon, PAGE)).toEqual([]);
98
+ });
99
+
100
+ test("script-src-elem wins over script-src, which wins over default-src", () => {
101
+ const csp =
102
+ "default-src 'none'; script-src 'self'; script-src-elem https://static.cloudflareinsights.com";
103
+ expect(scriptsRefusedByCsp(csp, beacon, PAGE)).toEqual([]);
104
+ expect(scriptsRefusedByCsp("default-src https:; script-src 'self'", beacon, PAGE)).toHaveLength(
105
+ 1,
106
+ );
107
+ });
108
+
109
+ test("'none' admits nothing, even alongside a scheme in another directive", () => {
110
+ expect(scriptsRefusedByCsp("script-src 'none'", beacon, PAGE)).toHaveLength(1);
111
+ });
112
+
113
+ test("a scheme source only matches its own scheme", () => {
114
+ expect(scriptsRefusedByCsp("script-src http:", beacon, PAGE)).toHaveLength(1);
115
+ expect(scriptsRefusedByCsp("script-src https:", beacon, PAGE)).toEqual([]);
116
+ });
117
+
118
+ test("an http:// host source does not admit an https origin", () => {
119
+ expect(
120
+ scriptsRefusedByCsp("script-src http://static.cloudflareinsights.com", beacon, PAGE),
121
+ ).toHaveLength(1);
122
+ });
123
+
124
+ test("a port or path on the source narrows nothing โ€” the conservative direction", () => {
125
+ const csp = "script-src https://static.cloudflareinsights.com:443/beacon.min.js";
126
+ expect(scriptsRefusedByCsp(csp, beacon, PAGE)).toEqual([]);
127
+ });
128
+
129
+ test("'strict-dynamic' is undecidable from markup, so it permits", () => {
130
+ expect(scriptsRefusedByCsp("script-src 'self' 'strict-dynamic' 'nonce-abc'", beacon, PAGE)).toEqual(
131
+ [],
132
+ );
133
+ });
134
+
135
+ test("* permits, and 'self' still matches this app's own origin", () => {
136
+ expect(scriptsRefusedByCsp("script-src *", beacon, PAGE)).toEqual([]);
137
+ const own = [{ src: "/assets/x.js", origin: "https://collections.cursedalchemy.com" }];
138
+ expect(scriptsRefusedByCsp("script-src 'self'", own, PAGE)).toEqual([]);
139
+ });
140
+
141
+ test("nothing injected means nothing refused, however strict the policy", () => {
142
+ expect(scriptsRefusedByCsp("script-src 'none'", [], PAGE)).toEqual([]);
143
+ });
144
+ });
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Does a page's own CSP refuse a script that arrived in that page's own HTML?
3
+ *
4
+ * ## The invariant, which names no vendor
5
+ *
6
+ * > No script that reaches the browser inside a response's HTML may be refused by the CSP on
7
+ * > that same response.
8
+ *
9
+ * Every part of the arrangement may move and this stays right. Turn an edge injection off and
10
+ * the foreign scripts disappear โ€” green. Decide a third-party script is wanted and allow its
11
+ * host โ€” green. Harden a page with `script-src 'self'` while something is still injecting into
12
+ * it โ€” RED, and it names the origin, which is the console line the owner would otherwise find
13
+ * himself during an incident.
14
+ *
15
+ * ## ๐Ÿ”ด Why it lives in this package
16
+ *
17
+ * It was written in `apps/collections` on 2026-09-18 and it was right there, but it could only
18
+ * ever see one app โ€” and the response it was written about, the master-lock wall, is served by
19
+ * THIS package to every app that mounts the guard. One copy here is what makes the two halves
20
+ * of `webAnalytics.ts`'s decision checkable in the same place: the wall refuses the beacon, and
21
+ * a public shell carries it.
22
+ *
23
+ * ## ๐Ÿ”ด Conservative on purpose
24
+ *
25
+ * Every ambiguity resolves to PERMITTED. A source expression this file cannot parse, a
26
+ * `'strict-dynamic'` that changes what host lists even mean, a CSP with no script directive at
27
+ * all โ€” all of them pass. A deployed smoke decides whether an app's `scripts/deploy.ts` rolls
28
+ * back; a false red there discards good work over a CSP grammar corner, so the only thing that
29
+ * fails here is a script with NO source expression that could admit it.
30
+ */
31
+
32
+ /** An absolute script URL found in the HTML, with the origin already resolved. */
33
+ export interface InjectedScript {
34
+ /** Exactly as it appeared in the markup, for the failure message. */
35
+ src: string;
36
+ /** `https://static.cloudflareinsights.com`, resolved against the page's own URL. */
37
+ origin: string;
38
+ }
39
+
40
+ /**
41
+ * Every `<script src=โ€ฆ>` in `html` whose origin is NOT `pageUrl`'s.
42
+ *
43
+ * Inline scripts are ignored: they are the page's own (most shells here ship one, a pre-paint
44
+ * colour-scheme script) and a CSP that refuses them is a different finding from this one.
45
+ * `data:` and `blob:` are ignored for the same reason โ€” neither is a host anything could have
46
+ * injected.
47
+ */
48
+ export function foreignScriptSources(html: string, pageUrl: string): InjectedScript[] {
49
+ const page = new URL(pageUrl);
50
+ const found: InjectedScript[] = [];
51
+ const seen = new Set<string>();
52
+ const tag = /<script\b[^>]*?\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/gi;
53
+ for (const match of html.matchAll(tag)) {
54
+ const raw = (match[1] ?? match[2] ?? match[3] ?? "").trim();
55
+ if (!raw) continue;
56
+ let origin: string;
57
+ try {
58
+ const url = new URL(raw, page);
59
+ if (url.protocol !== "http:" && url.protocol !== "https:") continue;
60
+ origin = url.origin;
61
+ } catch {
62
+ continue; // not a URL this check can reason about โ€” see the conservative note
63
+ }
64
+ if (origin === page.origin) continue;
65
+ if (seen.has(origin + raw)) continue;
66
+ seen.add(origin + raw);
67
+ found.push({ src: raw, origin });
68
+ }
69
+ return found;
70
+ }
71
+
72
+ /**
73
+ * Which of `scripts` the `content-security-policy` header would refuse.
74
+ *
75
+ * `null`/absent/empty header means no policy, which refuses nothing. The directive consulted
76
+ * is `script-src-elem`, then `script-src`, then `default-src` โ€” the browser's own fallback
77
+ * order for a `<script src>` element.
78
+ */
79
+ export function scriptsRefusedByCsp(
80
+ csp: string | null | undefined,
81
+ scripts: InjectedScript[],
82
+ pageUrl: string,
83
+ ): InjectedScript[] {
84
+ if (!csp?.trim() || scripts.length === 0) return [];
85
+ const directives = new Map<string, string[]>();
86
+ for (const part of csp.split(";")) {
87
+ const tokens = part.trim().split(/\s+/).filter(Boolean);
88
+ const name = tokens.shift()?.toLowerCase();
89
+ if (name && !directives.has(name)) directives.set(name, tokens);
90
+ }
91
+ const sources =
92
+ directives.get("script-src-elem") ??
93
+ directives.get("script-src") ??
94
+ directives.get("default-src");
95
+ if (!sources) return [];
96
+ // `'strict-dynamic'` makes host allowlists inert in ways that depend on how the script was
97
+ // reached. Not decidable from the markup alone, so it is permitted โ€” see the header.
98
+ if (sources.some((s) => s.toLowerCase() === "'strict-dynamic'")) return [];
99
+ const self = new URL(pageUrl).origin;
100
+ return scripts.filter(
101
+ (script) => !sources.some((source) => sourceAdmits(source, script.origin, self)),
102
+ );
103
+ }
104
+
105
+ /** Does one CSP source expression admit `origin`? Unparseable โ‡’ yes, deliberately. */
106
+ function sourceAdmits(source: string, origin: string, self: string): boolean {
107
+ const value = source.trim();
108
+ if (!value) return false;
109
+ if (value === "*") return true;
110
+ if (value.startsWith("'")) {
111
+ const keyword = value.toLowerCase();
112
+ if (keyword === "'self'") return origin === self;
113
+ // `'none'`, `'unsafe-inline'`, `'nonce-โ€ฆ'`, `'sha256-โ€ฆ'`, `'report-sample'`: none of
114
+ // them admits a host, and `'none'` in particular must not.
115
+ return false;
116
+ }
117
+ let target: URL;
118
+ try {
119
+ target = new URL(origin);
120
+ } catch {
121
+ return true;
122
+ }
123
+ // A bare scheme source โ€” `https:`, `data:`.
124
+ if (/^[a-z][a-z0-9+.-]*:$/i.test(value)) return value.toLowerCase() === target.protocol;
125
+
126
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(value);
127
+ let rest = value;
128
+ if (withScheme) {
129
+ if (withScheme[0].toLowerCase() !== `${target.protocol}//`) return false;
130
+ rest = value.slice(withScheme[0].length);
131
+ }
132
+ // Port and path narrow a source; ignoring them can only ever ADMIT more, which is the
133
+ // direction this file errs in.
134
+ const host = rest.split("/")[0]?.split(":")[0]?.toLowerCase() ?? "";
135
+ if (!host) return true;
136
+ if (host === "*") return true;
137
+ if (host.startsWith("*.")) return target.hostname.toLowerCase().endsWith(host.slice(1));
138
+ return target.hostname.toLowerCase() === host;
139
+ }
@@ -0,0 +1,68 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ WEB_ANALYTICS_BEACON_ORIGIN,
4
+ WEB_ANALYTICS_BEACON_SRC,
5
+ WEB_ANALYTICS_SITE_TAG,
6
+ WEB_ANALYTICS_SITE_TOKEN,
7
+ WEB_ANALYTICS_TAG,
8
+ htmlCarriesWebAnalytics,
9
+ webAnalyticsTag,
10
+ } from "./webAnalytics";
11
+
12
+ /**
13
+ * ๐Ÿ”ด The snippet, as Cloudflare's own `GET /accounts/<acct>/rum/site_info/list` returned it on
14
+ * 2026-09-18 for `cursedalchemy.com`, unescaped. Pinning it byte-for-byte is the point: this
15
+ * is the thing a shell pastes, and the day it drifts from the vendor's text is a day nobody
16
+ * would otherwise notice until a dashboard stopped filling.
17
+ */
18
+ const FROM_CLOUDFLARE =
19
+ `<!-- Cloudflare Web Analytics --><script type='module' src='https://static.cloudflareinsights.com/beacon.min.js' ` +
20
+ `data-cf-beacon='{"token": "bd88d63678b64bc89f9702f48846bc88"}'></script><!-- End Cloudflare Web Analytics -->`;
21
+
22
+ describe("the fleet's beacon", () => {
23
+ test("is exactly the snippet Cloudflare hands out for this site", () => {
24
+ expect(WEB_ANALYTICS_TAG).toBe(FROM_CLOUDFLARE);
25
+ });
26
+
27
+ test("carries the site TOKEN, never the site TAG", () => {
28
+ // Both are 32 hex characters and swapping them errors nowhere โ€” the page loads, the
29
+ // beacon loads, and the pageview is recorded against nothing.
30
+ expect(WEB_ANALYTICS_TAG).toContain(WEB_ANALYTICS_SITE_TOKEN);
31
+ expect(WEB_ANALYTICS_TAG).not.toContain(WEB_ANALYTICS_SITE_TAG);
32
+ expect(WEB_ANALYTICS_SITE_TAG).not.toBe(WEB_ANALYTICS_SITE_TOKEN);
33
+ });
34
+
35
+ test("the src is under the origin a CSP would have to admit", () => {
36
+ expect(WEB_ANALYTICS_BEACON_SRC.startsWith(`${WEB_ANALYTICS_BEACON_ORIGIN}/`)).toBe(true);
37
+ expect(new URL(WEB_ANALYTICS_BEACON_SRC).origin).toBe(WEB_ANALYTICS_BEACON_ORIGIN);
38
+ });
39
+
40
+ test("another site's token produces that site's tag and nothing else changes", () => {
41
+ const other = webAnalyticsTag("0".repeat(32));
42
+ expect(other).toContain(WEB_ANALYTICS_BEACON_SRC);
43
+ expect(other).not.toContain(WEB_ANALYTICS_SITE_TOKEN);
44
+ });
45
+ });
46
+
47
+ describe("htmlCarriesWebAnalytics", () => {
48
+ test("true for the tag as authored, and for a re-wrapped copy of it", () => {
49
+ expect(htmlCarriesWebAnalytics(`<head>${WEB_ANALYTICS_TAG}</head>`)).toBe(true);
50
+ // A formatter is allowed to break the attributes across lines; the check must survive it.
51
+ const rewrapped =
52
+ `<script\n\ttype="module"\n\tsrc="${WEB_ANALYTICS_BEACON_SRC}"\n` +
53
+ `\tdata-cf-beacon='{"token": "${WEB_ANALYTICS_SITE_TOKEN}"}'\n></script>`;
54
+ expect(htmlCarriesWebAnalytics(rewrapped)).toBe(true);
55
+ });
56
+
57
+ test("false for a shell with no beacon at all", () => {
58
+ expect(htmlCarriesWebAnalytics("<head><title>Vault</title></head>")).toBe(false);
59
+ });
60
+
61
+ test("false when the src is right but the token is somebody else's", () => {
62
+ expect(htmlCarriesWebAnalytics(webAnalyticsTag("0".repeat(32)))).toBe(false);
63
+ });
64
+
65
+ test("false when the token is right but the src is not the beacon", () => {
66
+ expect(htmlCarriesWebAnalytics(`<!-- ${WEB_ANALYTICS_SITE_TOKEN} -->`)).toBe(false);
67
+ });
68
+ });
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Cloudflare Web Analytics โ€” ONE definition of the beacon, for every app on the fleet.
3
+ *
4
+ * ## ๐Ÿ”ด Why this is here and not in each app's `index.html` alone
5
+ *
6
+ * Until 2026-09-18 the zone carried **Automatic Setup**: Cloudflare rewrote every HTML
7
+ * response leaving `cursedalchemy.com` and appended the beacon tag. Nothing in any repo
8
+ * asked for it, nothing in any repo could see it locally, and it arrived on pages that
9
+ * deliberately refuse third-party script โ€” including the master-lock wall, where a master
10
+ * password is typed. The owner read a console during an outage on 2026-09-17 and the FIRST
11
+ * line was that tag being refused; it broke nothing and cost an agent the first minutes of
12
+ * the incident.
13
+ *
14
+ * His decision, 2026-09-18, in his words: *"do your recommendation with option c but not on
15
+ * the wall. If we can stop the console error that would be good since we are purposely
16
+ * blocking on that page and it makes it look like there is a mistake when it shows."* โ€” and,
17
+ * on why the wall's CSP may not simply be loosened: *"I don't want to trust cloudflare with
18
+ * master password data that is typed in."*
19
+ *
20
+ * So the install method moves from automatic to MANUAL. Same site, same dashboard, same
21
+ * data; the difference is that the fleet now decides *which pages* carry it. Public shells
22
+ * do. {@link "../master-lock/guard".MASTER_LOCK_CSP the wall} does not โ€” and under manual
23
+ * setup it is not injected into at all, so there is no refused script and no console line.
24
+ *
25
+ * ## ๐Ÿ”ด `token` is the site TOKEN, not the site TAG
26
+ *
27
+ * They are both 32 hex characters and they are not interchangeable. `site_tag`
28
+ * ({@link WEB_ANALYTICS_SITE_TAG}) is the id the RUM API addresses the site by;
29
+ * `site_token` ({@link WEB_ANALYTICS_SITE_TOKEN}) is what goes in `data-cf-beacon`. Putting
30
+ * the tag in the snippet does not error anywhere โ€” the page loads, the beacon loads, and the
31
+ * data goes nowhere. Measured 2026-09-18 against
32
+ * `GET /accounts/<acct>/rum/site_info/list`, which returns both fields **and** the ready-made
33
+ * `snippet`; {@link WEB_ANALYTICS_TAG} is that snippet byte-for-byte, and the token in it is
34
+ * the same one the edge was injecting.
35
+ *
36
+ * Neither value is a secret: the token is served to every visitor inside every page's HTML,
37
+ * which is the whole mechanism. It is an identifier, not a credential.
38
+ */
39
+
40
+ /** The host the beacon is fetched from โ€” the origin a CSP would have to admit. */
41
+ export const WEB_ANALYTICS_BEACON_ORIGIN = "https://static.cloudflareinsights.com";
42
+
43
+ /** The beacon itself. Manual setup serves the unversioned path; the edge served a pinned one. */
44
+ export const WEB_ANALYTICS_BEACON_SRC = `${WEB_ANALYTICS_BEACON_ORIGIN}/beacon.min.js`;
45
+
46
+ /**
47
+ * The `cursedalchemy.com` site's id in the RUM API. **Not** what goes in the tag โ€” see the
48
+ * module note. Kept beside the token so the next reader cannot pick the wrong one by accident.
49
+ */
50
+ export const WEB_ANALYTICS_SITE_TAG = "d1bea8562544421fbd2db5f11b74d1cc";
51
+
52
+ /** The `cursedalchemy.com` site's `data-cf-beacon` token. This is what the tag carries. */
53
+ export const WEB_ANALYTICS_SITE_TOKEN = "bd88d63678b64bc89f9702f48846bc88";
54
+
55
+ /**
56
+ * The snippet, exactly as Cloudflare's own `site_info` endpoint spells it.
57
+ *
58
+ * ๐Ÿ”ด The single-quoted attributes and the space after `"token":` are Cloudflare's, not a
59
+ * style choice, and they are kept because this string is COMPARED against what an app's
60
+ * shell contains. `data-cf-beacon`'s value is JSON containing double quotes, so the attribute
61
+ * quoting cannot be flipped without escaping; leaving it identical to the vendor's text means
62
+ * a future reader can diff it against the dashboard in one glance.
63
+ *
64
+ * `type='module'` rather than `defer`: that is what the endpoint returns today and what the
65
+ * edge was injecting, so switching install methods changes nothing about how the page loads.
66
+ */
67
+ export function webAnalyticsTag(token: string = WEB_ANALYTICS_SITE_TOKEN): string {
68
+ return (
69
+ "<!-- Cloudflare Web Analytics -->" +
70
+ `<script type='module' src='${WEB_ANALYTICS_BEACON_SRC}' ` +
71
+ `data-cf-beacon='{"token": "${token}"}'></script>` +
72
+ "<!-- End Cloudflare Web Analytics -->"
73
+ );
74
+ }
75
+
76
+ /** The fleet's tag, for the one site this generation serves. */
77
+ export const WEB_ANALYTICS_TAG = webAnalyticsTag();
78
+
79
+ /**
80
+ * Does `html` carry the beacon for this site?
81
+ *
82
+ * Deliberately NOT a substring test against {@link WEB_ANALYTICS_TAG}: a shell is authored by
83
+ * hand and a formatter is allowed to re-wrap the tag across lines or re-order its attributes.
84
+ * What must be true is that the beacon's source is there AND that it is carrying the right
85
+ * token โ€” the two halves that decide whether a pageview is recorded at all.
86
+ */
87
+ export function htmlCarriesWebAnalytics(
88
+ html: string,
89
+ token: string = WEB_ANALYTICS_SITE_TOKEN,
90
+ ): boolean {
91
+ return html.includes(WEB_ANALYTICS_BEACON_SRC) && html.includes(token);
92
+ }
@@ -25,7 +25,13 @@ export {
25
25
  type LocalBackupOpts,
26
26
  type TimeTravelOpts,
27
27
  } from './backup';
28
- export { createD1Kysely, D1LikeDialect, type D1PlumbingDb } from './kysely';
28
+ // ๐Ÿ”ด `./kysely` is deliberately NOT re-exported here โ€” import it from
29
+ // `cursedbelt-server/d1/kysely`. It statically imports `kysely` (real values: `Kysely`,
30
+ // `SqliteAdapter`, `SqliteQueryCompiler`), which is an OPTIONAL peer, so re-exporting it
31
+ // made `import 'cursedbelt-server/d1'` throw `Cannot find package 'kysely'` for every app
32
+ // that does not use the query builder โ€” which per `../db/kysely.ts`'s own header is most
33
+ // of them, since business tables stay on raw statements. Measured 2026-09-18 against the
34
+ // published 4.3.0 tarball. `barrelsReachNoOptionalPeer.spec.ts` is what keeps it out.
29
35
  export { type InvocationD1, perInvocation } from './invocation';
30
36
  export { assertBatchSize, assertWithinLimits, chunkForBind, LIMITS } from './limits';
31
37
  export { createLocalD1, refuseInteractiveTransaction } from './local';