SPEC-SEC-001: Credentials and Identity Lookup Code Contract
| Reference Number | Version | Effective Date | Supersedes | Authority Level |
|---|---|---|---|---|
| SPEC-SEC-001 | 1.5 | 2026-09-15 | 1.4 | Technical Specification |
I. Purpose
This specification defines the code-level security contract for credential handling, authentication, recovery-capability handling, and class-scoped identity lookup. It translates the governing INV-* requirements into testable technical behavior without making the security namespace a domain or an independent authority tier.
II. Scope
This specification applies to every route, domain, feature executor, background job, migration, serializer, logger, cache, and test fixture that handles credentials, authentication identifiers, recovery capabilities, or identity lookup. It governs code behavior only. Infrastructure ownership, secret administration, rotation, monitoring, incident response, and release workflow are governed by SOP-SEC-001.
This document does not create users, seats, classes, roles, or capabilities. Those remain owned by the applicable DOM-* and FEAT-* contracts.
III. Authority and Incorporation
Technical Specification. SPEC-SEC-001 is not an independent level of runtime authority and cannot promote itself through metadata. Its requirements are binding only where incorporated by a governing INV-*, DOM-*, or FEAT-* contract, and it remains subordinate to every applicable INV-* requirement. The SEC label identifies a cross-cutting subject, not a runtime authority namespace.
IV. Dependencies
docs/INVARIANT/CORE/INV-CORE-000_CORE_INVARIANTS.mddocs/INVARIANT/CORE/INV-CORE-001_CAPABILITY_BASED_ARCHITECTURE_AND_AUTHORITY_MODEL.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-001_SCOPED_REQUEST_CONTEXT.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-002_NO_IMPLICIT_GLOBAL_ACCESS.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-003_SCOPED_CAPABILITY_EVALUATION.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-005_NO_PII_LEAKAGE_IN_EXECUTION_LAYER.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-006_COMMAND_BOUNDARY_FOR_MUTATION.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-008_IDENTITY_RESOLUTION_AND_SEAT_SCOPE.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-016_LAWFUL_EXISTENCE_AND_AUDIT_LINEAGE.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-018_PII_STORAGE_AND_RETENTION_ENFORCEMENT.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-019_IDENTITY_AND_OWNERSHIP_MODEL.mddocs/INVARIANT/ARCHITECTURE/INV-ARC-021_CROSS_DOMAIN_REFERENCE_AND_COORDINATION.md
V. Normative Code Contract
V.1 Credential material
- Code MUST pass passphrases through one canonical password-hashing primitive exposed by the owning identity contract.
- The canonical password KDF is scrypt with the encoded profile
scrypt:32768:8:1. Each password hash MUST contain its own randomly generated salt in the standard encoded verifier format. - Password hashing MUST NOT use
PEPPER_KEY,SECRET_KEY,ENCRYPTION_KEY, or any other application secret as an input. Those keys have separate ownership and rotation semantics. - Code MUST store only the resulting verifier; it MUST NOT store plaintext passphrases, reversible passphrase encryption, or a recoverable copy in logs, events, sessions, URLs, caches, test output, or error objects.
- Verification MUST use the stored verifier and MUST return only a boolean/typed success result to its caller. Algorithm and parameters MUST be centralized in the canonical primitive, not duplicated in FEAT documents or route code.
- Malformed, missing, expired, or otherwise unusable verifiers MUST fail closed and MUST not disclose which condition occurred.
V.1a Required runtime environment
The application MUST refuse to start in any non-test environment when a required variable is absent, blank, malformed, or uses a development/default value.
| Variable | Code purpose | Required handling |
|---|---|---|
SECRET_KEY |
Session and application signing | High-entropy, environment-specific secret; never reused as a password pepper or PII-encryption key. |
ENCRYPTION_KEY |
Symmetric encryption of recoverable display PII | Valid key for the approved encryption facility; production key must be stored outside the repository and retained only under controlled key custody. |
PEPPER_KEY |
HMAC key for deterministic lookup/matching digests | High-entropy, environment-specific key; never used for password hashing. |
AUDIT_HMAC_KEY |
Integrity protection for security/audit lineage where required by its owning contract | High-entropy, environment-specific key; never logged or exposed to application responses. |
DATABASE_URL |
Canonical persistence boundary | Must identify the explicitly selected environment; implicit local/default database selection is prohibited for production. |
FLASK_ENV |
Explicit environment selection | Must be set explicitly; production must not run under development/test settings. |
PEPPER_KEY rotation is a lookup-digest migration and access-control event. SECRET_KEY rotation is a session-signing event. ENCRYPTION_KEY rotation is a protected-PII re-encryption event. AUDIT_HMAC_KEY rotation is an audit-lineage verification event. They MUST be planned and executed independently; no code may silently fall back from one variable to another.
V.2 Lookup and PII representation
- A lookup MUST resolve the canonical
class_idbefore evaluating class-local identity or claim authority. - Name or other PII lookup values MUST be normalized by one canonical, documented function, then compared using HMAC-SHA-256 under
PEPPER_KEYwith a field/domain separation label. The digest MUST not permit recovery of the original value. - Recoverable display PII MUST use the application’s approved symmetric encryption facility. Hash and encrypted display representations MUST be separate fields when both purposes exist.
users.id,seats.id, andclass_idMUST retain their distinct meanings.public_idMUST be resolved under canonical class scope and MUST never grant authority.- Lookup responses MUST return the minimum result needed by the owning contract. They MUST NOT expose credential material, lookup digests, encryption keys, internal identifiers, or unrelated class membership.
Canonical lookup encoding (pre-launch cutover)
app.hash_utils.normalize_lookup_text is the single text normalization primitive:
Unicode NFKC, then strip leading/trailing whitespace; names additionally use
Unicode lowercase. Username case, internal whitespace, punctuation, and accents
remain significant. Display values are not rewritten. Class IDs are canonical
opaque IDs and are not normalized as names. Claim distinguishing codes retain
their owning contract’s trim/uppercase rule.
Compute HMAC-SHA-256 under PEPPER_KEY over UTF-8 JSON arrays, serialized with
ensure_ascii=False and separators (',', ':'). First two elements are
"cth.identity.lookup.v1" and the fixed purpose label. Remaining elements:
| Label | Ordered elements |
|---|---|
username |
Normalized username |
username-verifier |
Lowercase hex salt, normalized username |
claim-first-name |
Canonical class ID, normalized first name |
claim-last-name |
Canonical class ID, normalized last name |
roster-fingerprint |
Canonical class ID, normalized first name, normalized last name, distinguishing code (empty if absent) |
Structured encoding prevents delimiter ambiguity. Missing class scope and unknown name fields fail closed. Principal lookup remains global; claim lookup first resolves class scope and includes that scope in its digest. Hall-pass verification uses the name normalization primitive for in-memory profile comparison, with no persistent lookup digest or claim-artifact dependency.
This is a pre-launch replacement with no existing data to migrate. All current readers, writers and fixtures use this format; do not attempt old-digest lookup, dual writes, compatibility fallback, or inference from display names to repair a credential. Password hashing and teacher-recovery capability formats are separate contracts and are not changed by this lookup encoding.
V.3 Authentication, capability, and context
- Authentication establishes
users.id; it does not by itself establishseat_idorclass_id. - Any class-scoped action MUST construct explicit request context containing the required principal, actor, boundary, request, and time values. Missing or contradictory context MUST fail immediately.
- Authorization MUST be evaluated at request time through the owning domain capability contract. Route-local role checks, global cached authorization, inferred scope, and helper-based scope repair are prohibited.
- Mutations MUST execute through the owning FEAT command boundary. Reads MUST be pure and MUST NOT commit state, rotate credentials, create recovery artifacts, or alter session identity as an incidental effect.
V.4 Recovery and capability artifacts
- Recovery and claim artifacts MUST be opaque, single-purpose, class-constrained where applicable, time-bounded when their owning contract requires it, and stored only in a non-reversible verification form unless recoverability is explicitly required by the governing contract.
- Code MUST bind artifact use to its owner and canonical scope before consuming it.
- Successful consumption MUST be atomic with the state change it authorizes; replay, mismatch, expired use, and cross-class use MUST fail closed.
- Responses for failed authentication, lookup, claim, and recovery attempts MUST be generic enough not to reveal account existence, roster membership, credential validity, or class membership.
Student recovery-session nonce representation
Under DOM-IDEN-002 §IX, generate 32 random bytes and encode with unpadded URL-safe
Base64. The accepting signed, non-permanent session holds that nonce. Persist on
User only SHA-256 of b"student-recovery-session:v1\0" + nonce.encode() and the
original code expiry. This is a high-entropy bearer capability, not a password or
name lookup digest. Compare verifiers in constant time. Check server state on
every setup step and under a User row lock at completion; clear it atomically with
credential replacement. Never log or export the nonce or verifier. Reissuing the
teacher code invalidates the stored nonce. Account deletion removes these fields
with their User row. Cookie signing does not replace the server-side check.
V.5 Observability and retention
Security events MAY record a request identifier, owning FEAT identifier, outcome class, and non-sensitive technical metadata. They MUST NOT contain PII, passphrases, tokens, raw lookup inputs, digests, encryption keys, or detailed failure material. Records MUST follow the owning identity and class deletion semantics; no security artifact may preserve deleted identity data without an explicitly authorized, separately scoped requirement.
VI. Required Verification
Implementations MUST have focused tests proving: plaintext and secret non-retention; normalization and keyed lookup behavior; class-scoped resolution; principal/seat/class separation; fail-closed malformed and replay paths; generic failure responses; pure reads; atomic authorized mutations; and deletion of security material with its owning identity. Source inspection alone is insufficient evidence for rendered or runtime behavior.
VII. Non-Goals and Prohibitions
Implementations MUST NOT select or substitute cryptographic algorithms or parameters outside those explicitly governed by this specification. This specification does not create a SEC domain, authorize cross-class administration, or provide compatibility bridges for legacy credential or identity schemes. Any algorithm or parameter change must be recorded as a revision to this specification and operationalized by SOP-SEC-001 before release.
VIII. Amendment
Revisions require a version increment, updated effective date, explicit derivation from the dependencies in Section IV, reconciliation with affected DOM-* and FEAT-* contracts, and focused verification of every changed invariant boundary.
Teacher recovery capability encoding
Under DOM-IDEN-003 §IX, confirmation codes are six-digit ASCII numeric values.
Use HMAC-SHA-256 under PEPPER_KEY over compact UTF-8 JSON
["teacher-recovery-code:v2", request_id, class_id, code], with integer request_id.
Rows also require issued_round equal to the attempt submission_round and a live
code_expires_at (30 minutes from issuance, capped by attempt expiry).
Resume PINs use UTF-8 teacher-recovery-resume:v1\0 + PIN under HMAC-SHA-256.
Attempt access nonces have 32 random bytes encoded URL-safe; store SHA-256 of
UTF-8 teacher-recovery-attempt:v1\0 + nonce. Setup nonces use the distinct prefix
teacher-recovery-setup:v1\0. Here \0 denotes one NUL byte. Compare nonce
verifiers in constant time. No plaintext code is saved for resume. Pending setup
username and TOTP seed use the approved encryption facility; neither is stored in
the signed cookie. Receipt responses reveal no code/class validity; only complete
set validation returns a generic result. Failed rounds invalidate earlier-round
codes/proofs while preserving recipient selection.
Last updated: 2026-09-15