Snapshot of the in-flight state that already existed, identically, in both this worktree and the main checkout before this session began: the initial HTTP Client platform implementation (previously untracked), the redis-lab removal, and the JPA / object-storage / notification integration work. Kept separate from this session's HTTP Client review response, which lands in the following commit, so the two bodies of work stay reviewable apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.8 KiB
Web Security Boundary Design
Date: 2026-08-02
Status: approved by the user's instruction to apply the reviewed P1/P2 work sequentially
Scope: JWT/OIDC/JWKS and CORS behavior at the adapter:inbound:web Spring Security filter boundary
Context
The module has unit contracts for JWT validators, exception classification, envelope writers, and
CORS settings. It does not yet prove that a real bearer request crosses issuer discovery, JWKS
retrieval, signature/claim validation, principal conversion, SecurityFilterChain, and the public
error envelope. CORS configuration is likewise untested at the filter boundary, where preflight
ordering relative to authentication is the important behavior.
These are release-boundary checks and must not silently skip because an external IdP, environment variable, or optional flag is absent.
Decision
Add a dedicated webSecurityBoundaryTest task that reuses the ordinary test output/classpath and
runs only JUnit tests tagged security-boundary. Ordinary test excludes that tag so each contract
runs once. The dedicated task:
- fails when no tests are discovered;
- disables up-to-date reuse;
- fails the root suite when any test reports
SKIPPED; - is required by the inbound-web
checktask; - uses UTC and no environment-dependent conditions or assumptions.
JWT tests use a JDK loopback HttpServer bound to 127.0.0.1 on an ephemeral port. It serves the
minimum OIDC discovery document and JWKS response. Tests generate ephemeral RSA keys and compact
RS256 JWTs with the already-resolved Nimbus dependency; no new library or external network is
allowed. Each failure case uses a fresh server and Spring context to prevent decoder/JWK cache
cross-contamination.
CORS tests build the production SecurityConfig and real springSecurityFilterChain with direct
configuration properties. They issue real preflight and actual-origin MockMvc requests. A test JWT
decoder bean is allowed here because CORS ordering—not token decoding—is the owned boundary.
JWT/JWKS Contract
- application context startup performs zero discovery/JWKS calls (lazy decoder);
- a correctly signed token reaches a protected controller and exposes the expected
AuthenticatedPrincipalsubject/roles; - expiry beyond the configured 60-second skew, issuer mismatch, audience mismatch, wrong
signature, and unknown
kidproduce their exact stable 401 error codes and boundedWWW-Authenticate/Retry-Afterheaders; - deterministic JWKS 503 produces
AUTH_JWKS_UNAVAILABLE, HTTP 503, andRetry-After: 30; - after that first-request 503, the same lazy decoder/context retries initialization and succeeds once the JWKS endpoint recovers;
- discovery metadata that is fetched successfully but is internally inconsistent produces the
fixed 500
INTERNAL_AUTH_MISCONFIGURATIONenvelope rather than a raw initialization exception; - responses never contain the bearer token, issuer URL,
kid, JWK material, or internal decoder diagnostics.
CORS Contract
- an approved credentialed preflight to an authenticated endpoint succeeds before bearer authentication and emits exact origin/credentials/method/header/max-age policy;
- an unapproved origin receives 403 without allow-origin or allow-credentials reflection;
- disabled CORS emits no CORS response headers;
- wildcard origin without credentials returns
*and no credentials header; - an approved actual-origin request receives matching CORS and bounded
Varyheaders; - wildcard plus credentials remains a settings startup failure (already covered by settings tests).
Non-Goals
- external IdP/TLS/rotation rehearsal;
- browser-engine SameSite behavior;
- Redis-backed session continuity (the next P1 batch);
- new test libraries, Docker, or changes to production dependency direction;
- staging, commit, amend, or push by an agent.