@fuzdev/fuz_app 0.95.0 → 0.97.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.
Files changed (51) hide show
  1. package/dist/auth/CLAUDE.md +1 -1
  2. package/dist/auth/account_queries.d.ts +20 -5
  3. package/dist/auth/account_queries.d.ts.map +1 -1
  4. package/dist/auth/account_queries.js +21 -4
  5. package/dist/auth/bearer_auth.d.ts.map +1 -1
  6. package/dist/auth/bearer_auth.js +6 -7
  7. package/dist/auth/daemon_token_middleware.d.ts +21 -4
  8. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  9. package/dist/auth/daemon_token_middleware.js +61 -10
  10. package/dist/auth/middleware.d.ts.map +1 -1
  11. package/dist/auth/middleware.js +6 -2
  12. package/dist/auth/request_context.js +2 -2
  13. package/dist/db/CLAUDE.md +6 -3
  14. package/dist/db/cell_queries.js +8 -1
  15. package/dist/db/create_db.d.ts.map +1 -1
  16. package/dist/db/create_db.js +2 -14
  17. package/dist/db/db_pg.d.ts +30 -2
  18. package/dist/db/db_pg.d.ts.map +1 -1
  19. package/dist/db/db_pg.js +33 -2
  20. package/dist/db/fact_queries.d.ts.map +1 -1
  21. package/dist/http/CLAUDE.md +3 -2
  22. package/dist/http/error_schemas.d.ts +0 -6
  23. package/dist/http/error_schemas.d.ts.map +1 -1
  24. package/dist/http/error_schemas.js +0 -6
  25. package/dist/http/origin.d.ts +18 -1
  26. package/dist/http/origin.d.ts.map +1 -1
  27. package/dist/http/origin.js +17 -0
  28. package/dist/server/serve_fact_route.d.ts +12 -0
  29. package/dist/server/serve_fact_route.d.ts.map +1 -1
  30. package/dist/server/serve_fact_route.js +40 -0
  31. package/dist/testing/CLAUDE.md +23 -3
  32. package/dist/testing/audit_completeness.d.ts.map +1 -1
  33. package/dist/testing/audit_completeness.js +4 -1
  34. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -1
  35. package/dist/testing/cross_backend/bootstrap_backend.js +8 -0
  36. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  37. package/dist/testing/cross_backend/cell_crud.js +23 -0
  38. package/dist/testing/cross_backend/conformance_case.d.ts +40 -0
  39. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  40. package/dist/testing/cross_backend/conformance_case.js +34 -0
  41. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  42. package/dist/testing/cross_backend/conformance_table.js +58 -0
  43. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  44. package/dist/testing/cross_backend/fact_serving.js +5 -1
  45. package/dist/testing/cross_backend/setup.d.ts +17 -0
  46. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  47. package/dist/testing/cross_backend/setup.js +29 -4
  48. package/dist/testing/db.d.ts.map +1 -1
  49. package/dist/testing/db.js +6 -1
  50. package/dist/testing/middleware.js +4 -4
  51. package/package.json +6 -2
@@ -46,8 +46,6 @@ export const ERROR_PAYLOAD_TOO_LARGE = 'payload_too_large';
46
46
  // --- Origin & bearer token verification ---
47
47
  /** Request origin not in allowlist. */
48
48
  export const ERROR_FORBIDDEN_ORIGIN = 'forbidden_origin';
49
- /** Bearer token sent with Origin/Referer header (browser context). */
50
- export const ERROR_BEARER_REJECTED_BROWSER = 'bearer_token_rejected_in_browser_context';
51
49
  /** Bearer token failed validation (missing, malformed, or revoked). */
52
50
  export const ERROR_INVALID_TOKEN = 'invalid_token';
53
51
  /** Token references a deleted account. */
@@ -86,10 +84,6 @@ export const ERROR_NO_ACTORS_ON_ACCOUNT = 'no_actors_on_account';
86
84
  */
87
85
  export const ERROR_ACCOUNT_VANISHED = 'account_vanished';
88
86
  // --- Keeper / daemon token ---
89
- /** Daemon token header present but malformed or not matching current/previous token. */
90
- export const ERROR_INVALID_DAEMON_TOKEN = 'invalid_daemon_token';
91
- /** Daemon token valid but keeper account not yet resolved (pre-bootstrap). */
92
- export const ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED = 'keeper_account_not_configured';
93
87
  /** Keeper account ID set but account row not found. */
94
88
  export const ERROR_KEEPER_ACCOUNT_NOT_FOUND = 'keeper_account_not_found';
95
89
  // --- Bootstrap ---
@@ -9,7 +9,24 @@
9
9
  *
10
10
  * @module
11
11
  */
12
- import type { Handler } from 'hono';
12
+ import type { Context, Handler } from 'hono';
13
+ /**
14
+ * True when the request looks like it originated from a browser context — it
15
+ * carries an `Origin` or `Referer` header. Browsers attach these
16
+ * automatically on the state-changing surface; loopback / CLI clients don't.
17
+ * Uses `!== undefined` so an empty-string header still counts as browser
18
+ * context. Checks `Referer` too (not just `Origin`) because some browser
19
+ * requests send only `Referer`. Twin of the Rust spine's
20
+ * `bearer_auth::is_browser_context`.
21
+ *
22
+ * The bearer (`auth/bearer_auth.ts`) and daemon-token
23
+ * (`auth/daemon_token_middleware.ts`) middleware both silently discard their
24
+ * credential when this is true: a stolen bearer token can't be replayed from a
25
+ * browser, and the loopback daemon token never legitimately carries an
26
+ * `Origin`. Distinct from `verify_request_source` — that gates browser
27
+ * requests against an allowlist; this only detects browser context.
28
+ */
29
+ export declare const is_browser_context: (c: Context) => boolean;
13
30
  /**
14
31
  * Parses FUZ_ALLOWED_ORIGINS env var into regex matchers for request source verification.
15
32
  * Origin allowlisting for locally-running services — not the CSRF layer
@@ -1 +1 @@
1
- {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAIlC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,KAAK,CAAC,MAAM,CAO5E,CAAC;AAEP;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC/B,QAAQ,MAAM,EACd,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACrC,OAAuD,CAAC;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GAChC,kBAAkB,aAAa,CAAC,MAAM,CAAC,KAAG,OAgB1C,CAAC"}
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAC,OAAO,EAAE,OAAO,EAAC,MAAM,MAAM,CAAC;AAI3C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,OAAO,KAAG,OAC8B,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,KAAK,CAAC,MAAM,CAO5E,CAAC;AAEP;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC/B,QAAQ,MAAM,EACd,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACrC,OAAuD,CAAC;AAE3D;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,qBAAqB,GAChC,kBAAkB,aAAa,CAAC,MAAM,CAAC,KAAG,OAgB1C,CAAC"}
@@ -11,6 +11,23 @@
11
11
  */
12
12
  import { escape_regexp } from '@fuzdev/fuz_util/regexp.ts';
13
13
  import { ERROR_FORBIDDEN_ORIGIN } from './error_schemas.js';
14
+ /**
15
+ * True when the request looks like it originated from a browser context — it
16
+ * carries an `Origin` or `Referer` header. Browsers attach these
17
+ * automatically on the state-changing surface; loopback / CLI clients don't.
18
+ * Uses `!== undefined` so an empty-string header still counts as browser
19
+ * context. Checks `Referer` too (not just `Origin`) because some browser
20
+ * requests send only `Referer`. Twin of the Rust spine's
21
+ * `bearer_auth::is_browser_context`.
22
+ *
23
+ * The bearer (`auth/bearer_auth.ts`) and daemon-token
24
+ * (`auth/daemon_token_middleware.ts`) middleware both silently discard their
25
+ * credential when this is true: a stolen bearer token can't be replayed from a
26
+ * browser, and the loopback daemon token never legitimately carries an
27
+ * `Origin`. Distinct from `verify_request_source` — that gates browser
28
+ * requests against an allowlist; this only detects browser context.
29
+ */
30
+ export const is_browser_context = (c) => c.req.header('Origin') !== undefined || c.req.header('Referer') !== undefined;
14
31
  /**
15
32
  * Parses FUZ_ALLOWED_ORIGINS env var into regex matchers for request source verification.
16
33
  * Origin allowlisting for locally-running services — not the CSRF layer
@@ -63,6 +63,18 @@
63
63
  * is the doc cell, so serving goes view-doc-cell → doc-cell-refs-include-hash
64
64
  * → serve.
65
65
  *
66
+ * ## Untrusted-content hardening
67
+ *
68
+ * Fact bytes are served with a producer-supplied `content_type` emitted
69
+ * verbatim, inline. To stop a fact stored as (or sniffable to) `text/html`
70
+ * from executing as stored XSS for any reader of a referencing cell, **every**
71
+ * served-fact response carries `X-Content-Type-Options: nosniff` +
72
+ * `Content-Security-Policy: default-src 'none'; sandbox` (the
73
+ * `FACT_SECURITY_HEADERS` set, applied in all three serve branches). The same
74
+ * pair, byte-identical, is set by the Rust twin (`fuz_fact_serving::serve`).
75
+ * `Content-Disposition: attachment` is deliberately **not** added — it would
76
+ * force-download and break legitimate inline image rendering.
77
+ *
66
78
  * ## Defense-in-depth
67
79
  *
68
80
  * The `external_url` regex is re-validated before issuing
@@ -1 +1 @@
1
- {"version":3,"file":"serve_fact_route.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/serve_fact_route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AAMH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAepD,OAAO,EAAsC,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAE1F,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAsC7C,MAAM,WAAW,+BAA+B;IAC/C;;;;;;OAMG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;CACZ;AAwGD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,SAAS,+BAA+B,KACtC,SA8CF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,SAgCF,CAAC"}
1
+ {"version":3,"file":"serve_fact_route.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/serve_fact_route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AAMH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAepD,OAAO,EAAsC,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAE1F,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAkE7C,MAAM,WAAW,+BAA+B;IAC/C;;;;;;OAMG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;CACZ;AA2GD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,SAAS,+BAA+B,KACtC,SA8CF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,SAgCF,CAAC"}
@@ -63,6 +63,18 @@
63
63
  * is the doc cell, so serving goes view-doc-cell → doc-cell-refs-include-hash
64
64
  * → serve.
65
65
  *
66
+ * ## Untrusted-content hardening
67
+ *
68
+ * Fact bytes are served with a producer-supplied `content_type` emitted
69
+ * verbatim, inline. To stop a fact stored as (or sniffable to) `text/html`
70
+ * from executing as stored XSS for any reader of a referencing cell, **every**
71
+ * served-fact response carries `X-Content-Type-Options: nosniff` +
72
+ * `Content-Security-Policy: default-src 'none'; sandbox` (the
73
+ * `FACT_SECURITY_HEADERS` set, applied in all three serve branches). The same
74
+ * pair, byte-identical, is set by the Rust twin (`fuz_fact_serving::serve`).
75
+ * `Content-Disposition: attachment` is deliberately **not** added — it would
76
+ * force-download and break legitimate inline image rendering.
77
+ *
66
78
  * ## Defense-in-depth
67
79
  *
68
80
  * The `external_url` regex is re-validated before issuing
@@ -92,6 +104,31 @@ import { can_view_cell } from '../auth/cell_authorize.js';
92
104
  import { parse_file_fact_url } from '../db/file_fact_url.js';
93
105
  /** `Cache-Control` for fact responses — 5 min revocation window. */
94
106
  const CACHE_CONTROL = 'private, max-age=300';
107
+ /**
108
+ * `X-Content-Type-Options` for fact responses — block MIME sniffing so an
109
+ * `application/octet-stream` fact can't be sniffed into executable HTML.
110
+ */
111
+ const X_CONTENT_TYPE_OPTIONS = 'nosniff';
112
+ /**
113
+ * `Content-Security-Policy` for fact responses. Fact bytes carry a
114
+ * producer-supplied `content_type` served verbatim inline, so a fact stored as
115
+ * (or sniffable to) `text/html` would otherwise execute as stored XSS for any
116
+ * reader of a referencing cell. `default-src 'none'; sandbox` neutralizes
117
+ * script execution and sub-resource loads even when a fact is rendered inline;
118
+ * harmless for directly-served images (they load no sub-resources). The same
119
+ * pair, byte-identical, is set by the Rust twin (`fuz_fact_serving::serve`).
120
+ */
121
+ const CONTENT_SECURITY_POLICY = "default-src 'none'; sandbox";
122
+ /**
123
+ * The untrusted-content hardening headers applied to every served-fact
124
+ * response across all three branches (embedded, `X-Accel-Redirect`,
125
+ * disk-stream). `Content-Disposition: attachment` is deliberately omitted — it
126
+ * would force-download and break legitimate inline image rendering.
127
+ */
128
+ const FACT_SECURITY_HEADERS = {
129
+ 'X-Content-Type-Options': X_CONTENT_TYPE_OPTIONS,
130
+ 'Content-Security-Policy': CONTENT_SECURITY_POLICY,
131
+ };
95
132
  /**
96
133
  * Path-param schema for the bare-hash route. Matching the canonical
97
134
  * fact-hash form here pushes malformed-hash 400s through the framework's
@@ -150,6 +187,7 @@ const serve_fact_bytes = async (c, route, hash, config) => {
150
187
  'Content-Type': content_type,
151
188
  'Content-Length': size,
152
189
  'Cache-Control': CACHE_CONTROL,
190
+ ...FACT_SECURITY_HEADERS,
153
191
  });
154
192
  }
155
193
  // External — defense-in-depth re-validate the URL before trusting it
@@ -167,6 +205,7 @@ const serve_fact_bytes = async (c, route, hash, config) => {
167
205
  'Content-Length': size,
168
206
  'Cache-Control': CACHE_CONTROL,
169
207
  'X-Accel-Redirect': `${x_accel_redirect_prefix}${shard}/${rest}`,
208
+ ...FACT_SECURITY_HEADERS,
170
209
  });
171
210
  }
172
211
  // Dev / tests: stream from disk. `createReadStream` errors land on the
@@ -178,6 +217,7 @@ const serve_fact_bytes = async (c, route, hash, config) => {
178
217
  'Content-Type': content_type,
179
218
  'Content-Length': size,
180
219
  'Cache-Control': CACHE_CONTROL,
220
+ ...FACT_SECURITY_HEADERS,
181
221
  });
182
222
  };
183
223
  /**
@@ -973,9 +973,29 @@ wire-shape check passes green on even when behavior is wrong.
973
973
  error_reason?, fields?, headers?, equivalence_group?}, note?, xfail?}`. A case is **data** — `method`
974
974
  resolves its `input`/`output` from the live registry (RPC) or `RouteSpec`
975
975
  (the 6 REST auth routes), so the case never carries a schema. `as` is the
976
- closed `ConformancePrincipal` enum (`keeper` / `daemon` / `token` /
977
- `anonymous` / `fresh_non_admin` / `role_holder` / `wrong_role` /
978
- `expired_session`) — fixture accessors, never inline credential minting.
976
+ closed `ConformancePrincipal` enum (`keeper` / `daemon` / `invalid_daemon` /
977
+ `daemon_browser` / `token` / `bearer_browser` / `anonymous` /
978
+ `fresh_non_admin` / `role_holder` / `wrong_role` / `expired_session`) —
979
+ fixture accessors, never inline credential minting.
980
+ `invalid_daemon` sends a malformed `X-Daemon-Token` alongside the keeper
981
+ session cookie over a no-Origin transport: the middleware soft-fail-discards
982
+ the daemon token (matching the Rust spine's `None`), auth falls through to the
983
+ session leg, and a daemon-gated action then refuses the session credential
984
+ with `credential_type_required` — not a hard invalid-token 401 (the session
985
+ base credential is what makes the credential-type gate, not the auth gate, the
986
+ refusing layer).
987
+ `daemon_browser` sends a _valid_ `X-Daemon-Token` in a browser context
988
+ (default `Origin` present) alongside the keeper session cookie: the middleware
989
+ discards the header-bearing token as browser context (mirroring the bearer
990
+ guard + the Rust spine's `is_browser_context`), so auth falls through to the
991
+ session leg → `credential_type_required`. Unlike `daemon` (no Origin → token
992
+ honored → 400 confirm guard), the 403 proves a valid token is _dropped_, not
993
+ honored, when an `Origin` is present.
994
+ `bearer_browser` is the bearer twin: a _valid_ bearer api-token in a browser
995
+ context (default `Origin` present), fresh jar (no session) — the bearer
996
+ middleware discards it, so an authed action 401s, wire-indistinguishable from
997
+ `anonymous` (pinned by the `browser_bearer_replay` equivalence group). The
998
+ `token` principal is the honored counterpart (suppresses `Origin`).
979
999
  `expired_session` is the keeper behind an expired server-side session
980
1000
  (`fixture.mint_expired_session()`: a backdated `auth_session` row behind a
981
1001
  still-valid signed cookie, so the DB-row expiry gate is what refuses it).
@@ -1 +1 @@
1
- {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAU9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,0BAA0B,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AA8FD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAomBzF,CAAC"}
1
+ {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAU9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,0BAA0B,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AA8FD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAumBzF,CAAC"}
@@ -445,7 +445,10 @@ export const describe_audit_completeness_tests = (options) => {
445
445
  path: rpc_path,
446
446
  spec: account_purge_action_spec,
447
447
  params: { account_id: target.account.id, confirm: true },
448
- // Keeper-gated: daemon-token credential, not a session.
448
+ // Keeper-gated: daemon-token credential, not a session. Suppress the
449
+ // default `origin` header — the daemon-token middleware discards the
450
+ // credential in a browser context (Origin/Referer present).
451
+ suppress_default_origin: true,
449
452
  headers: fixture.create_daemon_token_headers(),
450
453
  });
451
454
  assert.ok(res.ok, `account_purge failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/bootstrap_backend.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIvD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,aAAa,KACnB,OAAO,CAAC,yBAAyB,CAgBnC,CAAC"}
1
+ {"version":3,"file":"bootstrap_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/bootstrap_backend.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIvD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,YAAY,CAAC;AAE1D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,aAAa,KACnB,OAAO,CAAC,yBAAyB,CAwBnC,CAAC"}
@@ -22,6 +22,14 @@ export const bootstrap_backend = async (config) => {
22
22
  return {
23
23
  ...handle,
24
24
  keeper_transport,
25
+ // Origin-free twin carrying the same keeper cookie jar — the
26
+ // daemon-token middleware discards the credential in a browser
27
+ // context, so the `_testing_*` daemon-token calls route through this.
28
+ keeper_daemon_transport: create_fetch_transport({
29
+ base_url: config.base_url,
30
+ initial_cookies: keeper.cookies,
31
+ origin: null,
32
+ }),
25
33
  keeper_account: keeper.account,
26
34
  keeper_actor: keeper.actor,
27
35
  keeper_cookies: keeper.cookies,
@@ -1 +1 @@
1
- {"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsD9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IAwSlF,CAAC"}
1
+ {"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsD9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IA6VlF,CAAC"}
@@ -83,6 +83,29 @@ export const describe_cell_crud_cross_tests = (options) => {
83
83
  assert.ok(!gone.ok, 'deleted cell still readable');
84
84
  assert.strictEqual(error_reason(gone), 'cell_not_found');
85
85
  });
86
+ test_if(capabilities.cell_crud, 'refs are deduped and lexicographically sorted (deterministic, cross-impl-identical)', async () => {
87
+ const fixture = await setup_test();
88
+ const owner = await fixture.create_account({ username: 'cell_refs_owner' });
89
+ const t = fixture.fresh_transport();
90
+ const owner_headers = owner.create_session_headers();
91
+ // Three valid distinct fact hashes that sort a < b < c, embedded in
92
+ // `data` in a deliberately unsorted, nested, duplicated order. Both
93
+ // spines must derive `refs` as the sorted [a, b, c] regardless of
94
+ // traversal order or `HashSet` iteration — the byte-identical
95
+ // cross-backend contract (Rust `extract_refs` ↔ TS `derive_refs`).
96
+ const ref_a = `blake3:${'a'.repeat(64)}`;
97
+ const ref_b = `blake3:${'b'.repeat(64)}`;
98
+ const ref_c = `blake3:${'c'.repeat(64)}`;
99
+ const sorted = [ref_a, ref_b, ref_c];
100
+ const created = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { data: { kind: 'note', cover: ref_c, nested: { attachments: [ref_b, ref_a, ref_b] } } }, owner_headers), CellCreateOutput);
101
+ assert.deepStrictEqual(created.cell.refs, sorted, 'create did not return sorted refs');
102
+ // Read it back — the stored column round-trips sorted too.
103
+ const got = expect_output(await cross_rpc_call(t, rpc_path, 'cell_get', { id: created.cell.id }, owner_headers), CellGetOutput);
104
+ assert.deepStrictEqual(got.cell.refs, sorted, 'get did not return sorted refs');
105
+ // Update reorders the embedded refs — the new column is still sorted.
106
+ const updated = expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: created.cell.id, data: { refs: [ref_b, ref_c, ref_a] } }, owner_headers), CellUpdateOutput);
107
+ assert.deepStrictEqual(updated.cell.refs, sorted, 'update did not return sorted refs');
108
+ });
86
109
  test_if(capabilities.cell_crud, 'anon sees public cells only; private is 404', async () => {
87
110
  const fixture = await setup_test();
88
111
  const owner = await fixture.create_account({ username: 'cell_anon_owner' });
@@ -33,8 +33,39 @@ import { z } from 'zod';
33
33
  * - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
34
34
  * `ROLE_ADMIN`), session credential.
35
35
  * - `daemon` — the keeper authenticated via the daemon-token header.
36
+ * - `invalid_daemon` — a *malformed/invalid* `X-Daemon-Token` carried
37
+ * alongside the keeper's session cookie, over a non-browser (no-Origin)
38
+ * transport. The middleware soft-fail-discards the invalid daemon token
39
+ * (matching the Rust spine's `None`), so auth falls through to the session
40
+ * leg: the request authenticates as the keeper-via-session and a
41
+ * daemon-gated action then refuses the session credential with
42
+ * `credential_type_required` — not a hard `invalid_daemon_token` 401. The
43
+ * no-Origin transport keeps the daemon token on the invalid-token path
44
+ * rather than the browser-context discard; the session base credential is
45
+ * what makes the credential-type gate (not the auth gate) the refusing
46
+ * layer (without it the discard would 401 anonymous).
47
+ * - `daemon_browser` — a *valid* `X-Daemon-Token` carried in a browser
48
+ * context (default `Origin` present) alongside the keeper's session cookie.
49
+ * Browsers attach `Origin` automatically; the daemon-token middleware
50
+ * discards a header-bearing daemon token as browser context (mirroring the
51
+ * bearer guard and the Rust spine's `is_browser_context`), so the *valid*
52
+ * token is dropped and auth falls through to the session leg → a daemon-gated
53
+ * action then refuses the session credential with `credential_type_required`.
54
+ * Distinct from `invalid_daemon`: here the token is well-formed and current,
55
+ * so a 403 (not a 400 confirm-guard hit like `daemon`) proves the
56
+ * browser-context discard fired — a valid daemon token does NOT authenticate
57
+ * when an `Origin` is present. `Origin` is deliberately NOT suppressed; its
58
+ * presence is the signal under test.
36
59
  * - `token` — the keeper authenticated via a bearer api-token (non-browser
37
60
  * context; the runner suppresses `Origin` so the token isn't discarded).
61
+ * - `bearer_browser` — a *valid* bearer api-token carried in a browser context
62
+ * (default `Origin` present), fresh jar so NO session rides alongside. The
63
+ * bearer middleware discards the token as browser context (mirroring the
64
+ * daemon guard + the Rust spine's `is_browser_context`), so the request
65
+ * arrives anonymous and an authed action 401s. Proves a stolen bearer cannot
66
+ * be replayed from a browser — wire-indistinguishable from sending no
67
+ * credential (the `token` principal is the honored counterpart: it suppresses
68
+ * `Origin`, so the same token authenticates).
38
69
  * - `anonymous` — no credential, fresh cookie jar.
39
70
  * - `fresh_non_admin` — a freshly minted account with no roles, session
40
71
  * credential (via the production invite → signup → login flow).
@@ -53,6 +84,9 @@ export declare const ConformancePrincipal: z.ZodEnum<{
53
84
  keeper: "keeper";
54
85
  token: "token";
55
86
  daemon: "daemon";
87
+ invalid_daemon: "invalid_daemon";
88
+ daemon_browser: "daemon_browser";
89
+ bearer_browser: "bearer_browser";
56
90
  anonymous: "anonymous";
57
91
  fresh_non_admin: "fresh_non_admin";
58
92
  role_holder: "role_holder";
@@ -68,6 +102,9 @@ export declare const ConformanceCaseRequest: z.ZodObject<{
68
102
  keeper: "keeper";
69
103
  token: "token";
70
104
  daemon: "daemon";
105
+ invalid_daemon: "invalid_daemon";
106
+ daemon_browser: "daemon_browser";
107
+ bearer_browser: "bearer_browser";
71
108
  anonymous: "anonymous";
72
109
  fresh_non_admin: "fresh_non_admin";
73
110
  role_holder: "role_holder";
@@ -120,6 +157,9 @@ export declare const ConformanceCase: z.ZodObject<{
120
157
  keeper: "keeper";
121
158
  token: "token";
122
159
  daemon: "daemon";
160
+ invalid_daemon: "invalid_daemon";
161
+ daemon_browser: "daemon_browser";
162
+ bearer_browser: "bearer_browser";
123
163
  anonymous: "anonymous";
124
164
  fresh_non_admin: "fresh_non_admin";
125
165
  role_holder: "role_holder";
@@ -1 +1 @@
1
- {"version":3,"file":"conformance_case.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_case.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;EAS/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;kBAgBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B;;;;;;kBAiDrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;kBAK/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"conformance_case.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_case.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAY/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;kBAgBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B;;;;;;kBAiDrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;kBAK/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
@@ -33,8 +33,39 @@ import { z } from 'zod';
33
33
  * - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
34
34
  * `ROLE_ADMIN`), session credential.
35
35
  * - `daemon` — the keeper authenticated via the daemon-token header.
36
+ * - `invalid_daemon` — a *malformed/invalid* `X-Daemon-Token` carried
37
+ * alongside the keeper's session cookie, over a non-browser (no-Origin)
38
+ * transport. The middleware soft-fail-discards the invalid daemon token
39
+ * (matching the Rust spine's `None`), so auth falls through to the session
40
+ * leg: the request authenticates as the keeper-via-session and a
41
+ * daemon-gated action then refuses the session credential with
42
+ * `credential_type_required` — not a hard `invalid_daemon_token` 401. The
43
+ * no-Origin transport keeps the daemon token on the invalid-token path
44
+ * rather than the browser-context discard; the session base credential is
45
+ * what makes the credential-type gate (not the auth gate) the refusing
46
+ * layer (without it the discard would 401 anonymous).
47
+ * - `daemon_browser` — a *valid* `X-Daemon-Token` carried in a browser
48
+ * context (default `Origin` present) alongside the keeper's session cookie.
49
+ * Browsers attach `Origin` automatically; the daemon-token middleware
50
+ * discards a header-bearing daemon token as browser context (mirroring the
51
+ * bearer guard and the Rust spine's `is_browser_context`), so the *valid*
52
+ * token is dropped and auth falls through to the session leg → a daemon-gated
53
+ * action then refuses the session credential with `credential_type_required`.
54
+ * Distinct from `invalid_daemon`: here the token is well-formed and current,
55
+ * so a 403 (not a 400 confirm-guard hit like `daemon`) proves the
56
+ * browser-context discard fired — a valid daemon token does NOT authenticate
57
+ * when an `Origin` is present. `Origin` is deliberately NOT suppressed; its
58
+ * presence is the signal under test.
36
59
  * - `token` — the keeper authenticated via a bearer api-token (non-browser
37
60
  * context; the runner suppresses `Origin` so the token isn't discarded).
61
+ * - `bearer_browser` — a *valid* bearer api-token carried in a browser context
62
+ * (default `Origin` present), fresh jar so NO session rides alongside. The
63
+ * bearer middleware discards the token as browser context (mirroring the
64
+ * daemon guard + the Rust spine's `is_browser_context`), so the request
65
+ * arrives anonymous and an authed action 401s. Proves a stolen bearer cannot
66
+ * be replayed from a browser — wire-indistinguishable from sending no
67
+ * credential (the `token` principal is the honored counterpart: it suppresses
68
+ * `Origin`, so the same token authenticates).
38
69
  * - `anonymous` — no credential, fresh cookie jar.
39
70
  * - `fresh_non_admin` — a freshly minted account with no roles, session
40
71
  * credential (via the production invite → signup → login flow).
@@ -52,7 +83,10 @@ import { z } from 'zod';
52
83
  export const ConformancePrincipal = z.enum([
53
84
  'keeper',
54
85
  'daemon',
86
+ 'invalid_daemon',
87
+ 'daemon_browser',
55
88
  'token',
89
+ 'bearer_browser',
56
90
  'anonymous',
57
91
  'fresh_non_admin',
58
92
  'role_holder',
@@ -1 +1 @@
1
- {"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8B9B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAMjE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,KAAK,eAAe,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,YAAY,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AA+FD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC,MAAM,CAIrD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,MAAM,KACX,IAQF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EACvC,OAAO,MAAM,KACX,IAYF,CAAC;AAgKF;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,uBAAuB,KAAG,IAkEnF,CAAC"}
1
+ {"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8B9B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAOjE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,KAAK,eAAe,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,YAAY,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAyJD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC,MAAM,CAIrD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,MAAM,KACX,IAQF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EACvC,OAAO,MAAM,KACX,IAYF,CAAC;AAgKF;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,uBAAuB,KAAG,IAkEnF,CAAC"}
@@ -25,10 +25,21 @@ import '../assert_dev_env.js';
25
25
  * @module
26
26
  */
27
27
  import { assert, describe, test } from 'vitest';
28
+ import { DAEMON_TOKEN_HEADER } from '../../auth/daemon_token.js';
28
29
  import { find_auth_route, rest_auth_route_suffixes, } from '../integration_helpers.js';
29
30
  import { find_rpc_action, headers_to_record, rpc_call, resolve_rpc_endpoints_for_setup, } from '../rpc_helpers.js';
30
31
  import {} from './conformance_case.js';
31
32
  import { xfail_until } from './xfail.js';
33
+ /**
34
+ * A deliberately malformed `X-Daemon-Token` value for the `invalid_daemon`
35
+ * principal. Fails the `DaemonToken` Zod schema (not 43 base64url chars), so
36
+ * the daemon-token middleware soft-fail-discards it rather than authenticating
37
+ * — the request falls through with no identity, surfacing
38
+ * `credential_type_required` on a daemon-gated action (matching the Rust
39
+ * spine's `None`). Sent over a no-Origin transport so it lands on the
40
+ * invalid-token path, not the browser-context discard.
41
+ */
42
+ const INVALID_DAEMON_TOKEN_VALUE = 'not-a-valid-daemon-token';
32
43
  /**
33
44
  * Map a `ConformancePrincipal` onto the transport + headers it
34
45
  * authenticates with, reading exclusively from the per-test `TestFixture`.
@@ -51,6 +62,39 @@ const resolve_principal = async (fixture, as, principals) => {
51
62
  headers: fixture.create_daemon_token_headers(),
52
63
  suppress_default_origin: true,
53
64
  };
65
+ case 'invalid_daemon':
66
+ // A malformed `X-Daemon-Token` carried ALONGSIDE the keeper's session
67
+ // cookie, over a non-browser (no-Origin) transport. The invalid daemon
68
+ // token is soft-fail-discarded (matching the Rust spine's `None`), so
69
+ // auth falls through to the session leg: the request authenticates as
70
+ // the keeper-via-session (clearing `require_auth`), then the daemon-gated
71
+ // action's credential-type gate refuses the session credential →
72
+ // `403 credential_type_required`. No-Origin keeps the daemon token on
73
+ // the invalid-token path, NOT the browser-context discard. Without the
74
+ // base credential this would 401 (anonymous) — the session is what makes
75
+ // the *credential-type* gate, not the auth gate, the refusing layer.
76
+ return {
77
+ transport: fixture.fresh_transport({ origin: null }),
78
+ headers: fixture.create_session_headers({
79
+ [DAEMON_TOKEN_HEADER]: INVALID_DAEMON_TOKEN_VALUE,
80
+ }),
81
+ suppress_default_origin: true,
82
+ };
83
+ case 'daemon_browser':
84
+ // A VALID `X-Daemon-Token` carried in a browser context (default Origin
85
+ // present) ALONGSIDE the keeper's session cookie. The daemon-token
86
+ // middleware discards a header-bearing daemon token as browser context
87
+ // (mirroring the bearer guard + the Rust spine's `is_browser_context`),
88
+ // so the valid token is dropped and auth falls through to the session
89
+ // leg → keeper-via-session → a daemon-gated action refuses the session
90
+ // credential with `credential_type_required`. Origin is NOT suppressed —
91
+ // its presence is the browser-context signal under test. Unlike the
92
+ // `daemon` principal (no Origin → token honored → reaches the 400 confirm
93
+ // guard), the 403 here proves the valid token was discarded, not honored.
94
+ return {
95
+ transport: fixture.transport,
96
+ headers: fixture.create_session_headers(fixture.create_daemon_token_headers()),
97
+ };
54
98
  case 'token':
55
99
  // Bearer is discarded in a browser context — empty jar + no Origin.
56
100
  return {
@@ -58,6 +102,20 @@ const resolve_principal = async (fixture, as, principals) => {
58
102
  headers: fixture.create_bearer_headers(),
59
103
  suppress_default_origin: true,
60
104
  };
105
+ case 'bearer_browser':
106
+ // A VALID bearer api-token in a browser context (default Origin
107
+ // present), fresh jar so no session rides alongside. The bearer
108
+ // middleware discards a header-bearing token as browser context
109
+ // (mirroring the daemon guard + the Rust spine's `is_browser_context`),
110
+ // so the request arrives anonymous → an authed action 401s. Origin is
111
+ // NOT suppressed (its presence is the browser-context signal) — the
112
+ // inverse of the `token` principal, which suppresses it so the same
113
+ // token is honored. The 401 proves the valid token was discarded, not
114
+ // honored (a non-discarding leg would authenticate → 200).
115
+ return {
116
+ transport: fixture.fresh_transport(),
117
+ headers: fixture.create_bearer_headers(),
118
+ };
61
119
  case 'anonymous':
62
120
  // Fresh jar so the keeper cookie (cross-process) can't leak in.
63
121
  return { transport: fixture.fresh_transport(), headers: {} };
@@ -1 +1 @@
1
- {"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAC,wBAAwB,EAAE,SAAS,EAAC,MAAM,YAAY,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,wBAAwB;IAC5E,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AA8BD,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8NxF,CAAC"}
1
+ {"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAC,wBAAwB,EAAE,SAAS,EAAC,MAAM,YAAY,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,wBAAwB;IAC5E,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AA8BD,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAiOxF,CAAC"}
@@ -68,7 +68,11 @@ export const describe_fact_serving_cross_tests = (options) => {
68
68
  const { setup_test, setup_test_multi_actor, capabilities } = options;
69
69
  const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
70
70
  /** Seed an embedded fact over the daemon channel; returns its `blake3:` hash. */
71
- const put_fact = async (fixture, content, content_type) => expect_output(await cross_rpc_call(fixture.transport, rpc_path, '_testing_put_fact', content_type === undefined ? { content } : { content, content_type }, fixture.create_daemon_token_headers()), TestingPutFactOutput).hash;
71
+ const put_fact = async (fixture, content, content_type) => expect_output(await cross_rpc_call(
72
+ // Origin-free transport — the daemon-token middleware discards the
73
+ // credential when an `Origin` / `Referer` is present (browser
74
+ // context), and cross-process `fixture.transport` auto-adds `Origin`.
75
+ fixture.fresh_transport({ origin: null }), rpc_path, '_testing_put_fact', content_type === undefined ? { content } : { content, content_type }, fixture.create_daemon_token_headers()), TestingPutFactOutput).hash;
72
76
  /**
73
77
  * Create a cell referencing `hash` (`extract_refs` lifts it into `cell.refs`).
74
78
  * Pass `acting` when the caller's account holds more than one actor —
@@ -253,6 +253,11 @@ export interface RpcPathCrossSuiteOptions extends CrossSuiteOptions {
253
253
  * const bootstrapped: BootstrappedBackendHandle = {
254
254
  * ...handle,
255
255
  * keeper_transport,
256
+ * keeper_daemon_transport: create_fetch_transport({
257
+ * base_url: config.base_url,
258
+ * initial_cookies: keeper.cookies,
259
+ * origin: null,
260
+ * }),
256
261
  * keeper_account: keeper.account,
257
262
  * keeper_actor: keeper.actor,
258
263
  * keeper_cookies: keeper.cookies,
@@ -268,6 +273,18 @@ export interface RpcPathCrossSuiteOptions extends CrossSuiteOptions {
268
273
  export interface BootstrappedBackendHandle extends BackendHandle {
269
274
  /** Transport carrying the keeper session cookie + cookie jar. */
270
275
  readonly keeper_transport: FetchTransport;
276
+ /**
277
+ * Daemon-token transport for the keeper — same cookie jar as
278
+ * `keeper_transport` but with `origin: null` so it sends no `Origin`
279
+ * header. The daemon-token middleware discards the credential in a
280
+ * browser context (any `Origin` / `Referer`), so the `_testing_*`
281
+ * daemon-token calls (`_testing_reset` / `_testing_schema_snapshot` /
282
+ * `_testing_action_manifest` / `_testing_mint_session`) must route
283
+ * through this Origin-free transport. CSRF/cookie-session calls
284
+ * (`invite_create`, `role_grant_offer_create`) stay on `keeper_transport`,
285
+ * which keeps its default `Origin`.
286
+ */
287
+ readonly keeper_daemon_transport: FetchTransport;
271
288
  /** Keeper account JSON captured from `POST /bootstrap`. */
272
289
  readonly keeper_account: {
273
290
  readonly id: Uuid;
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/setup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/setup.ts"],"names":[],"mappings":"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"}