Cross-process malformed-envelope and invalid-params suite for a JSON-RPC
endpoint.
describe_rpc_attack_surface_tests runs its envelope and params groups
in-process against stub apps, so they never reach a spawned backend — and
the Rust spine parses envelopes and params by hand, while the
action-manifest parity gate compares declarations and is blind to param
schemas. This suite sends the same requests over fixture.transport to
each backend.
Two groups:
- Envelopes — create_rpc_envelope_cases, the in-process group's set:
non-JSON body, wrong or missing
jsonrpc, missing method or id, a
batch array, non-object POST params, an unknown method, GET missing
method or id, GET invalid-JSON params, GET non-object params on
every read, and a GET of a mutating method.
Each is sent by the principal its method's auth admits, so the envelope
check is what refuses, and each pins status, JSON-RPC code, the echoed
id, and data.reason (exact where the TS dispatcher sets one, absent
otherwise). Three ordering cases go without a credential: an anonymous
GET of a mutating method is refused invalid_request rather than 401
(the read restriction runs after the method lookup and before
authorization), an anonymous batch array is refused invalid_request
with id: null (the envelope parse runs before auth, and batches are
unsupported), and an anonymous GET with non-object params is the 401
(params validation runs after authorization). - Params — for each declared method (and each live-mounted one, below),
generate_rpc_params_test_cases
(the in-process generator minus its structural cases), each sent as the
method's authorized principal and owing 400
invalid_params with the
data.reason the broken field declares (invalid_reason in its
.meta(), read by expected_invalid_reason) — none for an undeclared
field or a whole-body case. One test per method, reporting every case that answers
otherwise; xfail_params runs named cases of a method as their own
xfail_until tests, for a known divergence on one backend.
The authorized principal is the keeper on the first channel the method's
auth admits: its session, else its daemon token or API token over a
no-Origin transport (resolve_conformance_principal), mirroring the
in-process select_auth_app. Preconditions: the keeper holds every
role a declared method gates on (default_cross_process_setup seeds
keeper and admin; pass extra_keeper_roles for others), and it has a
single actor. No generated body carries a valid acting — the generator's
base fills required and defaulted fields, and an optional one only where a
refinement needs it (each alone first, so an acting declared ahead of an
at-least-one or exactly-one-of pair stays out) — and a malformed acting is
read as omitted ahead of validation on both spines, so a single-actor
keeper resolves its actor and validation is what refuses. A keeper with a
second actor answers actor_required first; such a suite lists the
actor-required methods in skip_methods.
Off the declared surface. live_rpc_endpoints adds the methods the
backend live-mounts at rpc_path beyond the declared surface — cell verbs,
opt-in resolvers, the _testing_* backdoors. Both groups run over them too:
the envelope cases built from those methods, in a group of their own, and a
params test per method, _testing_* included (a daemon-only backdoor is
sent with the keeper's daemon token). A spine that keeps a method family off
its declared surface still owes the twin's answer to bad params there.
Skipped: _testing_* methods on the declared surface (a declared surface
lists them only by accident; the live mount is where they are walked), and
the protocol actions (PROTOCOL_ACTION_METHODS, fixed shared specs covered
by the WS suites, which generate_rpc_auth_conformance_cases skips too).
Cross-process only: in-process, describe_rpc_attack_surface_tests runs
the same envelope cases and params generator.