@fuzdev/fuz_app 0.63.0 → 0.64.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.
- package/dist/actions/CLAUDE.md +124 -11
- package/dist/actions/connection_closer.d.ts +68 -0
- package/dist/actions/connection_closer.d.ts.map +1 -0
- package/dist/actions/connection_closer.js +41 -0
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +23 -2
- package/dist/actions/register_ws_endpoint.d.ts +11 -9
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +5 -5
- package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +23 -7
- package/dist/actions/ws_endpoint_spec.d.ts +119 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
- package/dist/actions/ws_endpoint_spec.js +13 -0
- package/dist/auth/CLAUDE.md +79 -15
- package/dist/auth/account_action_specs.d.ts +1 -1
- package/dist/auth/account_actions.d.ts +13 -0
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +31 -1
- package/dist/auth/account_routes.d.ts +12 -2
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +55 -8
- package/dist/auth/account_schema.d.ts +3 -3
- package/dist/auth/admin_action_specs.d.ts +8 -8
- package/dist/auth/admin_actions.d.ts +11 -0
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +25 -0
- package/dist/auth/audit_emitter.d.ts +56 -12
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +38 -12
- package/dist/auth/audit_log_schema.d.ts +5 -3
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +5 -3
- package/dist/auth/bootstrap_routes.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts +2 -2
- package/dist/auth/signup_routes.d.ts +1 -1
- package/dist/auth/standard_rpc_actions.d.ts +1 -0
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +1 -0
- package/dist/http/CLAUDE.md +26 -10
- package/dist/http/ip_canonical.d.ts +99 -0
- package/dist/http/ip_canonical.d.ts.map +1 -0
- package/dist/http/ip_canonical.js +191 -0
- package/dist/http/origin.d.ts +13 -5
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +13 -31
- package/dist/http/pending_effects.d.ts +1 -1
- package/dist/http/pending_effects.js +1 -1
- package/dist/http/proxy.d.ts +13 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +15 -23
- package/dist/http/surface.d.ts +50 -0
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +27 -1
- package/dist/primitive_schemas.d.ts +20 -4
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +25 -4
- package/dist/realtime/sse_auth_guard.d.ts +16 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +15 -3
- package/dist/server/app_backend.d.ts +66 -19
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +57 -34
- package/dist/server/app_server.d.ts +60 -0
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +95 -2
- package/dist/server/startup.d.ts.map +1 -1
- package/dist/server/startup.js +12 -0
- package/dist/testing/CLAUDE.md +64 -28
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +4 -5
- package/dist/testing/adversarial_headers.d.ts +6 -0
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -5
- package/dist/testing/app_server.d.ts +33 -32
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +4 -13
- package/dist/testing/attack_surface.d.ts +8 -7
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +12 -8
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +3 -5
- package/dist/testing/audit_drift_guard.d.ts +116 -0
- package/dist/testing/audit_drift_guard.d.ts.map +1 -0
- package/dist/testing/audit_drift_guard.js +134 -0
- package/dist/testing/connection_closer_helpers.d.ts +44 -0
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
- package/dist/testing/connection_closer_helpers.js +48 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +7 -9
- package/dist/testing/rate_limiting.js +4 -4
- package/dist/testing/rpc_helpers.d.ts +2 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +6 -8
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +12 -6
- package/dist/testing/stubs.d.ts +11 -0
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +4 -0
- package/dist/testing/surface_invariants.d.ts +66 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +103 -1
- package/dist/ui/SurfaceExplorer.svelte +161 -2
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -22,21 +22,21 @@ Enforced by grep, not a linter; make this the first line in new modules.
|
|
|
22
22
|
|
|
23
23
|
### `stubs.ts` — `AppDeps` + `AppServerContext` stubs
|
|
24
24
|
|
|
25
|
-
| Helper | Role
|
|
26
|
-
| ----------------------------------------------------- |
|
|
27
|
-
| `create_throwing_stub<T>(label)` | Proxy whose every property access throws `Throwing stub 'label' — unexpected access to 'prop'`; JS-internal probes return `undefined`; `toJSON` returns `"[throwing_stub:label]"` so accidental serialization is visible rather than `{}`.
|
|
28
|
-
| `create_noop_stub<T>(label, overrides?)` | Proxy whose every method returns `async () => undefined`; `overrides` lets callers pin specific props.
|
|
29
|
-
| `stub` | Pre-built throwing stub labelled `'stub'`.
|
|
30
|
-
| `create_stub_db()` | Returns a real `Db` whose `client.query` yields `{rows: []}` and whose `transaction(fn)` synchronously calls `fn(inner_stub_db)`. Safe for `apply_route_specs`'s declarative transaction wrapper.
|
|
31
|
-
| `stub_handler()` | Returns a fresh `Response('stub')`.
|
|
32
|
-
| `stub_mw` | Pass-through middleware handler (`async (_c, next) => next()`).
|
|
33
|
-
| `stub_app_deps` | Frozen `AppDeps` — every capability is a throwing stub, `audit` is a no-op `AuditEmitter` from `create_test_audit_emitter`.
|
|
34
|
-
| `create_stub_app_deps()` | Factory returning fresh `AppDeps` with no-op FS/keyring/password, a `create_noop_stub` DB, silent `Logger`, no-op `audit`.
|
|
35
|
-
| `create_test_audit_emitter()` | No-op `AuditEmitter` for tests that don't assert on audit fan-out. `emit` / `emit_role_grant_target` are no-ops; `emit_pool` resolves immediately; `notify` is a no-op; `on_event_chain` is empty.
|
|
36
|
-
| `create_stub_audit_sse()` | No-op `AuditLogSse` for surface-test wiring without booting real SSE. `subscribe` returns a no-op cleanup; `on_audit_event` is a no-op; the `registry` is a fresh `SubscriberRegistry` (live `.size` / `.close_*` for tests touching registry state, isolated per call). For real SSE plumbing, build via `create_audit_log_sse` against `create_test_app`.
|
|
37
|
-
| `create_stub_api_middleware({include_daemon_token?})` | Stub `MiddlewareSpec[]` matching `create_auth_middleware_specs`'s output (origin/session/request_context/bearer_auth, optional daemon_token) for surface generation without booting real auth. See `auth/CLAUDE.md` §Middleware for the real stack.
|
|
38
|
-
| `create_stub_app_server_context(session_options)` | Stub `AppServerContext` — rate limiters null, `bootstrap_status.available: false`, `app_settings.open_signup: false`.
|
|
39
|
-
| `create_test_app_surface_spec(options)` | Builds an `AppSurfaceSpec` that mirrors `create_app_server`'s route assembly: consumer routes + factory-managed bootstrap routes (prefixed via `bootstrap_route_prefix`, default `'/api/account'`) + stub middleware + surface generation. `CreateTestAppSurfaceSpecOptions` accepts `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `transform_middleware?`, `bootstrap_route_prefix?`. Single source of truth for attack-surface tests — track `create_app_server` wiring changes here. |
|
|
25
|
+
| Helper | Role |
|
|
26
|
+
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `create_throwing_stub<T>(label)` | Proxy whose every property access throws `Throwing stub 'label' — unexpected access to 'prop'`; JS-internal probes return `undefined`; `toJSON` returns `"[throwing_stub:label]"` so accidental serialization is visible rather than `{}`. |
|
|
28
|
+
| `create_noop_stub<T>(label, overrides?)` | Proxy whose every method returns `async () => undefined`; `overrides` lets callers pin specific props. |
|
|
29
|
+
| `stub` | Pre-built throwing stub labelled `'stub'`. |
|
|
30
|
+
| `create_stub_db()` | Returns a real `Db` whose `client.query` yields `{rows: []}` and whose `transaction(fn)` synchronously calls `fn(inner_stub_db)`. Safe for `apply_route_specs`'s declarative transaction wrapper. |
|
|
31
|
+
| `stub_handler()` | Returns a fresh `Response('stub')`. |
|
|
32
|
+
| `stub_mw` | Pass-through middleware handler (`async (_c, next) => next()`). |
|
|
33
|
+
| `stub_app_deps` | Frozen `AppDeps` — every capability is a throwing stub, `audit` is a no-op `AuditEmitter` from `create_test_audit_emitter`. |
|
|
34
|
+
| `create_stub_app_deps()` | Factory returning fresh `AppDeps` with no-op FS/keyring/password, a `create_noop_stub` DB, silent `Logger`, no-op `audit`. |
|
|
35
|
+
| `create_test_audit_emitter()` | No-op `AuditEmitter` for tests that don't assert on audit fan-out. `emit` / `emit_role_grant_target` are no-ops; `emit_pool` resolves immediately; `notify` is a no-op; `on_event_chain` is empty. |
|
|
36
|
+
| `create_stub_audit_sse()` | No-op `AuditLogSse` for surface-test wiring without booting real SSE. `subscribe` returns a no-op cleanup; `on_audit_event` is a no-op; the `registry` is a fresh `SubscriberRegistry` (live `.size` / `.close_*` for tests touching registry state, isolated per call). For real SSE plumbing, build via `create_audit_log_sse` against `create_test_app`. |
|
|
37
|
+
| `create_stub_api_middleware({include_daemon_token?})` | Stub `MiddlewareSpec[]` matching `create_auth_middleware_specs`'s output (origin/session/request_context/bearer_auth, optional daemon_token) for surface generation without booting real auth. See `auth/CLAUDE.md` §Middleware for the real stack. |
|
|
38
|
+
| `create_stub_app_server_context(session_options)` | Stub `AppServerContext` — rate limiters null, `bootstrap_status.available: false`, `app_settings.open_signup: false`. |
|
|
39
|
+
| `create_test_app_surface_spec(options)` | Builds an `AppSurfaceSpec` that mirrors `create_app_server`'s route assembly: consumer routes + factory-managed bootstrap routes (prefixed via `bootstrap_route_prefix`, default `'/api/account'`) + stub middleware + surface generation. `CreateTestAppSurfaceSpecOptions` accepts `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `ws_endpoints?`, `transform_middleware?`, `bootstrap_route_prefix?`. Single source of truth for attack-surface tests — track `create_app_server` wiring changes here. |
|
|
40
40
|
|
|
41
41
|
Throwing stubs surface mock escape: a test that accidentally reaches into
|
|
42
42
|
stub territory breaks immediately with a label-scoped error rather than
|
|
@@ -82,6 +82,25 @@ DB rows but not a full session/token bundle. For tests that also need
|
|
|
82
82
|
an API token + session cookie + role_grants, use `bootstrap_test_account`
|
|
83
83
|
from `app_server.ts` instead.
|
|
84
84
|
|
|
85
|
+
### `audit_drift_guard.ts` — audit-emission validation
|
|
86
|
+
|
|
87
|
+
| Helper | Role |
|
|
88
|
+
| ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
89
|
+
| `install_audit_drift_guard()` | `beforeEach` resets + `afterEach` zero-checks the `audit_metadata_validation_failures` + `audit_unknown_event_type_failures` counters from `auth/audit_log_queries.ts`. Call once at the top of any `describe_db` block that fires audit emits — production validation is fail-open, so without this any regression that ships a typo'd `event_type` or an undeclared metadata field is silent. Pair with `await_pending_effects: true` (the `create_test_app` default) so fire-and-forget audit writes have completed by response time. |
|
|
90
|
+
| `create_emit_ordering_audit_factory<E>(seq_ref, events_ref, build_inner)` | Returns an `AuditFactory` that wraps the result of `build_inner({db, log})` so every `emit` call pushes `{kind: 'emit', at: seq.value++}` into a shared sequence + events array. Pass through `create_test_app({audit_factory: …})` — the test backend invokes it with its constructed `{db, log}` and lands the wrapped emitter on `deps.audit`. Generic `E extends {kind: string; at: number}` so the events array typechecks against the caller's own `close` / custom marker shape. Pair with `create_recording_closer(seq_ref)` (in `connection_closer_helpers.ts`) for close-vs-emit ordering tests. Scope is `emit` only — `emit_role_grant_target`, `emit_pool`, `notify` forward to the inner emitter unwrapped (same caveat as the previous `patch_audit_emit_capture`). |
|
|
91
|
+
| `AuditEmitMarker` | `{kind: 'emit'; at: number}` — the type of marker `create_emit_ordering_audit_factory` pushes. |
|
|
92
|
+
| `create_recording_audit_emitter(calls_ref?)` | Build a no-op `AuditEmitter` that pushes every `emit` and `emit_pool` call into `calls`. Pass `calls_ref` to write into a caller-owned array; omit to let the helper allocate one. Returns `{emitter, calls}` — destructure `emitter` as the `audit` dep and read `calls` to assert on captured metadata. Replaces per-file capturing emitters previously duplicated across `password_change.test.ts`, `audit_log.test.ts`, etc. |
|
|
93
|
+
| `RecordingAuditEmitter` | `{emitter: AuditEmitter; calls: Array<AuditLogInput>}` — return shape of `create_recording_audit_emitter`. |
|
|
94
|
+
|
|
95
|
+
### `connection_closer_helpers.ts` — `ConnectionCloser` test doubles
|
|
96
|
+
|
|
97
|
+
| Helper | Role |
|
|
98
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| `create_recording_closer(seq_ref?)` | Returns `{closer, calls}` where every method on `closer` records `{method, id, at}` into `calls`. Pass `seq_ref` to share the sequence counter with `create_emit_ordering_audit_factory` so close + emit markers compose for ordering tests. |
|
|
100
|
+
| `assert_close_call(call, method, id)` | Pins `{method, id}` on a single recorded close call without baking in the `at: N` sequence number. Use at every "did the closer fire?" assertion site; reserve `at: N` assertions for the dedicated ordering test paired with the capture helper. |
|
|
101
|
+
| `RecordedClose` | `{method: 'session' \| 'token' \| 'account', id, at}` — recorded shape pushed by the closer. |
|
|
102
|
+
| `RecordingCloser` | `{closer, calls}` — return shape of `create_recording_closer`. |
|
|
103
|
+
|
|
85
104
|
## Database — `db.ts`
|
|
86
105
|
|
|
87
106
|
Factory builders for parameterized DB tests. Consumer projects pass their
|
|
@@ -140,13 +159,13 @@ Key module-scope values:
|
|
|
140
159
|
"insert account + actor + roles + API token + session + cookie" flow.
|
|
141
160
|
Takes `{db, keyring, session_options, password, username?, password_value?, roles?}`.
|
|
142
161
|
|
|
143
|
-
| Type | Shape
|
|
144
|
-
| --------------------------------------------------- |
|
|
145
|
-
| `TestAppServer extends AppBackend` | Adds `account`, `actor`, `api_token`, `session_cookie`, `keyring`, `cleanup()`.
|
|
146
|
-
| `TestAppServerOptions` | `session_options` (required), optional `db`, `db_type`, `password`, `username`, `password_value`, `roles`, `on_audit_event`, `audit_log_config
|
|
147
|
-
| `CreateTestAppOptions extends TestAppServerOptions` | Adds `create_route_specs` (required)
|
|
148
|
-
| `TestAccount` | `{account, actor, session_cookie, api_token, create_session_headers, create_bearer_headers}`.
|
|
149
|
-
| `TestApp` | `{app, backend, surface_spec, surface, route_specs, create_session_headers, create_bearer_headers, create_daemon_token_headers, create_account, cleanup}`.
|
|
162
|
+
| Type | Shape |
|
|
163
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
164
|
+
| `TestAppServer extends AppBackend` | Adds `account`, `actor`, `api_token`, `session_cookie`, `keyring`, `cleanup()`. |
|
|
165
|
+
| `TestAppServerOptions` | `session_options` (required), optional `db`, `db_type`, `password`, `username`, `password_value`, `roles`, `audit_factory`. The optional `audit_factory` defaults to `default_audit_factory` (no-listener `create_audit_emitter` over the test backend's `{db, log}`); pass a custom factory to compose `on_audit_event` / `audit_log_config`, wrap with `emit_decorator` (via `create_emit_ordering_audit_factory`), or otherwise replace the emitter. Mirrors `CreateAppBackendOptions` end-to-end — the previous `on_audit_event` / `audit_log_config` sugar was removed alongside the production rename. |
|
|
166
|
+
| `CreateTestAppOptions extends TestAppServerOptions` | Adds `create_route_specs` (required), `rpc_endpoints?: RpcEndpointsSuiteOption` (top-level only — single source of truth, symmetric with the suite-level option), and `app_options?: SuiteAppOptions` (`Partial<AppServerOptions>` excluding the four fields the helper manages: `backend`, `session_options`, `create_route_specs`, `rpc_endpoints`). |
|
|
167
|
+
| `TestAccount` | `{account, actor, session_cookie, api_token, create_session_headers, create_bearer_headers}`. |
|
|
168
|
+
| `TestApp` | `{app, backend, surface_spec, surface, route_specs, create_session_headers, create_bearer_headers, create_daemon_token_headers, create_account, cleanup}`. |
|
|
150
169
|
|
|
151
170
|
`create_test_app` hard-codes the test-friendly `AppServerOptions`:
|
|
152
171
|
`allowed_origins: [/^http:\/\/localhost/]`, stub proxy pinned to
|
|
@@ -213,6 +232,21 @@ Structural invariants (options-free, apply universally):
|
|
|
213
232
|
| `assert_error_code_status_consistency` | The same `z.literal()` error code never appears at two different HTTP statuses. |
|
|
214
233
|
| `assert_404_schemas_use_specific_errors` | Routes with params declaring 404 must use `z.literal()` or `z.enum()`, not generic `z.string()`. |
|
|
215
234
|
|
|
235
|
+
RPC / WS structural invariants (options-free, apply universally over
|
|
236
|
+
`surface.rpc_endpoints` + `surface.ws_endpoints`):
|
|
237
|
+
|
|
238
|
+
| Assertion | Checks |
|
|
239
|
+
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
240
|
+
| `assert_rpc_method_descriptions_present` | Every RPC method on every endpoint has a non-empty `description`. |
|
|
241
|
+
| `assert_ws_method_descriptions_present` | Every WS method on every endpoint has a non-empty `description`. |
|
|
242
|
+
| `assert_ws_endpoints_include_protocol_actions` | Every WS endpoint includes `heartbeat` + `cancel` (the `protocol_actions` spread from `actions/protocol.js`). |
|
|
243
|
+
| `assert_ws_notifications_have_null_auth` | WS method `kind === 'remote_notification' ⟺ auth === null` — guards against drift between spec union and surface emitter. |
|
|
244
|
+
|
|
245
|
+
Per-endpoint duplicate method names and the auth-shape biconditional are
|
|
246
|
+
already enforced at startup by `compile_action_registry` (see
|
|
247
|
+
`actions/CLAUDE.md` §Registry compile) — these assertions only cover
|
|
248
|
+
contract-surface concerns a runtime registration check cannot reach.
|
|
249
|
+
|
|
216
250
|
Policy invariants (configurable, sensible defaults):
|
|
217
251
|
|
|
218
252
|
| Assertion | Checks |
|
|
@@ -248,7 +282,8 @@ Tightness audit:
|
|
|
248
282
|
|
|
249
283
|
Aggregate runners (called by the standard attack-surface suite):
|
|
250
284
|
|
|
251
|
-
- `assert_surface_invariants(surface)` — runs all structural assertions.
|
|
285
|
+
- `assert_surface_invariants(surface)` — runs all route-level structural assertions.
|
|
286
|
+
- `assert_rpc_ws_surface_invariants(surface)` — runs all RPC/WS structural assertions.
|
|
252
287
|
- `assert_surface_security_policy(surface, options?)` — runs all policy assertions.
|
|
253
288
|
|
|
254
289
|
### `error_coverage.ts` — reachability tracking
|
|
@@ -331,7 +366,7 @@ Single-call bundle of 5 top-level groups (10 named tests + every
|
|
|
331
366
|
adversarial case per route):
|
|
332
367
|
|
|
333
368
|
1. **attack surface snapshot** — `matches committed snapshot`, `is deterministic`.
|
|
334
|
-
2. **attack surface structure** — `only expected public routes`, `full middleware stack on API routes`, `surface invariants`, `security policy`, `error schema tightness` (logs counts and asserts against `default_error_schema_tightness` by default; pass an override config or `null` via `error_schema_tightness`).
|
|
369
|
+
2. **attack surface structure** — `only expected public routes`, `full middleware stack on API routes`, `surface invariants`, `rpc/ws surface invariants`, `security policy`, `error schema tightness` (logs counts and asserts against `default_error_schema_tightness` by default; pass an override config or `null` via `error_schema_tightness`).
|
|
335
370
|
3. **adversarial HTTP auth enforcement** — `unauthenticated → 401`, `wrong role → 403` × roles, `authenticated without role → 403`, `keeper routes reject session credential → 403`, `correct auth passes guard`.
|
|
336
371
|
4. **adversarial input validation** — delegated to `describe_adversarial_input`.
|
|
337
372
|
5. **adversarial 404 response validation** — delegated to `describe_adversarial_404`.
|
|
@@ -384,8 +419,8 @@ body matches the declared 404 Zod schema. No DB needed.
|
|
|
384
419
|
3. no auth headers → passes through
|
|
385
420
|
4. bearer + empty Origin → 403 `ERROR_FORBIDDEN_ORIGIN` (defense-in-depth)
|
|
386
421
|
5. lowercase `bearer` scheme → RFC 7235 §2.1 soft-fail
|
|
387
|
-
6. bearer + rogue Referer →
|
|
388
|
-
7. bearer + allowed Referer → bearer silently discarded
|
|
422
|
+
6. bearer + rogue Referer (no Origin) → passes origin check (Origin-only posture), bearer silently discarded (Referer is still a browser-context indicator for bearer auth)
|
|
423
|
+
7. bearer + allowed Referer (no Origin) → bearer silently discarded (browser context)
|
|
389
424
|
|
|
390
425
|
Each case declares `validate_expectation: 'called' | 'not_called'` so the
|
|
391
426
|
suite asserts that short-circuit middleware actually fires before token
|
|
@@ -575,8 +610,9 @@ Required options: `{session_options, create_route_specs, roles: RoleSchemaResult
|
|
|
575
610
|
the same `RpcEndpointsSuiteOption` union every DB-backed suite accepts
|
|
576
611
|
(`integration`, `admin_integration`, `audit_completeness`, `rate_limiting`,
|
|
577
612
|
`rpc_round_trip`, `sse_round_trip`). Prefer the factory form: it forwards
|
|
578
|
-
raw to `
|
|
579
|
-
with the real ctx — the only way
|
|
613
|
+
raw to the top-level `rpc_endpoints` slot on `CreateTestAppOptions` so
|
|
614
|
+
`create_app_server` resolves it per-test with the real ctx — the only way
|
|
615
|
+
action handlers can close over
|
|
580
616
|
`ctx.deps` / `ctx.app_settings` (e.g. `create_standard_rpc_actions(ctx.deps,
|
|
581
617
|
{app_settings: ctx.app_settings})`). Factory must return the same endpoint
|
|
582
618
|
`path` regardless of ctx — `resolve_rpc_endpoints_for_setup` invokes it
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAA0B,KAAK,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAGtF,OAAO,EAA6C,KAAK,eAAe,EAAC,MAAM,iBAAiB,CAAC;AACjG,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AASjB,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAoB1B;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAA0B,KAAK,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAGtF,OAAO,EAA6C,KAAK,eAAe,EAAC,MAAM,iBAAiB,CAAC;AACjG,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AASjB,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAoB1B;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAiCD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IAg2BF,CAAC"}
|
|
@@ -61,10 +61,8 @@ const build_admin_test_app_options = (options, db, roles) => ({
|
|
|
61
61
|
create_route_specs: options.create_route_specs,
|
|
62
62
|
db,
|
|
63
63
|
roles: roles ?? [ROLE_KEEPER, ROLE_ADMIN],
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
rpc_endpoints: options.rpc_endpoints,
|
|
67
|
-
},
|
|
64
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
65
|
+
app_options: options.app_options,
|
|
68
66
|
});
|
|
69
67
|
/**
|
|
70
68
|
* Standard admin integration test suite for fuz_app admin routes.
|
|
@@ -84,7 +82,8 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
84
82
|
// Hard-fail early so consumers see a clear setup error instead of a
|
|
85
83
|
// confusing test failure when `rpc_endpoints` is missing. Factory-form
|
|
86
84
|
// callers are resolved with a stub ctx purely to extract the endpoint
|
|
87
|
-
// path; real handlers run per-test via `
|
|
85
|
+
// path; real handlers run per-test via the top-level `rpc_endpoints`
|
|
86
|
+
// slot on `CreateTestAppOptions`.
|
|
88
87
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
89
88
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
90
89
|
const init_schema = async (db) => {
|
|
@@ -15,6 +15,12 @@ export interface AdversarialHeaderCase {
|
|
|
15
15
|
/**
|
|
16
16
|
* 7 standard adversarial header cases applicable to any middleware stack.
|
|
17
17
|
*
|
|
18
|
+
* Origin verification is Origin-only — fuz_app's `verify_request_source`
|
|
19
|
+
* no longer falls back to `Referer` (matches `zzz_server::auth::is_request_origin_allowed`).
|
|
20
|
+
* Bearer auth still treats a `Referer` header as a browser-context
|
|
21
|
+
* indicator and silently discards the bearer token — so Referer-bearing
|
|
22
|
+
* requests reach the route as unauthenticated rather than 403.
|
|
23
|
+
*
|
|
18
24
|
* @param allowed_origin - an origin that passes the origin check
|
|
19
25
|
*/
|
|
20
26
|
export declare const create_standard_adversarial_cases: (allowed_origin: string) => Array<AdversarialHeaderCase>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAIzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID
|
|
1
|
+
{"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAIzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,GAC7C,gBAAgB,MAAM,KACpB,KAAK,CAAC,qBAAqB,CAiE7B,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qCAAqC,GACjD,YAAY,MAAM,EAClB,SAAS,0BAA0B,EACnC,gBAAgB,MAAM,EACtB,cAAc,KAAK,CAAC,qBAAqB,CAAC,KACxC,IAkCF,CAAC"}
|
|
@@ -8,12 +8,18 @@ import './assert_dev_env.js';
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import { test, assert, describe } from 'vitest';
|
|
11
|
-
import { ApiError, ERROR_FORBIDDEN_ORIGIN
|
|
11
|
+
import { ApiError, ERROR_FORBIDDEN_ORIGIN } from '../http/error_schemas.js';
|
|
12
12
|
import { create_test_middleware_stack_app, TEST_MIDDLEWARE_PATH, } from './middleware.js';
|
|
13
13
|
// --- Standard adversarial header cases ---
|
|
14
14
|
/**
|
|
15
15
|
* 7 standard adversarial header cases applicable to any middleware stack.
|
|
16
16
|
*
|
|
17
|
+
* Origin verification is Origin-only — fuz_app's `verify_request_source`
|
|
18
|
+
* no longer falls back to `Referer` (matches `zzz_server::auth::is_request_origin_allowed`).
|
|
19
|
+
* Bearer auth still treats a `Referer` header as a browser-context
|
|
20
|
+
* indicator and silently discards the bearer token — so Referer-bearing
|
|
21
|
+
* requests reach the route as unauthenticated rather than 403.
|
|
22
|
+
*
|
|
17
23
|
* @param allowed_origin - an origin that passes the origin check
|
|
18
24
|
*/
|
|
19
25
|
export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
@@ -61,17 +67,19 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
61
67
|
validate_expectation: 'called',
|
|
62
68
|
},
|
|
63
69
|
{
|
|
64
|
-
name: 'bearer token with Referer
|
|
70
|
+
name: 'bearer token with rogue Referer (no Origin) passes origin check, bearer silently discarded (browser-context indicator)',
|
|
71
|
+
// Origin-only verification ignores Referer entirely. Bearer auth still
|
|
72
|
+
// treats Referer presence as a browser-context indicator and discards
|
|
73
|
+
// the token, so the request reaches the route as unauthenticated.
|
|
65
74
|
headers: {
|
|
66
75
|
Authorization: 'Bearer secret_fuz_token_test',
|
|
67
76
|
Referer: 'https://attacker.com/page',
|
|
68
77
|
},
|
|
69
|
-
expected_status:
|
|
70
|
-
expected_error: ERROR_FORBIDDEN_REFERER,
|
|
78
|
+
expected_status: 200,
|
|
71
79
|
validate_expectation: 'not_called',
|
|
72
80
|
},
|
|
73
81
|
{
|
|
74
|
-
name: 'bearer token with Referer
|
|
82
|
+
name: 'bearer token with allowed Referer (no Origin) — bearer silently discarded (browser context)',
|
|
75
83
|
headers: {
|
|
76
84
|
Authorization: 'Bearer secret_fuz_token_test',
|
|
77
85
|
Referer: `${allowed_origin}/page`,
|
|
@@ -18,8 +18,7 @@ import { type Keyring } from '../auth/keyring.js';
|
|
|
18
18
|
import type { Db, DbType } from '../db/db.js';
|
|
19
19
|
import type { PasswordHashDeps } from '../auth/password.js';
|
|
20
20
|
import { type SessionOptions } from '../auth/session_cookie.js';
|
|
21
|
-
import type
|
|
22
|
-
import type { AppBackend } from '../server/app_backend.js';
|
|
21
|
+
import { type AppBackend, type AuditFactory } from '../server/app_backend.js';
|
|
23
22
|
import { type AppServerOptions, type AppServerContext } from '../server/app_server.js';
|
|
24
23
|
import type { AppSurface, AppSurfaceSpec } from '../http/surface.js';
|
|
25
24
|
import type { RouteSpec } from '../http/route_spec.js';
|
|
@@ -107,23 +106,21 @@ export interface TestAppServerOptions {
|
|
|
107
106
|
/** Roles to grant. Default: `[ROLE_KEEPER]`. */
|
|
108
107
|
roles?: Array<string>;
|
|
109
108
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
* Optional audit log config — threaded into `create_audit_emitter` and
|
|
119
|
-
* captured inside `backend.deps.audit`'s closure.
|
|
109
|
+
* Build the bound `AuditEmitter` used by the test backend. Defaults to
|
|
110
|
+
* `default_audit_factory` (a no-listener `create_audit_emitter` over
|
|
111
|
+
* the test backend's `{db, log}`). Pass a custom factory when a test
|
|
112
|
+
* needs:
|
|
113
|
+
* - to capture audit events (compose `on_audit_event` inside the body)
|
|
114
|
+
* - to register consumer event-type schemas (pass `audit_log_config`)
|
|
115
|
+
* - to instrument `emit` ordering (`create_emit_ordering_audit_factory`)
|
|
116
|
+
* - to wrap or replace the emitter for some other reason
|
|
120
117
|
*
|
|
121
|
-
*
|
|
122
|
-
* `
|
|
123
|
-
*
|
|
124
|
-
*
|
|
118
|
+
* Matches the production shape — `create_app_backend` requires an
|
|
119
|
+
* `audit_factory` and `create_test_app_server` mirrors that contract
|
|
120
|
+
* end-to-end. The earlier `on_audit_event` / `audit_log_config` sugar
|
|
121
|
+
* fields were removed alongside the `CreateAppBackendOptions` rename.
|
|
125
122
|
*/
|
|
126
|
-
|
|
123
|
+
audit_factory?: AuditFactory;
|
|
127
124
|
}
|
|
128
125
|
/**
|
|
129
126
|
* Create an app server with a bootstrapped account for testing.
|
|
@@ -154,25 +151,29 @@ export interface CreateTestAppOptions extends TestAppServerOptions {
|
|
|
154
151
|
create_route_specs: (context: AppServerContext) => Array<RouteSpec>;
|
|
155
152
|
/**
|
|
156
153
|
* RPC endpoints mounted by `create_app_server` — eager array or
|
|
157
|
-
* `(ctx: AppServerContext) => Array<RpcEndpointSpec>` factory.
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* both are set `app_options` wins and `console.warn` fires.
|
|
154
|
+
* `(ctx: AppServerContext) => Array<RpcEndpointSpec>` factory. Single
|
|
155
|
+
* source of truth; the equivalent slot under `app_options` is `Omit`'d
|
|
156
|
+
* so setup-time path lookup and runtime dispatch read from one place.
|
|
157
|
+
* Symmetric with the suite-level `rpc_endpoints` option on
|
|
158
|
+
* `describe_standard_admin_integration_tests` etc.
|
|
163
159
|
*/
|
|
164
160
|
rpc_endpoints?: RpcEndpointsSuiteOption;
|
|
165
|
-
/**
|
|
166
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Optional overrides for `AppServerOptions`. The four fields
|
|
163
|
+
* `create_test_app` manages are excluded: `backend`, `session_options`,
|
|
164
|
+
* `create_route_specs`, and `rpc_endpoints` (see top-level slot above).
|
|
165
|
+
*/
|
|
166
|
+
app_options?: SuiteAppOptions;
|
|
167
167
|
}
|
|
168
168
|
/**
|
|
169
|
-
* `app_options` shape accepted by DB-backed suite
|
|
170
|
-
* (`describe_standard_integration_tests`,
|
|
171
|
-
* etc.). Excludes
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
169
|
+
* `app_options` shape accepted by `create_test_app` and the DB-backed suite
|
|
170
|
+
* helpers (`describe_standard_integration_tests`,
|
|
171
|
+
* `describe_audit_completeness_tests`, etc.). Excludes the four fields the
|
|
172
|
+
* helpers manage directly — `backend` / `session_options` /
|
|
173
|
+
* `create_route_specs` are constructed by the helper itself; `rpc_endpoints`
|
|
174
|
+
* lives on the top-level option (hard-failed by `require_rpc_endpoint_path`
|
|
175
|
+
* in the suites) so setup-time path lookup and runtime dispatch read from
|
|
176
|
+
* one source of truth.
|
|
176
177
|
*/
|
|
177
178
|
export type SuiteAppOptions = Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs' | 'rpc_endpoints'>>;
|
|
178
179
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAG/B,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAGjD,OAAO,EAA2B,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAU1D,OAAO,EAA8B,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG3F,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ import { query_create_api_token } from '../auth/api_token_queries.js';
|
|
|
11
11
|
import { create_session_cookie_value } from '../auth/session_cookie.js';
|
|
12
12
|
import { run_migrations } from '../db/migrate.js';
|
|
13
13
|
import { auth_migration_ns } from '../auth/migrations.js';
|
|
14
|
-
import {
|
|
14
|
+
import { default_audit_factory } from '../server/app_backend.js';
|
|
15
15
|
import { create_app_server, } from '../server/app_server.js';
|
|
16
16
|
import { generate_daemon_token, DAEMON_TOKEN_HEADER, } from '../auth/daemon_token.js';
|
|
17
17
|
import { create_pglite_factory } from './db.js';
|
|
@@ -96,8 +96,7 @@ const test_log = new Logger('test', { level: 'off' });
|
|
|
96
96
|
* API token, and session row.
|
|
97
97
|
*/
|
|
98
98
|
export const create_test_app_server = async (options) => {
|
|
99
|
-
const { session_options, db: existing_db, db_type = 'pglite-memory', password = stub_password_deps, username = 'keeper', password_value = 'test-password-123', roles = [ROLE_KEEPER],
|
|
100
|
-
audit_log_config, } = options;
|
|
99
|
+
const { session_options, db: existing_db, db_type = 'pglite-memory', password = stub_password_deps, username = 'keeper', password_value = 'test-password-123', roles = [ROLE_KEEPER], audit_factory = default_audit_factory, } = options;
|
|
101
100
|
// Keyring from test secret
|
|
102
101
|
const keyring_result = create_validated_keyring(TEST_COOKIE_SECRET);
|
|
103
102
|
if (!keyring_result.ok) {
|
|
@@ -116,12 +115,7 @@ export const create_test_app_server = async (options) => {
|
|
|
116
115
|
await existing_db.query('UPDATE app_settings SET open_signup = false, updated_at = NULL, updated_by = NULL WHERE open_signup = true OR updated_at IS NOT NULL');
|
|
117
116
|
// Use the caller's database — tables already created by the factory's init_schema.
|
|
118
117
|
// Caller owns the DB lifecycle — close is a no-op.
|
|
119
|
-
const audit =
|
|
120
|
-
db: existing_db,
|
|
121
|
-
log: test_log,
|
|
122
|
-
on_audit_event,
|
|
123
|
-
audit_log_config,
|
|
124
|
-
});
|
|
118
|
+
const audit = audit_factory({ db: existing_db, log: test_log });
|
|
125
119
|
backend = {
|
|
126
120
|
db_type,
|
|
127
121
|
db_name: 'test',
|
|
@@ -142,7 +136,7 @@ export const create_test_app_server = async (options) => {
|
|
|
142
136
|
// instead of creating a new PGlite each time. Schema is reset and migrations re-run
|
|
143
137
|
// on each call, but the expensive WASM cold start only happens once per worker thread.
|
|
144
138
|
const db = await fallback_pglite_factory.create();
|
|
145
|
-
const audit =
|
|
139
|
+
const audit = audit_factory({ db, log: test_log });
|
|
146
140
|
backend = {
|
|
147
141
|
db_type: 'pglite-memory',
|
|
148
142
|
db_name: '(memory)',
|
|
@@ -198,9 +192,6 @@ export const create_test_app = async (options) => {
|
|
|
198
192
|
rotated_at: new Date(),
|
|
199
193
|
keeper_account_id: test_server.account.id,
|
|
200
194
|
};
|
|
201
|
-
if (options.rpc_endpoints !== undefined && options.app_options?.rpc_endpoints !== undefined) {
|
|
202
|
-
console.warn('create_test_app: both top-level `rpc_endpoints` and `app_options.rpc_endpoints` are set; preferring `app_options.rpc_endpoints` (back-compat).');
|
|
203
|
-
}
|
|
204
195
|
const result = await create_app_server({
|
|
205
196
|
backend: test_server,
|
|
206
197
|
session_options: options.session_options,
|
|
@@ -67,17 +67,18 @@ export interface StandardAttackSurfaceOptions {
|
|
|
67
67
|
/**
|
|
68
68
|
* Run the standard attack surface test suite.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
70
|
+
* Test groups:
|
|
71
71
|
* 1. Snapshot — live surface matches committed JSON
|
|
72
72
|
* 2. Determinism — building twice yields identical results
|
|
73
73
|
* 3. Public routes — bidirectional check (no unexpected, no missing)
|
|
74
74
|
* 4. Middleware stack — every API route has the full middleware chain
|
|
75
|
-
* 5. Surface invariants — structural assertions (error schemas, descriptions, duplicates, consistency)
|
|
76
|
-
* 6.
|
|
77
|
-
* 7.
|
|
78
|
-
* 8.
|
|
79
|
-
* 9. Adversarial
|
|
80
|
-
* 10. Adversarial
|
|
75
|
+
* 5. Surface invariants — structural assertions over `surface.routes` (error schemas, descriptions, duplicates, consistency)
|
|
76
|
+
* 6. RPC/WS surface invariants — structural assertions over `surface.rpc_endpoints` + `surface.ws_endpoints` (descriptions, protocol-action spread, kind ⇔ auth)
|
|
77
|
+
* 7. Security policy — rate limiting on sensitive routes, no unexpected public mutations, method conventions
|
|
78
|
+
* 8. Error schema tightness — informational log of generic vs specific error schemas, plus assertion against `default_error_schema_tightness` by default (opt out with `error_schema_tightness: null`)
|
|
79
|
+
* 9. Adversarial auth — unauthenticated/wrong-role/correct-auth enforcement
|
|
80
|
+
* 10. Adversarial input — input body and params validation
|
|
81
|
+
* 11. Adversarial 404 — stub 404 handlers, validate response bodies against declared schemas
|
|
81
82
|
*
|
|
82
83
|
* Consumer test files call this with project-specific options, then add
|
|
83
84
|
* any project-specific assertions in additional `describe` blocks.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,
|
|
1
|
+
{"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAoBjC,OAAO,EAA4B,KAAK,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAsClF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA4H3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,gHAAgH;IAChH,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA2EF,CAAC"}
|
|
@@ -15,7 +15,7 @@ import './assert_dev_env.js';
|
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
17
17
|
import { test, assert, describe } from 'vitest';
|
|
18
|
-
import { assert_surface_invariants, assert_surface_security_policy, audit_error_schema_tightness, assert_error_schema_tightness, default_error_schema_tightness, fuz_app_stock_route_tightness_allowlist, } from './surface_invariants.js';
|
|
18
|
+
import { assert_surface_invariants, assert_rpc_ws_surface_invariants, assert_surface_security_policy, audit_error_schema_tightness, assert_error_schema_tightness, default_error_schema_tightness, fuz_app_stock_route_tightness_allowlist, } from './surface_invariants.js';
|
|
19
19
|
import { describe_adversarial_input } from './adversarial_input.js';
|
|
20
20
|
import { describe_adversarial_404 } from './adversarial_404.js';
|
|
21
21
|
import { create_test_app_from_specs, create_test_request_context, create_auth_test_apps, select_auth_app, resolve_test_path, } from './auth_apps.js';
|
|
@@ -193,17 +193,18 @@ export const resolve_standard_error_schema_tightness = (consumer) => {
|
|
|
193
193
|
/**
|
|
194
194
|
* Run the standard attack surface test suite.
|
|
195
195
|
*
|
|
196
|
-
*
|
|
196
|
+
* Test groups:
|
|
197
197
|
* 1. Snapshot — live surface matches committed JSON
|
|
198
198
|
* 2. Determinism — building twice yields identical results
|
|
199
199
|
* 3. Public routes — bidirectional check (no unexpected, no missing)
|
|
200
200
|
* 4. Middleware stack — every API route has the full middleware chain
|
|
201
|
-
* 5. Surface invariants — structural assertions (error schemas, descriptions, duplicates, consistency)
|
|
202
|
-
* 6.
|
|
203
|
-
* 7.
|
|
204
|
-
* 8.
|
|
205
|
-
* 9. Adversarial
|
|
206
|
-
* 10. Adversarial
|
|
201
|
+
* 5. Surface invariants — structural assertions over `surface.routes` (error schemas, descriptions, duplicates, consistency)
|
|
202
|
+
* 6. RPC/WS surface invariants — structural assertions over `surface.rpc_endpoints` + `surface.ws_endpoints` (descriptions, protocol-action spread, kind ⇔ auth)
|
|
203
|
+
* 7. Security policy — rate limiting on sensitive routes, no unexpected public mutations, method conventions
|
|
204
|
+
* 8. Error schema tightness — informational log of generic vs specific error schemas, plus assertion against `default_error_schema_tightness` by default (opt out with `error_schema_tightness: null`)
|
|
205
|
+
* 9. Adversarial auth — unauthenticated/wrong-role/correct-auth enforcement
|
|
206
|
+
* 10. Adversarial input — input body and params validation
|
|
207
|
+
* 11. Adversarial 404 — stub 404 handlers, validate response bodies against declared schemas
|
|
207
208
|
*
|
|
208
209
|
* Consumer test files call this with project-specific options, then add
|
|
209
210
|
* any project-specific assertions in additional `describe` blocks.
|
|
@@ -231,6 +232,9 @@ export const describe_standard_attack_surface_tests = (options) => {
|
|
|
231
232
|
test('surface invariants', () => {
|
|
232
233
|
assert_surface_invariants(surface);
|
|
233
234
|
});
|
|
235
|
+
test('rpc/ws surface invariants', () => {
|
|
236
|
+
assert_rpc_ws_surface_invariants(surface);
|
|
237
|
+
});
|
|
234
238
|
test('security policy', () => {
|
|
235
239
|
assert_surface_security_policy(surface, security_policy);
|
|
236
240
|
});
|
|
@@ -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;AAkB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAIrD,OAAO,EAGN,KAAK,eAAe,EAEpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB,OAAO,EAIN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAqB1B;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE;;;;;;;;;;;OAWG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAkB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAIrD,OAAO,EAGN,KAAK,eAAe,EAEpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAIN,KAAK,SAAS,EACd,MAAM,SAAS,CAAC;AAKjB,OAAO,EAIN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAqB1B;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C,4CAA4C;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,wDAAwD;IACxD,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE;;;;;;;;;;;OAWG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC,iDAAiD;IACjD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,qEAAqE;IACrE,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;CAChC;AAyED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAihBzF,CAAC"}
|
|
@@ -50,10 +50,8 @@ const build_options = (options, db) => ({
|
|
|
50
50
|
create_route_specs: options.create_route_specs,
|
|
51
51
|
db,
|
|
52
52
|
roles: [ROLE_KEEPER, ROLE_ADMIN],
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
rpc_endpoints: options.rpc_endpoints,
|
|
56
|
-
},
|
|
53
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
54
|
+
app_options: options.app_options,
|
|
57
55
|
});
|
|
58
56
|
/** Headers for unauthenticated JSON requests (login, signup). */
|
|
59
57
|
const UNAUTHENTICATED_JSON_HEADERS = {
|
|
@@ -82,7 +80,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
82
80
|
// Hard-fail early so consumers see a clear setup error instead of a
|
|
83
81
|
// confusing test failure when `rpc_endpoints` is missing. Factory-form
|
|
84
82
|
// callers are resolved with a stub ctx purely to extract the endpoint
|
|
85
|
-
// path; real handlers run per-test via `
|
|
83
|
+
// path; real handlers run per-test via the top-level `rpc_endpoints` slot on `CreateTestAppOptions`.
|
|
86
84
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
87
85
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
88
86
|
const init_schema = async (db) => {
|