Skip to content

test(contract): add the §10.1 rule-8 guardrail regression tests - #16

Merged
ilpanich merged 1 commit into
mainfrom
claude/rule8-guardrail-tests
Aug 4, 2026
Merged

test(contract): add the §10.1 rule-8 guardrail regression tests#16
ilpanich merged 1 commit into
mainfrom
claude/rule8-guardrail-tests

Conversation

@ilpanich

@ilpanich ilpanich commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Adds the CONTRACT.md §10.1 rule-8 guardrail regression tests — the one part of the local-verification set with no test outside PHP, TypeScript and Python.

The rule

Rules 1-7 ask whether the token is good. Rule 8 asks whether it is the token the decision is even about. SEC-085 satisfied all seven and was still an authentication bypass: the PHP guard routed a failed verification into a second, successful one against the application's own session, admitting the caller as the app's service account — in an IAM integration typically far more privileged than the user whose request it replaced.

Why this SDK matters more than most

Unlike the Go/Python/TypeScript guards, which are handed a bare verifier and a tenant, the Ktor plugin holds a full AxiamClient (config.client) and calls client.verifySession(token) on it. That is precisely the structural shape SEC-085 exploited — a stateful client, carrying its own session, reachable from the guard.

verifySession is correct today: it decides on the supplied token alone. But nothing pinned that, and the client's session sits one method call away.

Making the substitution genuinely reachable

The tests log the client in, so its cookie jar holds a real session token for app-service-account, and assert that precondition before testing anything. Without that priming the tests would pass against the vulnerable shape for an incidental reason — the fallback would fail because there was no session to fall back to — which is the trap the PHP reference test documents at length.

Tests (3)

  • a failed caller token is rejected even when the client session is healthy — four rejection shapes (expired, garbage, alg:none, foreign tenant), each asserting 401 and that the app's principal never appears as the identity.
  • verifySession decides on the supplied token and never the client session — pins the seam directly, so the guard's correctness doesn't rest on which client method it happens to call today. This is the assertion that would have caught SEC-085 at its source rather than at the framework bridge.
  • a rejected caller has no identity attached to the call — SEC-085 was a bypass rather than a mere error because the request continued carrying an identity.

Falsified: injecting the SEC-085 fallback into verifySession fails all 3.

Verified locally: ./gradlew test green (full suite).

🤖 Generated with Claude Code

https://claude.ai/code/session_01SkTHvZQMV47t3UwkEtmB1D


Generated by Claude Code

CONTRACT.md §10.1 rule 8 — "subject of the decision" — was the one part
of the local-verification set with no regression test outside PHP,
TypeScript and Python. §15.1 hand-verified that every guard rejects
correctly, so this closes a missing guardrail rather than a live defect.

Rules 1-7 ask whether the token is good. Rule 8 asks whether it is the
token the decision is even about. SEC-085 satisfied all seven and was
still an authentication bypass: the PHP guard routed a failed
verification into a second, successful one against the application's
own session, admitting the caller as the app's service account.

Each test asserts its precondition rather than assuming it: a second,
fully valid credential for a more privileged principal is first shown
to pass the same guard, so a fallback would genuinely have succeeded.
Without that, the tests would pass merely because nothing was available
to substitute — the trap the PHP reference test documents at length.

Every test was falsified by injecting the SEC-085 fallback into the
guard and confirming it fails.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SkTHvZQMV47t3UwkEtmB1D
@ilpanich
ilpanich merged commit 188e321 into main Aug 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants