@fuzdev/fuz_app 0.85.0 → 0.85.1
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/dist/testing/CLAUDE.md +31 -3
- package/dist/testing/cross_backend/testing_backdoor.d.ts +6 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -0
- package/dist/testing/cross_backend/testing_backdoor.js +126 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +12 -3
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +40 -9
- package/dist/testing/cross_backend/testing_server_core.d.ts +13 -2
- package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_core.js +19 -7
- package/dist/testing/mock_fs.d.ts +1 -0
- package/dist/testing/mock_fs.d.ts.map +1 -1
- package/dist/testing/mock_fs.js +1 -6
- package/dist/testing/surface_invariants.d.ts +34 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +49 -1
- package/dist/testing/ws_round_trip.d.ts +1 -0
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +1 -38
- package/package.json +1 -1
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -13,9 +13,13 @@ testing-patterns. This file is a reference index for the helpers themselves.
|
|
|
13
13
|
|
|
14
14
|
## Production guard — always the first import
|
|
15
15
|
|
|
16
|
-
Every module here starts with `import './assert_dev_env.js';`
|
|
17
|
-
from `esm-env` and throws if false, preventing production-bundle
|
|
18
|
-
|
|
16
|
+
Every runtime-reachable module here starts with `import './assert_dev_env.js';`
|
|
17
|
+
— reads `DEV` from `esm-env` and throws if false, preventing production-bundle
|
|
18
|
+
inclusion. Make this the first line in new modules. Enforced by
|
|
19
|
+
`src/test/testing/assert_dev_env_coverage.test.ts`, which fails if any module
|
|
20
|
+
omits the guard. The sole exemption is `cross_backend/make_cross_backend_project.ts`
|
|
21
|
+
— a vitest-project factory consumed by consumers' `vite.config.ts` at config
|
|
22
|
+
time (never runtime), where a throwing guard would break `vite build`.
|
|
19
23
|
|
|
20
24
|
## Stubs, factories, mocks
|
|
21
25
|
|
|
@@ -274,6 +278,7 @@ RPC / WS structural invariants (options-free, apply over `surface.rpc_endpoints`
|
|
|
274
278
|
* `assert_ws_method_descriptions_present` — every WS method on every endpoint has a non-empty `description`.
|
|
275
279
|
* `assert_ws_endpoints_include_protocol_actions` — every WS endpoint includes `heartbeat` + `cancel` (the `protocol_actions` spread from `actions/protocol.js`).
|
|
276
280
|
* `assert_ws_notifications_have_null_auth` — WS method `kind === 'remote_notification' ⟺ auth === null`; guards against drift between spec union and surface emitter.
|
|
281
|
+
* `assert_no_testing_methods` — no `_testing_*` backdoor action (`TESTING_METHOD_PREFIX`) appears as an RPC or WS method on the declared surface. The test-binary actions are live-mounted only; this guards against a future wiring change folding `create_testing_actions(...)` into the surface-generating registry.
|
|
277
282
|
|
|
278
283
|
Per-endpoint duplicate method names and the auth-shape biconditional are
|
|
279
284
|
already enforced at startup by `compile_action_registry` (see
|
|
@@ -1208,6 +1213,29 @@ spine bootstrap (auth + cell + cell_history + fact) and is regenerated +
|
|
|
1208
1213
|
drift-guarded by `src/test/cross_backend/spine_expected_schema.db.test.ts`
|
|
1209
1214
|
(`UPDATE_SCHEMA_READY=1`, then `gro format`).
|
|
1210
1215
|
|
|
1216
|
+
### Testing-backdoor credential gate — `cross_backend/testing_backdoor.ts`
|
|
1217
|
+
|
|
1218
|
+
`describe_testing_backdoor_cross_tests({setup_test, capabilities, rpc_path?})` —
|
|
1219
|
+
the negative-credential parity suite for the `_testing_*` backdoor actions.
|
|
1220
|
+
For each of `_testing_reset` / `_testing_mint_session` / `_testing_put_fact` /
|
|
1221
|
+
`_testing_schema_snapshot` (the three privileged writes plus the schema-dump
|
|
1222
|
+
read) it fires three principals over real HTTP: **anonymous** → 401, **session** →
|
|
1223
|
+
403 `credential_type_required`, **bearer** → 403 `credential_type_required` —
|
|
1224
|
+
proving the daemon-token gate that fences each backdoor action holds end-to-end
|
|
1225
|
+
on the real dispatcher (the spec-derived `describe_rpc_attack_surface_tests`
|
|
1226
|
+
never enumerates them because they're off the declared surface). Each method is
|
|
1227
|
+
sent with **valid** params so the session/bearer cases clear the 400
|
|
1228
|
+
input-validation phase and reach the 403 credential gate (order is
|
|
1229
|
+
401 → 400 → 403); the handler never runs. Cited property: `docs/security.md`
|
|
1230
|
+
§Test Backdoor Actions. Cross-process only (the `_testing_*` actions are
|
|
1231
|
+
mounted on the spawned binary, not the in-process app — like the ws/sse
|
|
1232
|
+
suites); ungated, since every cross backend that uses
|
|
1233
|
+
`default_cross_process_setup` mounts them. fuz_app's own wiring is
|
|
1234
|
+
`src/test/cross_backend/testing_backdoor.cross.test.ts`. The complement is the
|
|
1235
|
+
in-process spec-level gate test (`src/test/testing/testing_actions_auth.test.ts`,
|
|
1236
|
+
asserting each spec declares `credential_types: ['daemon_token']`) + the
|
|
1237
|
+
`assert_no_testing_methods` surface invariant.
|
|
1238
|
+
|
|
1211
1239
|
### Building a TS test-server binary — `testing_server_core.ts` + adapters
|
|
1212
1240
|
|
|
1213
1241
|
The reusable shape for standing up a **spawnable TS** cross-process test
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { CellCrossTestOptions } from './cell_cross_helpers.js';
|
|
3
|
+
/** Options for the testing-backdoor negative-credential suite. */
|
|
4
|
+
export type TestingBackdoorCrossTestOptions = CellCrossTestOptions;
|
|
5
|
+
export declare const describe_testing_backdoor_cross_tests: (options: TestingBackdoorCrossTestOptions) => void;
|
|
6
|
+
//# sourceMappingURL=testing_backdoor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing_backdoor.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_backdoor.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAyD9B,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAgElE,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,GAAG,oBAAoB,CAAC;AAEnE,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAiCF,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-backend negative-credential suite for the `_testing_*` backdoor
|
|
4
|
+
* actions.
|
|
5
|
+
*
|
|
6
|
+
* `_testing_reset` / `_testing_mint_session` / `_testing_put_fact` /
|
|
7
|
+
* `_testing_schema_snapshot` are privileged test-binary actions the
|
|
8
|
+
* production wire never exposes — three direct DB writes (full auth wipe,
|
|
9
|
+
* forged session row, raw fact insert) plus a full-schema introspection read
|
|
10
|
+
* (the highest info-leak of the set were the gate to break). Their only
|
|
11
|
+
* structural fence is the **daemon-token** credential gate on
|
|
12
|
+
* each spec's `auth` axis. A test binary live-mounts them on its RPC
|
|
13
|
+
* endpoint but keeps them off the declared surface — so the spec-derived
|
|
14
|
+
* `describe_rpc_attack_surface_tests` never enumerates them, and nothing
|
|
15
|
+
* else fires them with a non-daemon credential to prove the gate holds
|
|
16
|
+
* end-to-end. This suite does, against each impl's real auth resolution.
|
|
17
|
+
*
|
|
18
|
+
* For every backdoor method, three principals:
|
|
19
|
+
*
|
|
20
|
+
* - **anonymous** (no credential) → `401` (pre-validation auth refuses an
|
|
21
|
+
* account-less caller before anything else).
|
|
22
|
+
* - **session** (the keeper's browser-context cookie) → `403`
|
|
23
|
+
* `credential_type_required` — a session cookie, even one carrying the
|
|
24
|
+
* keeper role, tops out below the daemon-token channel.
|
|
25
|
+
* - **bearer** (the keeper's api-token, non-browser context) → `403`
|
|
26
|
+
* `credential_type_required` — same ceiling; an api token cannot reach
|
|
27
|
+
* keeper operations.
|
|
28
|
+
*
|
|
29
|
+
* Each method is sent with **valid** params so the session/bearer cases
|
|
30
|
+
* clear the dispatcher's input-validation (400) phase and actually reach the
|
|
31
|
+
* post-authorization credential gate (the order is 401 → 400 → 403); the
|
|
32
|
+
* handler never runs (the gate refuses first), so the writes never execute.
|
|
33
|
+
*
|
|
34
|
+
* Complements the spec-level gate check (which pins that each spec *declares*
|
|
35
|
+
* `credential_types: ['daemon_token']`) and the surface-absence invariant
|
|
36
|
+
* (`assert_no_testing_methods`) — this one pins the runtime 401/403 behavior
|
|
37
|
+
* on both impls. Cited property: `security.md` §Test Backdoor Actions
|
|
38
|
+
* (daemon-token-gated, off-surface, DEV-excluded).
|
|
39
|
+
*
|
|
40
|
+
* Cross-process only — the `_testing_*` actions are mounted on the spawned
|
|
41
|
+
* binary, not the in-process app — like the ws/sse suites. Wire from a
|
|
42
|
+
* `*.cross.test.ts`. Requires the standard `_testing_*` actions mounted (the
|
|
43
|
+
* same precondition `default_cross_process_setup` already imposes for its
|
|
44
|
+
* per-test `_testing_reset`); ungated, since every cross backend mounts them.
|
|
45
|
+
*
|
|
46
|
+
* `$lib`-free by contract (relative specifiers only), like the sibling
|
|
47
|
+
* cross-backend suites.
|
|
48
|
+
*
|
|
49
|
+
* @module
|
|
50
|
+
*/
|
|
51
|
+
import { describe, test, assert } from 'vitest';
|
|
52
|
+
import { ERROR_CREDENTIAL_TYPE_REQUIRED } from '../../http/error_schemas.js';
|
|
53
|
+
import { rpc_call } from '../rpc_helpers.js';
|
|
54
|
+
import { SPINE_RPC_PATH } from './default_spine_surface.js';
|
|
55
|
+
/** A well-formed UUID that never names a real row. */
|
|
56
|
+
const NIL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
57
|
+
/**
|
|
58
|
+
* The backdoor methods + a **valid** params payload each (so the
|
|
59
|
+
* session/bearer cases reach the 403 credential gate rather than a 400 on
|
|
60
|
+
* input validation). The handlers never run — the gate refuses first.
|
|
61
|
+
*/
|
|
62
|
+
const backdoor_methods = [
|
|
63
|
+
{ method: '_testing_reset', params: {} },
|
|
64
|
+
{ method: '_testing_mint_session', params: { account_id: NIL_UUID, expires_in_seconds: -60 } },
|
|
65
|
+
{ method: '_testing_put_fact', params: { content: 'backdoor-probe' } },
|
|
66
|
+
// The schema-dump read — `exclude_tables` is optional, so `{}` is valid
|
|
67
|
+
// and clears the 400 phase like the writes above.
|
|
68
|
+
{ method: '_testing_schema_snapshot', params: {} },
|
|
69
|
+
];
|
|
70
|
+
const principals = [
|
|
71
|
+
{
|
|
72
|
+
name: 'anonymous',
|
|
73
|
+
status: 401,
|
|
74
|
+
// Fresh jar so the keeper cookie (cross-process) can't leak in.
|
|
75
|
+
resolve: (f) => ({ transport: f.fresh_transport(), headers: {} }),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'session',
|
|
79
|
+
status: 403,
|
|
80
|
+
reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
|
|
81
|
+
resolve: (f) => ({ transport: f.transport, headers: f.create_session_headers() }),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'bearer',
|
|
85
|
+
status: 403,
|
|
86
|
+
reason: ERROR_CREDENTIAL_TYPE_REQUIRED,
|
|
87
|
+
// Bearer is discarded in a browser context, so suppress Origin (empty
|
|
88
|
+
// jar + no Origin) — the credential must actually resolve so the refusal
|
|
89
|
+
// lands on the credential-type gate, not on bearer-discard (→ 401).
|
|
90
|
+
resolve: (f) => ({
|
|
91
|
+
transport: f.fresh_transport({ origin: null }),
|
|
92
|
+
headers: f.create_bearer_headers(),
|
|
93
|
+
suppress_default_origin: true,
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
export const describe_testing_backdoor_cross_tests = (options) => {
|
|
98
|
+
const { setup_test } = options;
|
|
99
|
+
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
100
|
+
describe('testing backdoor credential gate parity', () => {
|
|
101
|
+
for (const { method, params } of backdoor_methods) {
|
|
102
|
+
for (const principal of principals) {
|
|
103
|
+
test(`${method} rejects ${principal.name} → ${principal.status}`, async () => {
|
|
104
|
+
const fixture = await setup_test();
|
|
105
|
+
const { transport, headers, suppress_default_origin } = principal.resolve(fixture);
|
|
106
|
+
const res = await rpc_call({
|
|
107
|
+
app: transport,
|
|
108
|
+
path: rpc_path,
|
|
109
|
+
method,
|
|
110
|
+
params,
|
|
111
|
+
headers,
|
|
112
|
+
...(suppress_default_origin && { suppress_default_origin: true }),
|
|
113
|
+
});
|
|
114
|
+
const label = `${method} ${principal.name}`;
|
|
115
|
+
assert.ok(!res.ok, `${label}: expected denial (${principal.status}) but the call succeeded`);
|
|
116
|
+
assert.strictEqual(res.status, principal.status, `${label}: status`);
|
|
117
|
+
// `!res.ok` narrows `res` to the error variant for `res.error`.
|
|
118
|
+
if (principal.reason !== undefined && !res.ok) {
|
|
119
|
+
const reason = res.error.data?.reason;
|
|
120
|
+
assert.strictEqual(reason, principal.reason, `${label}: error.data.reason`);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
};
|
|
@@ -183,9 +183,18 @@ export declare const testing_drain_effects_action_spec: {
|
|
|
183
183
|
* `_testing_mint_session` — mint an expired-by-construction server-side
|
|
184
184
|
* session for an existing account and return its signed cookie value.
|
|
185
185
|
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
186
|
+
* `expires_in_seconds` is **constrained negative** (`z.number().int().negative()`)
|
|
187
|
+
* so the action is structurally incapable of minting a *usable* session: it
|
|
188
|
+
* can only produce an already-backdated, already-dead `auth_session` row. The
|
|
189
|
+
* daemon-token gate + loopback binding already fence the backdoor, but the
|
|
190
|
+
* negative constraint is the make-impossible-states floor — even a misuse
|
|
191
|
+
* can't forge a valid session for an arbitrary `account_id`. The Rust mirror
|
|
192
|
+
* (`fuz_testing::create_testing_mint_session_action_spec`) enforces the same
|
|
193
|
+
* floor.
|
|
194
|
+
*
|
|
195
|
+
* The minted `auth_session` row's `expires_at` is backdated while the
|
|
196
|
+
* returned cookie's own signed payload stays valid (future). Cross-process
|
|
197
|
+
* auth resolution therefore passes the
|
|
189
198
|
* cookie-payload gate (`parse_session`) and is refused by the authoritative
|
|
190
199
|
* DB-row gate (`query_session_get_valid` — `WHERE expires_at > NOW()`) —
|
|
191
200
|
* the gate the in-process payload-expiry tests never reach and the one that
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAIvE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,gBAAgB,CAAC;AAkCvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAwBC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACiB,CAAC;AAEzE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAqIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
|
|
@@ -68,6 +68,20 @@ import { auth_integration_truncate_tables } from '../db.js';
|
|
|
68
68
|
import { query_schema_snapshot, SchemaSnapshot } from '../schema_introspect.js';
|
|
69
69
|
import { query_create_actor } from '../../auth/account_queries.js';
|
|
70
70
|
import { create_test_account_with_credentials, mint_test_session, DEFAULT_TEST_PASSWORD, } from '../app_server.js';
|
|
71
|
+
/**
|
|
72
|
+
* Shared `auth` axis for every `_testing_*` action: keeper-only via the
|
|
73
|
+
* daemon-token credential, no acting actor. This is the entire structural
|
|
74
|
+
* fence on the backdoor surface (these actions run direct DB writes the
|
|
75
|
+
* production wire never exposes), so all five specs reference this one const
|
|
76
|
+
* rather than re-declaring it — a single source of truth the gate test
|
|
77
|
+
* (`testing_actions_auth.test.ts`) pins. Mirrors the Rust `DAEMON_TOKEN_ONLY`
|
|
78
|
+
* / shared `AuthSpec` in `fuz_testing`.
|
|
79
|
+
*/
|
|
80
|
+
const TESTING_ACTION_AUTH = {
|
|
81
|
+
account: 'required',
|
|
82
|
+
actor: 'none',
|
|
83
|
+
credential_types: ['daemon_token'],
|
|
84
|
+
};
|
|
71
85
|
/** Output shape for an individual seeded account (keeper or extra). */
|
|
72
86
|
const SeededAccountShape = z.strictObject({
|
|
73
87
|
account: z.strictObject({ id: Uuid, username: z.string() }),
|
|
@@ -104,7 +118,7 @@ export const testing_reset_action_spec = {
|
|
|
104
118
|
method: '_testing_reset',
|
|
105
119
|
kind: 'request_response',
|
|
106
120
|
initiator: 'frontend',
|
|
107
|
-
auth:
|
|
121
|
+
auth: TESTING_ACTION_AUTH,
|
|
108
122
|
side_effects: true,
|
|
109
123
|
input: z.strictObject({
|
|
110
124
|
extra_keeper_roles: z.array(z.string()).optional(),
|
|
@@ -154,7 +168,7 @@ export const testing_drain_effects_action_spec = {
|
|
|
154
168
|
method: '_testing_drain_effects',
|
|
155
169
|
kind: 'request_response',
|
|
156
170
|
initiator: 'frontend',
|
|
157
|
-
auth:
|
|
171
|
+
auth: TESTING_ACTION_AUTH,
|
|
158
172
|
side_effects: false,
|
|
159
173
|
input: z.void(),
|
|
160
174
|
output: z.strictObject({ ok: z.boolean() }),
|
|
@@ -165,9 +179,18 @@ export const testing_drain_effects_action_spec = {
|
|
|
165
179
|
* `_testing_mint_session` — mint an expired-by-construction server-side
|
|
166
180
|
* session for an existing account and return its signed cookie value.
|
|
167
181
|
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
182
|
+
* `expires_in_seconds` is **constrained negative** (`z.number().int().negative()`)
|
|
183
|
+
* so the action is structurally incapable of minting a *usable* session: it
|
|
184
|
+
* can only produce an already-backdated, already-dead `auth_session` row. The
|
|
185
|
+
* daemon-token gate + loopback binding already fence the backdoor, but the
|
|
186
|
+
* negative constraint is the make-impossible-states floor — even a misuse
|
|
187
|
+
* can't forge a valid session for an arbitrary `account_id`. The Rust mirror
|
|
188
|
+
* (`fuz_testing::create_testing_mint_session_action_spec`) enforces the same
|
|
189
|
+
* floor.
|
|
190
|
+
*
|
|
191
|
+
* The minted `auth_session` row's `expires_at` is backdated while the
|
|
192
|
+
* returned cookie's own signed payload stays valid (future). Cross-process
|
|
193
|
+
* auth resolution therefore passes the
|
|
171
194
|
* cookie-payload gate (`parse_session`) and is refused by the authoritative
|
|
172
195
|
* DB-row gate (`query_session_get_valid` — `WHERE expires_at > NOW()`) —
|
|
173
196
|
* the gate the in-process payload-expiry tests never reach and the one that
|
|
@@ -186,11 +209,19 @@ export const testing_mint_session_action_spec = {
|
|
|
186
209
|
method: '_testing_mint_session',
|
|
187
210
|
kind: 'request_response',
|
|
188
211
|
initiator: 'frontend',
|
|
189
|
-
auth:
|
|
212
|
+
auth: TESTING_ACTION_AUTH,
|
|
190
213
|
side_effects: true,
|
|
191
214
|
input: z.strictObject({
|
|
192
215
|
account_id: Uuid,
|
|
193
|
-
expires_in_seconds: z
|
|
216
|
+
expires_in_seconds: z
|
|
217
|
+
.number()
|
|
218
|
+
.int()
|
|
219
|
+
.negative()
|
|
220
|
+
.meta({
|
|
221
|
+
description: 'Seconds to offset the session row from NOW(). Constrained negative so this ' +
|
|
222
|
+
'backdoor can ONLY mint an already-expired (backdated) row — never a usable ' +
|
|
223
|
+
'session for an arbitrary account. Its sole use is the expired-session gate.',
|
|
224
|
+
}),
|
|
194
225
|
}),
|
|
195
226
|
output: z.strictObject({ session_cookie: z.string() }),
|
|
196
227
|
async: true,
|
|
@@ -223,7 +254,7 @@ export const testing_put_fact_action_spec = {
|
|
|
223
254
|
method: '_testing_put_fact',
|
|
224
255
|
kind: 'request_response',
|
|
225
256
|
initiator: 'frontend',
|
|
226
|
-
auth:
|
|
257
|
+
auth: TESTING_ACTION_AUTH,
|
|
227
258
|
side_effects: true,
|
|
228
259
|
input: z.strictObject({
|
|
229
260
|
content: z.string(),
|
|
@@ -248,7 +279,7 @@ export const testing_schema_snapshot_action_spec = {
|
|
|
248
279
|
method: '_testing_schema_snapshot',
|
|
249
280
|
kind: 'request_response',
|
|
250
281
|
initiator: 'frontend',
|
|
251
|
-
auth:
|
|
282
|
+
auth: TESTING_ACTION_AUTH,
|
|
252
283
|
side_effects: false,
|
|
253
284
|
input: z.strictObject({ exclude_tables: z.array(z.string()).optional() }),
|
|
254
285
|
output: SchemaSnapshot,
|
|
@@ -127,14 +127,25 @@ export interface StartTestingServerOptions {
|
|
|
127
127
|
/** Optional logger; defaults to a `[daemon_name]`-namespaced `Logger`. */
|
|
128
128
|
log?: LoggerType;
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Loopback bind hosts — the only ones the test binary may serve on. It ships
|
|
132
|
+
* deterministic dev secrets (fixed cookie keys + bootstrap token in
|
|
133
|
+
* `default_secrets.ts`), so binding any network-reachable interface would let
|
|
134
|
+
* anyone who knows those fixed keys forge cookies against it. An allowlist
|
|
135
|
+
* (not an `0.0.0.0`/`::` blocklist) closes the gap a concrete LAN/public
|
|
136
|
+
* interface IP — e.g. `--host 192.168.1.50` — would otherwise slip through.
|
|
137
|
+
* Covers `localhost`, the IPv4 loopback `127.0.0.0/8`, and IPv6 `::1`.
|
|
138
|
+
*/
|
|
139
|
+
export declare const is_loopback_host: (host: string) => boolean;
|
|
130
140
|
/**
|
|
131
141
|
* Boot a test-mode server using the supplied runtime adapter.
|
|
132
142
|
*
|
|
133
143
|
* Mirrors a production `start_server` at the surface level — stale-daemon
|
|
134
144
|
* check, daemon-info write, bind, graceful drain — but the app is the
|
|
135
145
|
* caller's no-domain (or domain) {@link StartTestingServerOptions.build_app}
|
|
136
|
-
* and the runtime boundary is the {@link TestingServerAdapter}. Refuses
|
|
137
|
-
* bind
|
|
146
|
+
* and the runtime boundary is the {@link TestingServerAdapter}. Refuses any
|
|
147
|
+
* non-loopback bind host (the test binary must stay on loopback — see
|
|
148
|
+
* `is_loopback_host`).
|
|
138
149
|
*/
|
|
139
150
|
export declare const start_testing_server: (options: StartTestingServerOptions) => Promise<void>;
|
|
140
151
|
//# sourceMappingURL=testing_server_core.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing_server_core.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_core.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,MAAM,CAAC;AACxC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,OAAO,EAAE,WAAW,CAAC;IACrB,6DAA6D;IAC7D,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,mFAAmF;IACnF,iBAAiB,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,iBAAiB,CAAC;IACpD,8EAA8E;IAC9E,KAAK,EAAE,CAAC,OAAO,EAAE;QAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,KAAK,WAAW,CAAC;IACxF,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,yBAAyB,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAClE,oEAAoE;IACpE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC/B,kEAAkE;IAClE,GAAG,EAAE,IAAI,CAAC;IACV,qEAAqE;IACrE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,uEAAuE;IACvE,eAAe,CAAC,EAAE,CAAC,iBAAiB,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAChE;AAED,gDAAgD;AAChD,MAAM,WAAW,yBAAyB;IACzC,+CAA+C;IAC/C,OAAO,EAAE,oBAAoB,CAAC;IAC9B;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,0EAA0E;IAC1E,GAAG,CAAC,EAAE,UAAU,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"testing_server_core.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_server_core.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,IAAI,EAAC,MAAM,MAAM,CAAC;AACxC,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAS,KAAK,MAAM,IAAI,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAG1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IACjC,iBAAiB,EAAE,gBAAgB,CAAC;IACpC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,OAAO,EAAE,WAAW,CAAC;IACrB,6DAA6D;IAC7D,iBAAiB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;IACtD,mFAAmF;IACnF,iBAAiB,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,iBAAiB,CAAC;IACpD,8EAA8E;IAC9E,KAAK,EAAE,CAAC,OAAO,EAAE;QAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,KAAK,WAAW,CAAC;IACxF,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,yBAAyB,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IAClE,oEAAoE;IACpE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC/B,kEAAkE;IAClE,GAAG,EAAE,IAAI,CAAC;IACV,qEAAqE;IACrE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,uEAAuE;IACvE,eAAe,CAAC,EAAE,CAAC,iBAAiB,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAChE;AAED,gDAAgD;AAChD,MAAM,WAAW,yBAAyB;IACzC,+CAA+C;IAC/C,OAAO,EAAE,oBAAoB,CAAC;IAC9B;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1C,0EAA0E;IAC1E,GAAG,CAAC,EAAE,UAAU,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,OAG/C,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,GAAU,SAAS,yBAAyB,KAAG,OAAO,CAAC,IAAI,CA4D3F,CAAC"}
|
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
import { Logger } from '@fuzdev/fuz_util/log.js';
|
|
3
3
|
import { write_daemon_info, read_daemon_info, is_daemon_running } from '../../cli/daemon.js';
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Loopback bind hosts — the only ones the test binary may serve on. It ships
|
|
6
|
+
* deterministic dev secrets (fixed cookie keys + bootstrap token in
|
|
7
|
+
* `default_secrets.ts`), so binding any network-reachable interface would let
|
|
8
|
+
* anyone who knows those fixed keys forge cookies against it. An allowlist
|
|
9
|
+
* (not an `0.0.0.0`/`::` blocklist) closes the gap a concrete LAN/public
|
|
10
|
+
* interface IP — e.g. `--host 192.168.1.50` — would otherwise slip through.
|
|
11
|
+
* Covers `localhost`, the IPv4 loopback `127.0.0.0/8`, and IPv6 `::1`.
|
|
12
|
+
*/
|
|
13
|
+
export const is_loopback_host = (host) => {
|
|
14
|
+
const h = host.replace(/^\[(.*)\]$/, '$1'); // unwrap an `[::1]`-style IPv6 literal
|
|
15
|
+
return h === 'localhost' || h === '::1' || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
|
|
16
|
+
};
|
|
6
17
|
/**
|
|
7
18
|
* Boot a test-mode server using the supplied runtime adapter.
|
|
8
19
|
*
|
|
9
20
|
* Mirrors a production `start_server` at the surface level — stale-daemon
|
|
10
21
|
* check, daemon-info write, bind, graceful drain — but the app is the
|
|
11
22
|
* caller's no-domain (or domain) {@link StartTestingServerOptions.build_app}
|
|
12
|
-
* and the runtime boundary is the {@link TestingServerAdapter}. Refuses
|
|
13
|
-
* bind
|
|
23
|
+
* and the runtime boundary is the {@link TestingServerAdapter}. Refuses any
|
|
24
|
+
* non-loopback bind host (the test binary must stay on loopback — see
|
|
25
|
+
* `is_loopback_host`).
|
|
14
26
|
*/
|
|
15
27
|
export const start_testing_server = async (options) => {
|
|
16
28
|
const { adapter, daemon_name, host, port, app_version, build_app } = options;
|
|
17
29
|
const log = options.log ?? new Logger(`[${daemon_name}]`);
|
|
18
30
|
const { runtime } = adapter;
|
|
19
|
-
if (
|
|
20
|
-
log.error(`FATAL: binding to '${host}' exposes the test binary
|
|
21
|
-
`Use --host localhost (default)
|
|
31
|
+
if (!is_loopback_host(host)) {
|
|
32
|
+
log.error(`FATAL: binding to '${host}' exposes the test binary (which ships deterministic ` +
|
|
33
|
+
`dev secrets) beyond loopback. Use --host localhost (default), 127.0.0.1, or ::1 instead.`);
|
|
22
34
|
adapter.exit(1);
|
|
23
35
|
}
|
|
24
36
|
const stale = await read_daemon_info(runtime, daemon_name);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock_fs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/mock_fs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,MAAM;IACtB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC/C;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,gBAAe,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,KAAG,MAsB3E,CAAC"}
|
|
1
|
+
{"version":3,"file":"mock_fs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/mock_fs.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;GAKG;AAEH,MAAM,WAAW,MAAM;IACtB,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC/C;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,gBAAe,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,KAAG,MAsB3E,CAAC"}
|
package/dist/testing/mock_fs.js
CHANGED
|
@@ -142,6 +142,38 @@ export declare const assert_ws_endpoints_include_protocol_actions: (surface: App
|
|
|
142
142
|
* surface mocks the shape incorrectly.
|
|
143
143
|
*/
|
|
144
144
|
export declare const assert_ws_notifications_have_null_auth: (surface: AppSurface) => void;
|
|
145
|
+
/**
|
|
146
|
+
* Reserved method-name prefix for the daemon-token-gated test-backdoor
|
|
147
|
+
* actions (`_testing_reset`, `_testing_mint_session`, `_testing_put_fact`,
|
|
148
|
+
* `_testing_drain_effects`, `_testing_schema_snapshot`). Test binaries
|
|
149
|
+
* live-mount these on their RPC endpoint but they must never appear on a
|
|
150
|
+
* **declared** surface.
|
|
151
|
+
*/
|
|
152
|
+
export declare const TESTING_METHOD_PREFIX = "_testing_";
|
|
153
|
+
/**
|
|
154
|
+
* No `_testing_*` backdoor action ever appears as a method on the declared
|
|
155
|
+
* surface (RPC or WS).
|
|
156
|
+
*
|
|
157
|
+
* The test-backdoor actions (`_testing_reset` et al.) are daemon-token-gated
|
|
158
|
+
* privileged actions a consumer's test binary appends to its live RPC
|
|
159
|
+
* endpoint at assembly time — but they are deliberately excluded from
|
|
160
|
+
* surface generation (`spine_rpc_endpoints` and every consumer's
|
|
161
|
+
* `create_*_app_surface_spec` omit them) so the published attack surface,
|
|
162
|
+
* the committed `*_attack_surface.json` snapshot, and codegen never carry
|
|
163
|
+
* a backdoor. This invariant is the structural guard against a future
|
|
164
|
+
* wiring change that folds `create_testing_actions(...)` into the *declared*
|
|
165
|
+
* registry instead of the live-only append — the surface stays the
|
|
166
|
+
* authoritative "what the server exposes" map only if a backdoor can never
|
|
167
|
+
* hide in it.
|
|
168
|
+
*
|
|
169
|
+
* Pairs with the wire-level negative-credential check
|
|
170
|
+
* (`describe_testing_backdoor_cross_tests`, cross-process) and the
|
|
171
|
+
* spec-level gate check: this one pins absence-from-surface, those pin
|
|
172
|
+
* the daemon-token gate and the 401/403 behavior.
|
|
173
|
+
*
|
|
174
|
+
* @throws AssertionError naming the offending endpoint + method.
|
|
175
|
+
*/
|
|
176
|
+
export declare const assert_no_testing_methods: (surface: AppSurface) => void;
|
|
145
177
|
/**
|
|
146
178
|
* Configuration for security policy invariants.
|
|
147
179
|
*
|
|
@@ -282,7 +314,8 @@ export declare const assert_surface_invariants: (surface: AppSurface) => void;
|
|
|
282
314
|
* `actions/CLAUDE.md` §Registry compile) — these assertions cover only
|
|
283
315
|
* the contract-surface concerns that a runtime registration check
|
|
284
316
|
* cannot: empty descriptions, missing protocol-action spread on WS
|
|
285
|
-
* endpoints,
|
|
317
|
+
* endpoints, kind ⇔ auth drift on WS methods, and a `_testing_*`
|
|
318
|
+
* backdoor action leaking onto the declared surface.
|
|
286
319
|
*
|
|
287
320
|
* @throws AssertionError on the first invariant violation; the message
|
|
288
321
|
* names the offending endpoint, method, and field.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"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"}
|
|
@@ -452,6 +452,52 @@ export const assert_ws_notifications_have_null_auth = (surface) => {
|
|
|
452
452
|
}
|
|
453
453
|
}
|
|
454
454
|
};
|
|
455
|
+
/**
|
|
456
|
+
* Reserved method-name prefix for the daemon-token-gated test-backdoor
|
|
457
|
+
* actions (`_testing_reset`, `_testing_mint_session`, `_testing_put_fact`,
|
|
458
|
+
* `_testing_drain_effects`, `_testing_schema_snapshot`). Test binaries
|
|
459
|
+
* live-mount these on their RPC endpoint but they must never appear on a
|
|
460
|
+
* **declared** surface.
|
|
461
|
+
*/
|
|
462
|
+
export const TESTING_METHOD_PREFIX = '_testing_';
|
|
463
|
+
/**
|
|
464
|
+
* No `_testing_*` backdoor action ever appears as a method on the declared
|
|
465
|
+
* surface (RPC or WS).
|
|
466
|
+
*
|
|
467
|
+
* The test-backdoor actions (`_testing_reset` et al.) are daemon-token-gated
|
|
468
|
+
* privileged actions a consumer's test binary appends to its live RPC
|
|
469
|
+
* endpoint at assembly time — but they are deliberately excluded from
|
|
470
|
+
* surface generation (`spine_rpc_endpoints` and every consumer's
|
|
471
|
+
* `create_*_app_surface_spec` omit them) so the published attack surface,
|
|
472
|
+
* the committed `*_attack_surface.json` snapshot, and codegen never carry
|
|
473
|
+
* a backdoor. This invariant is the structural guard against a future
|
|
474
|
+
* wiring change that folds `create_testing_actions(...)` into the *declared*
|
|
475
|
+
* registry instead of the live-only append — the surface stays the
|
|
476
|
+
* authoritative "what the server exposes" map only if a backdoor can never
|
|
477
|
+
* hide in it.
|
|
478
|
+
*
|
|
479
|
+
* Pairs with the wire-level negative-credential check
|
|
480
|
+
* (`describe_testing_backdoor_cross_tests`, cross-process) and the
|
|
481
|
+
* spec-level gate check: this one pins absence-from-surface, those pin
|
|
482
|
+
* the daemon-token gate and the 401/403 behavior.
|
|
483
|
+
*
|
|
484
|
+
* @throws AssertionError naming the offending endpoint + method.
|
|
485
|
+
*/
|
|
486
|
+
export const assert_no_testing_methods = (surface) => {
|
|
487
|
+
for (const ep of surface.rpc_endpoints) {
|
|
488
|
+
for (const method of ep.methods) {
|
|
489
|
+
assert.ok(!method.name.startsWith(TESTING_METHOD_PREFIX), `RPC endpoint '${ep.path}' exposes test-backdoor method '${method.name}' on the ` +
|
|
490
|
+
`declared surface — '${TESTING_METHOD_PREFIX}*' actions must be live-mounted only, ` +
|
|
491
|
+
`never folded into the surface-generating registry`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
for (const ep of surface.ws_endpoints) {
|
|
495
|
+
for (const method of ep.methods) {
|
|
496
|
+
assert.ok(!method.name.startsWith(TESTING_METHOD_PREFIX), `WS endpoint '${ep.path}' exposes test-backdoor method '${method.name}' on the ` +
|
|
497
|
+
`declared surface — '${TESTING_METHOD_PREFIX}*' actions must be live-mounted only`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
};
|
|
455
501
|
/** Default patterns for sensitive REST routes that should be rate-limited. */
|
|
456
502
|
const DEFAULT_SENSITIVE_PATTERNS = [
|
|
457
503
|
/\/login$/,
|
|
@@ -637,7 +683,8 @@ export const assert_surface_invariants = (surface) => {
|
|
|
637
683
|
* `actions/CLAUDE.md` §Registry compile) — these assertions cover only
|
|
638
684
|
* the contract-surface concerns that a runtime registration check
|
|
639
685
|
* cannot: empty descriptions, missing protocol-action spread on WS
|
|
640
|
-
* endpoints,
|
|
686
|
+
* endpoints, kind ⇔ auth drift on WS methods, and a `_testing_*`
|
|
687
|
+
* backdoor action leaking onto the declared surface.
|
|
641
688
|
*
|
|
642
689
|
* @throws AssertionError on the first invariant violation; the message
|
|
643
690
|
* names the offending endpoint, method, and field.
|
|
@@ -647,6 +694,7 @@ export const assert_rpc_ws_surface_invariants = (surface) => {
|
|
|
647
694
|
assert_ws_method_descriptions_present(surface);
|
|
648
695
|
assert_ws_endpoints_include_protocol_actions(surface);
|
|
649
696
|
assert_ws_notifications_have_null_auth(surface);
|
|
697
|
+
assert_no_testing_methods(surface);
|
|
650
698
|
};
|
|
651
699
|
/**
|
|
652
700
|
* Run security policy invariants. Configurable with sensible defaults.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAO,MAAM,MAAM,CAAC;AACxC,OAAO,EACN,SAAS,EAET,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EACb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAC/C,OAAO,EAAc,KAAK,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,4BAA4B,CAAC;AAEvD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,kCAAkC,CAAC;AAE7E,OAAO,EAAqB,KAAK,uBAAuB,EAAC,MAAM,kCAAkC,CAAC;AAElG,OAAO,EAAC,yBAAyB,EAAC,MAAM,qCAAqC,CAAC;AAC9E,OAAO,EAAsB,KAAK,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAEpF,OAAO,EAKN,KAAK,cAAc,EACnB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAKN,KAAK,QAAQ,EACb,MAAM,2BAA2B,CAAC;AAMnC;;;GAGG;AACH,MAAM,WAAW,MAAM;IACtB,EAAE,EAAE,SAAS,CAAC;IACd,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,EAAE,KAAK,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CAChD;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,QAAO,MAajC,CAAC;AAEF,8CAA8C;AAC9C,MAAM,WAAW,sBAAsB;IACtC,eAAe,EAAE,cAAc,CAAC;IAChC,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;OAGG;IACH,eAAe,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,sBAAsB,KAAG,OAavE,CAAC;AAEF,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,iBAAiB,EAAE,MAAM,CAAC,CAAC,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtE;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAAO,WAatC,CAAC;AAEF;;;;GAIG;AACH,qBAAa,wBAAyB,YAAW,sBAAsB;;IACtE,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAa;gBAEjC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC;IAGjD,qBAAqB,IAAI,SAAS;IAGlC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;CAG/D;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC/B,YAAY,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,EAC9C,OAAO,YAAY,EACnB,IAAI,SAAS,KACX,OAAO,CAAC,IAAI,CAId,CAAC;AAMF,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IACjC,wEAAwE;IACxE,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,yFAAyF;IACzF,eAAe,CAAC,EAAE,cAAc,CAAC;IACjC,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,sFAAsF;IACtF,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtB;AAED,4CAA4C;AAC5C,MAAM,WAAW,0BAA0B;IAC1C;;;;;OAKG;IACH,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B,kEAAkE;IAClE,SAAS,CAAC,EAAE,yBAAyB,CAAC;IACtC;;;;OAIG;IACH,SAAS,CAAC,EAAE,uBAAuB,CAAC,WAAW,CAAC,CAAC;IACjD,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,cAAc,CAAC,EAAE,uBAAuB,CAAC,gBAAgB,CAAC,CAAC;IAC3D,yDAAyD;IACzD,eAAe,CAAC,EAAE,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;CAC7D;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE,yBAAyB,CAAC;IACrC;;;;;;;;;;OAUG;IACH,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC7D;AAiED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,0BAA0B,KAAG,aA0M5E,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,eAAe,QAAO,iBAGjC,CAAC;AAYH;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB,GAAI,IAAI,SAAS,MAAM,EAAE,SAAS;IACjE,OAAO,EAAE,aAAa,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CACtC,KAAG,IAIH,CAAC"}
|
|
@@ -1,41 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* In-process test helpers for WebSocket JSON-RPC round-trips.
|
|
3
|
-
*
|
|
4
|
-
* Drives `register_action_ws` without an HTTP server. Consumers supply
|
|
5
|
-
* specs + handlers; the harness constructs real `WSContext` instances
|
|
6
|
-
* backed by test-owned `send`/`close` pairs, fakes the authenticated
|
|
7
|
-
* Hono context (`request_context`, credential type, session id, api
|
|
8
|
-
* token id), and exposes a `connect()` factory returning a `WsClient`
|
|
9
|
-
* per connection.
|
|
10
|
-
*
|
|
11
|
-
* Three layers are exported:
|
|
12
|
-
*
|
|
13
|
-
* - **Primitives** (`create_fake_ws`, `create_fake_hono_context`,
|
|
14
|
-
* `create_stub_upgrade`, `MinimalActionEnvironment`,
|
|
15
|
-
* `dispatch_ws_message`) — used by fuz_app's own dispatcher tests
|
|
16
|
-
* and by consumers wiring tight one-off tests.
|
|
17
|
-
* - **Harness** (`create_ws_test_harness`, `keeper_identity`) — the
|
|
18
|
-
* high-level driver. Give it specs + handlers, get back
|
|
19
|
-
* `{transport, connect()}`. `connect()` is async and resolves after
|
|
20
|
-
* `on_socket_open` completes, so broadcasts sent immediately after
|
|
21
|
-
* `await harness.connect()` reach the client. Returns a `WsClient`
|
|
22
|
-
* (shared interface — see `transports/ws_client.ts`); the same
|
|
23
|
-
* interface is implemented by `transports/ws_transport.ts` for
|
|
24
|
-
* cross-process tests.
|
|
25
|
-
* - **Broadcast wiring** — `build_broadcast_api` for wiring a typed
|
|
26
|
-
* broadcast API against the harness's transport. Wire-frame types
|
|
27
|
-
* + predicates (`is_notification`, `is_response_for`,
|
|
28
|
-
* `JsonrpcNotificationFrame`, ...) live in `transports/ws_client.ts`
|
|
29
|
-
* so both in-process and cross-process drivers reference one source.
|
|
30
|
-
*
|
|
31
|
-
* Hono's wire upgrade is skipped — the Node test runtime has no
|
|
32
|
-
* `@hono/node-ws` adapter — but the full dispatch path is exercised
|
|
33
|
-
* (per-action auth, input validation, `ctx.notify` back to the
|
|
34
|
-
* originating socket, broadcast via `BackendWebsocketTransport`, and
|
|
35
|
-
* close-on-revoke).
|
|
36
|
-
*
|
|
37
|
-
* @module
|
|
38
|
-
*/
|
|
1
|
+
import './assert_dev_env.js';
|
|
39
2
|
import { WSContext, createWSMessageEvent, } from 'hono/ws';
|
|
40
3
|
import { Logger } from '@fuzdev/fuz_util/log.js';
|
|
41
4
|
import { create_uuid } from '@fuzdev/fuz_util/id.js';
|