test(services): pin that the per-value byte caps reject before authz
CLAUDE.md calls the KV/docs/pubsub/queue value-size caps an anti-DoS rail: oversized payloads are rejected "before authz so anonymous public scripts can't DoS Postgres." Only queue had a test, and it used an allow-all authz + anon cx — which catches a DROPPED cap but not a REORDERED one, because an anon cx passes script_gate regardless. Each service now has an ordering-proof test: a DENYING authz repo + an AUTHENTICATED member cx, so a size-check-first service returns *TooLarge while an authz-first one would return Forbidden. Each pairs it with an under-cap control through the same denied cx (returns Forbidden) to prove the cx really is denied, so the *TooLarge case genuinely bypassed authz. Queue's pre-existing test is upgraded to the same shape (+ a shared member_cx helper). Mutation-verified: moving the KV size check after authz flips its result to Forbidden and the test fails. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -775,4 +775,47 @@ mod tests {
|
||||
other => panic!("expected SubscriberToken, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `PICLOUD_PUBSUB_MAX_MESSAGE_BYTES` — CLAUDE.md: "Prevents one publish from
|
||||
/// amplifying into N outbox rows × M MB." Pins that an oversized message is
|
||||
/// rejected BEFORE authz. The cx is an authenticated member that
|
||||
/// `DenyingAuthzRepo` rejects, so the ordering is observable: size-first
|
||||
/// returns `MessageTooLarge`, authz-first would return `Forbidden`.
|
||||
#[tokio::test]
|
||||
async fn oversized_message_is_rejected_before_authz() {
|
||||
let repo = Arc::new(InMemoryPubsubRepo::new(vec![]));
|
||||
let svc = svc(repo.clone(), Arc::new(DenyingAuthzRepo)).with_max_message_bytes(16);
|
||||
let cx = member_cx(AppId::new());
|
||||
|
||||
let err = svc
|
||||
.publish_durable(
|
||||
&cx,
|
||||
"events",
|
||||
serde_json::json!({ "blob": "x".repeat(100) }),
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, PubsubError::MessageTooLarge { limit: 16, .. }),
|
||||
"an oversized publish must be MessageTooLarge before authz; an authz-first \
|
||||
order would return Forbidden for this denied cx. got {err:?}"
|
||||
);
|
||||
// The oversized publish never reached the repo — no outbox amplification.
|
||||
assert_eq!(
|
||||
repo.written_count(),
|
||||
0,
|
||||
"a rejected publish must not write any fan-out rows"
|
||||
);
|
||||
|
||||
// Control: an under-cap publish with the same cx is Forbidden — confirming
|
||||
// the cx is authz-denied, so the case above genuinely bypassed authz.
|
||||
let err = svc
|
||||
.publish_durable(&cx, "events", serde_json::json!({ "ok": 1 }))
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
matches!(err, PubsubError::Forbidden),
|
||||
"the control confirms this cx is authz-denied; got {err:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user