Files
tech-log-backend/docs/superpowers/specs/2026-08-02-web-security-boundary-design.md
T
DongHyeonkaandClaude Opus 5 5f10b791d3 chore: record pre-existing uncommitted repository state
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>
2026-08-11 16:48:43 +09:00

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 check task;
  • 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 AuthenticatedPrincipal subject/roles;
  • expiry beyond the configured 60-second skew, issuer mismatch, audience mismatch, wrong signature, and unknown kid produce their exact stable 401 error codes and bounded WWW-Authenticate/Retry-After headers;
  • deterministic JWKS 503 produces AUTH_JWKS_UNAVAILABLE, HTTP 503, and Retry-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_MISCONFIGURATION envelope 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 Vary headers;
  • 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.