A threat-oriented short document: what in this repo is deliberately demo-grade and must never be treated as production practice, and which patterns are production-hardened and worth copying. The findings ledger with per-finding fixes is security-findings-log.md.
Named honestly, so nobody copies the wrong half:
- Password hashing:
idn.Users.PasswordHashis a plain SHA-256 ofcorridor-demo-+ password (db/sql/seed/003_seed.sql). Fast, unsalted, demo-only. Production uses a memory-hard KDF (argon2, scrypt, or bcrypt) with per-user salts. - Secrets in containers and config: the client secrets
corridor-portal-secretandcorridor-legacy-secret, the SCIM bearercorridor-scim-token, and the SQL sa passwordCorridorDev1!are documented constants committed deliberately (docker-compose.yml,appsettings.Development.json,ClientRegistry). They have no secret value; production secrets arrive via a secret manager or at minimum environment variables, never in files. - Plain http everywhere: every URL is
http://localhost:<port>; TLS termination, HSTS, and secure cookie flags are out of scope for a local simulation. The OIDC and bearer handlers therefore setRequireHttpsMetadata = false(marked insrc/Corridor.Portal/Program.cs), which is correct only for local sims. - Rate limiting is present but coarse: okta-sim throttles the authorize and token endpoints with a per-IP fixed window (60 per minute; 429 responses). adfs-sim's login form does not throttle and nothing implements lockout or monitoring: production wraps all credential surfaces in smarter limits plus lockout.
- Committed dev certificates under
certs/sign synthetic tokens and protect nothing (certs/README.md). - Bearer-token-in-error-detail style conveniences: the SCIM 401 names its demo token to make the demo self-explaining; production never teaches callers the credential.
- No real PII: every user, licensee, case, and assignment is synthetic; this is a boundary, not a shortcut (ADR 0001).
The gitleaks full-history scan pins its known findings in .gitleaksignore: the
runbook's SCIM curl examples (documented demo bearer), the sonar.projectKey line (a
project identifier, not a secret), and the two committed development-only signing PEMs
(see certs/README.md). Every entry is a deliberate, named demo artifact; the scan stays
on.
The half worth copying:
- PKCE S256, enforced server-side: mandatory for public clients, method and length
checked, fixed-time verifier comparison (
src/Corridor.OktaSim/Endpoints/Oidc.cs; ADR 0003). - Single-use, short-lived authorization codes: atomically consumed, five-minute
lifetime, client and redirect_uri bound (
AuthCodeStore.cs). - Refresh-token rotation with family revocation: replaying a rotated token revokes
the whole family (
RefreshTokenStore.cs); the token endpoint reports reuse asinvalid_grant. - Signed XML with XXE-safe parsing: DTD prohibited, no resolver, on every SAML and
XACML parse path (
src/Corridor.OktaSim/Saml/SafeXml.cs, used byPdpEngineand the sims' builders; the legacy inspector setsXmlResolver = null). - Signature, audience, and lifetime validation on both token kinds: SAML
(certificate-pinned, NotBefore with skew, NotOnOrAfter) and JWT (RS256 via JWKS,
issuer, audience, one-minute skew, algorithm pinned) in
src/Corridor.Legacy/Security/. - Registered redirect enforcement: unregistered
redirect_urigets a plain 400, never a redirect (open-redirect avoidance,CheckRedirectAndScope). - Default-deny authorization: the XACML deny-all policy sorts last; malformed or unmatched requests are Deny with a reason, never a silent pass (ADR 0007).
- Transition guards in SQL: illegal case moves are refused inside
trace.usp_UpdateStatuswith error state 40001, applying to every caller (ADR 0005). - Audit events on every trust change: mode and audit writes happen together
(
TrustModeService.FlipAsync). - Error discipline: RFC 9457 problem details on REST, named
cor:subcodes on SOAP faults, XACML Deny-with-reason on PDP errors; nothing leaks stack traces to callers. - Parameterized data access everywhere: ADO.NET with
SqlParameteronly; no concatenated SQL in any service.
Static and web scanning are configured in sonar-project.properties and run outside CI
by choice (CI stays fast and dependency free). Findings, severities, and fixes are
tracked to closure in security-findings-log.md; every entry
there is Resolved. Vulnerability reports for this demo: open an issue; there is no real
data or service to protect, but regressions in the hardened patterns above are treated
as defects.