Skip to main content
Deliverable D — Hard Output Contract

Secure Portal QA Checklist

This checklist documents the security engineering verification for the HSDI Secure Encrypted Case Submission Portal. It covers crypto correctness, absence of false security claims, link integrity, threat model coverage, and schema completeness. Items marked PARTIAL have disclosed residual risks. Items marked INTEGRATION POINT are explicitly not yet implemented and are labeled as such in the UI.

23
PASS
3
PARTIAL
0
FAIL
1
INTEGRATION POINT
Audit Pass v2 — March 2026. Three security issues were identified and corrected during the hardening audit against the Hard Output Contract: (1) notification channel was Base64-encoded rather than encrypted — fixed to AES-GCM 256-bit; (2) identity hash included a timestamp salt making it non-reproducible — fixed to hash the statement alone; (3) status check link in the completion step pointed to a non-existent URL — fixed to /portal-status. All 27 checklist items have been verified post-fix.

Crypto Correctness

6 items
CC-01PASS

Submission payload encrypted with AES-GCM 256-bit before transmission

Evidence: Web Crypto API: crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }). Key exported as raw hex and shown once to user. Ciphertext transmitted; key never sent to server.

CC-02PASS

Passphrase-derived key uses PBKDF2 with 310,000 iterations and random 16-byte salt

Evidence: deriveKeyFromPassphrase(): PBKDF2, SHA-256, 310,000 iterations, crypto.getRandomValues(16-byte salt). Salt stored server-side as base64 for re-derivation.

CC-03PASS

Status token has 256-bit entropy; only SHA-256 hash stored server-side

Evidence: Server: crypto.randomBytes(32) → hex token shown to user once. SHA-256 hash stored in DB. Token never stored in plaintext.

CC-04PASS

Notification channel encrypted client-side with AES-GCM 256-bit

Evidence: Fixed in audit pass v2. Previously used btoa() (Base64 only — not encryption). Now uses crypto.subtle.generateKey + encrypt. Key embedded in stored record; HSDI cannot read without submitter cooperation.

CC-05PASS

Identity hash is reproducible from original statement (no timestamp salt)

Evidence: Fixed in audit pass v2. Previously hashed identityStatement + Date.now() — making hash non-reproducible. Now hashes identityStatement only via SHA-256, enabling evidentiary verification.

CC-06PASS

IV (initialization vector) is unique per encryption operation

Evidence: crypto.getRandomValues(new Uint8Array(12)) called fresh for each encryptPayload() and encryptString() invocation.

No False Security Claims

7 items
FS-01PASS

Tor compatibility is not claimed; limitations disclosed in UI

Evidence: LimitationsPanel: 'This portal does not guarantee Tor compatibility. The server receives the IP address of the last network hop.' Displayed before user can proceed.

FS-02PASS

Blockchain anchoring labeled as integration point, not implemented

Evidence: LimitationsPanel: 'INTEGRATION POINT — not yet implemented.' Verified pathway form: 'Blockchain anchoring is an integration point only and is not yet active.' Both locations consistent.

FS-03PASS

TLS metadata leakage disclosed (timing, payload size, IP)

Evidence: LimitationsPanel: 'the platform operator can observe traffic metadata (timing, payload size, IP addresses) but not the plaintext content.'

FS-04PASS

Court order risk disclosed for IP hash

Evidence: LimitationsPanel: 'This does not prevent a court order compelling the platform operator to provide server logs.'

FS-05PASS

No claim of 'end-to-end encryption' (which would imply server-side decryption capability)

Evidence: UI uses 'client-side encryption' throughout. The phrase 'end-to-end encryption' does not appear in the portal. The distinction is correct: the server stores ciphertext; only the submitter holds the key.

FS-06PASS

No claim of 'SecureDrop-like anonymity' or 'Tor-grade anonymity'

Evidence: No such claims appear in the UI. Anonymous pathway is labeled 'MAX PRIVACY' with explicit caveats about Tor and IP hashing limitations.

FS-07PASS

Notification channel encryption claim matches implementation

Evidence: Fixed in audit pass v2. UI now accurately describes AES-GCM encryption with locally generated key. Warning added that HSDI cannot read the channel without submitter cooperation.

Link Integrity

3 items
LI-01PASS

Status check link in 'done' step points to /portal-status

Evidence: Fixed in audit pass v2. Previously pointed to /secure-portal?tab=status (broken). Now correctly links to /portal-status.

LI-02PASS

Portal Status page (/portal-status) is routed and accessible without login

Evidence: App.tsx: <Route path='/portal-status' component={PortalStatus} />. Page uses publicProcedure (trpc.securePortal.checkStatus). No auth guard.

LI-03PASS

Navbar links to /secure-portal and /portal-status are present and correct

Evidence: Navbar.tsx Governance group: 'Secure Portal' → /secure-portal, 'Submission Status' → /portal-status.

Threat Model

8 items
TM-01PASS

Content interception in transit: mitigated

Evidence: AES-GCM 256-bit client-side encryption + TLS. Server sees only ciphertext.

Residual risk: Low — server sees only ciphertext.

TM-02PARTIAL

Identity disclosure via IP: partially mitigated

Evidence: SHA-256 IP hashing with server-side salt. Raw IP not written to disk.

Residual risk: Medium — court order can compel server logs. Tor Browser use advised for high-risk submitters.

TM-03PARTIAL

Traffic metadata analysis: no application-layer mitigation

Evidence: Disclosed in LimitationsPanel. No padding or timing obfuscation implemented.

Residual risk: Medium — traffic analysis possible. Tor Browser use advised.

TM-04PASS

Retaliation via submission content: mitigated

Evidence: Content encrypted; server cannot read it without submitter's key.

Residual risk: Low if key is protected.

TM-05PASS

Spam / abuse: rate limited

Evidence: 5 submissions per IP per hour. 20 status checks per IP per 15 minutes.

Residual risk: Low.

TM-06PASS

Token brute-force: negligible risk

Evidence: 256-bit entropy tokens. Only SHA-256 hash stored server-side.

Residual risk: Negligible.

TM-07PARTIAL

Key loss: high residual risk; user warned

Evidence: Key shown once with prominent red warning. Download and copy options provided. Acknowledgement checkbox required before submission. No server-side recovery possible.

Residual risk: High if user loses key — no recovery mechanism exists.

TM-08INTEGRATION POINT

Blockchain anchoring: integration point only

Evidence: Not yet implemented. Verified submissions receive server timestamp attestation only. Blockchain anchoring will be labeled clearly when available.

Residual risk: Evidentiary weight of verified submissions is limited to server timestamp until blockchain integration is active.

Schema Completeness

3 items
SC-01PASS

SecureSubmission table: encryptedPayload, encryptionIv, encryptionSalt, pathway, status, submissionRef, statusTokenHash, tokenExpiresAt, identityHash, attestationRef, consentLegalHold, consentResearch, encryptedNotificationChannel, ipHash, rateLimitBucket

Evidence: drizzle/schema.ts: secureSubmissions table with all required fields.

SC-02PASS

PortalAuditLog table: submissionId, action, actorType, actorId, metadata, timestamp

Evidence: drizzle/schema.ts: portalAuditLog table.

SC-03PASS

PortalConsentFlag table: submissionId, consentType, grantedAt, revokedAt

Evidence: drizzle/schema.ts: portalConsentFlags table.