Skip to content

Product documentation

CI security guards

The build-time scripts/check-*.sh family that makes each security invariant in this dossier un-regressable — a regression fails the PR before it can merge.

CI security guards

The invariants described elsewhere in this dossier are not documentation-only promises — each is backed by a build-time guard in scripts/check-*.sh, wired into .github/workflows/ci.yml and mirrored by a scripts/local-ci/* wrapper for pre-push runs. A change that re-opens one of these holes fails the PR before it can merge. For a security reviewer this is the important meta-property: the controls are enforced by construction, so they cannot silently rot as the codebase evolves.

Most guards were paid for by a specific pentest finding or CI incident (referenced by their SSO- ticket) and carry a narrow, documented suppression marker for the rare legitimate exception — a bare marker with no rationale still fails, so an exception is always explained in the diff.

Token, crypto & protocol integrity

GuardPreventsProtects
check-jwt-decoder-algorithms.shA NimbusJwtDecoder built without a JWS algorithm allow-list accepting whatever the JWKS advertises (which under some Nimbus configs admits none/HMAC). Every decoder must pin ES256 (or a justified multi-alg set).Tenant isolation, Signing & crypto
check-no-default-typing.shJackson polymorphic default-typing (activateDefaultTyping, …), the API surface behind the long deserialization-gadget CVE chain. Banned outside a documented, cryptographically-gated exemption.Deserialization safety
check-spring-as-principal-factor.shA Spring AS authorization/token-flow principal missing a FactorGrantedAuthority, which breaks ID-token issuance (and the auth-code flow) at runtime.Protocol integrity

Web, session & error hygiene

GuardPreventsProtects
check-no-raw-cookie-construction.shA raw Cookie(...) that omits the hub's four session-cookie attributes (HttpOnly, Secure, SameSite=Lax, Path=/). All cookies must route through securedHubCookie; the clear-cookie path must carry the same attributes.Session security
check-no-custom-error-shape.shA new {error, message} error envelope outside the RFC 6749 allow-list — the shape that leaked tenant ids in the SSO-834 pentest. Everything customer-plane is RFC 9457 problem-details.Token security
check-no-raw-mdc-put.shA raw MDC.put(...), which can inject CRLF into logs (log forging). Writes must go through MdcSafe (allow-list regex + length cap).Log integrity

Host-header URL poisoning

Three guards fail the build when production code reads the HTTP Host header (request.serverName / getServerName() / request.requestURL) and builds one of these user-reachable URLs from it — the attacker-controllable Host-poisoning bug class (compare CVE-2017-6056). Each requires the URL to come from a configured, deploy-time property instead:

  • check-no-host-header-in-email-url-builder.sh — password-reset and other email URLs (SSO-1134).
  • check-no-host-header-in-oauth-redirect-uri-builder.sh — federation-member OAuth redirect_uri values (SSO-832).
  • check-no-host-header-in-logout-redirect.shpost-logout redirect URIs (SSO-833).

Tenant isolation

  • check-no-tenant-in-customer-plane-path.sh (SSO-1937) — scans every product-api controller (not just the diff) and fails if any Spring mapping literal carries a path-borne tenant id. The tenant is always the caller's tnt claim, never the URL — see tenant isolation.

Availability & reactive safety

  • check-block-without-duration.sh (SSO-771) — every reactive→blocking .block() must carry an explicit Duration. An unbounded .block() pins a worker thread until the downstream completes; under load that exhausts the pool and turns a transient downstream blip into a multi-minute outage. Genuinely-safe calls carry an inline // safe: justification.

Kubernetes & infrastructure hardening

GuardPreventsProtects
check-pod-security-context.shA pod spec missing the shared runAsNonRoot: true + seccomp RuntimeDefault security context — the container-escape mitigation (SSO-843).Runtime isolation
check-deployment-sa-token.shA new Deployment without automountServiceAccountToken: false, which would mount a Kubernetes API token into a pod that has no business calling the API (CVE-2025-55182 follow-up).Blast-radius containment
check-audit-internal-k8s-enabled.shThe internal audit endpoint drifting back to permitAll(); it must require Kubernetes ServiceAccount-JWT auth in staging/prod (SSO-1337).Audit integrity
check-no-literal-vault-root.shA literal secrets-backend root token baked into any shipped artefact (SSO-798).Secret hygiene
check-no-literal-compose-secrets.shLiteral secrets embedded in compose files.Secret hygiene
check-ingress-tls-annotations.shIngress resources missing required TLS annotations.Transport security

Data safety

These guards protect the durability and integrity of user data — a security-adjacent property of a system of record:

  • check-migration-immutability.sh — no in-place edit/rename/delete of an already-applied Flyway migration (checksum integrity).
  • check-no-drop-database.sh — bans DROP DATABASE / TRUNCATE TABLE / DROP SCHEMA outside the allow-list; recovery is forward-patch-only, never destructive, so real user data is never silently lost.
  • check-no-unbounded-varchar.sh — variable-length content columns must be TEXT, not VARCHAR(n); an overflow otherwise surfaces as an opaque 500 that breaks a live flow.

How to read a guard as a reviewer

Each guard's header comment states the ticket, the bug it prevents, the exact pattern it matches, and its suppression marker. When auditing a change, the fastest way to confirm an invariant still holds is to check the guard is present in .github/workflows/ci.yml and green — the guard, not the prose, is the enforced contract. This dossier's other pages name the specific guard that backs each control.

Source: scripts/check-*.sh; .github/workflows/ci.yml; CLAUDE.md → "Known pitfalls" (the rationale for most guards).