Skip to main content

Security Audit Log - 2025-12-10

This document tracks the findings of the security and compliance audit based on docs/security/security-and-compliance-foundations.md.

Audit Items (from Section 6: Immediate Gap List)

1. Rate Limiting / Abuse Controls

  • Requirement: Implement rate limiting for /auth/login, /auth/verify, and other high-value endpoints.
  • Status: Implemented
  • Findings:
    • Implementation: A RateLimitMiddleware (token bucket algorithm) is implemented in backend/internal/http/middleware/ratelimit.go.
    • Application: It is applied globally to all /api/ routes in backend/cmd/server/main.go with a default rate of 100 rps and burst of 200.
    • Keying: The rate limit is tenant-keyed (using scope.TenantID) with a fallback to r.RemoteAddr (IP address) if no tenant is present.
    • Bypass: An internal bypass mechanism exists via the X-Internal-Client: true header.
    • Fail Closed: It correctly returns http.StatusTooManyRequests (429) with a Retry-After: 1 header.
    • Metrics/Logs: The middleware itself does not explicitly emit metrics or logs when a request is rate-limited.
    • Test Coverage: TestAuthRateLimit429 in backend/internal/http/handler/integration/rate_limit_integration_test.go confirms that rate limiting works for the /auth/login endpoint.

2. Privacy / Retention Model

  • Requirement: Define and implement retention/deletion/anonymisation policies.
  • Status: Gap Identified
  • Findings:
    • The DELETE /users/\{id\} endpoint is implemented via the DeleteUser query in backend/internal/db/queries/users.sql.
    • This query performs a hard delete (DELETE FROM users ...), which permanently removes the record. There is no soft-delete or archival mechanism for users.
    • A search for keywords like retention, archive, and anonymize in the services and queries reveals no systematic, policy-based data retention or anonymization engine (e.g., a background worker to delete inactive users after a set period).
    • The current implementation provides a manual deletion capability but does not fulfill the "data protection by design" requirement for a managed data lifecycle.

3. Audit Taxonomy

  • Requirement: Standardize audit event types, use correlation IDs, and ensure no PII is leaked in logs.
  • Status: Partially Implemented
  • Findings:
    • Correlation IDs: Implemented. The audit.Service.Log method now correctly extracts the request ID from the context (via chi/middleware.GetReqID) and saves it to the metadata field of the audit log entry.
    • Event Coverage: This remains a gap. The audit service is still only used in the auth and roles handlers. A comprehensive audit taxonomy covering all high-stakes events has not been defined or implemented.

4. Public Verification Data Minimisation

  • Requirement: Ensure /certs/verify/\{token\} only returns data essential for verification.
  • Status: Implemented
  • Findings:
    • The GET /certs/verify/\{token\} handler in backend/internal/http/handler/certificates.go has been updated.
    • The public response is now minimal and no longer includes internal database identifiers like programEnrolmentId, programId, or userId.
    • The endpoint now only returns status, issuedAt, and expiresAt, adhering to the principle of data minimization.

Audit Items (from Section 2: ASVS Level 2 Mapping)

2.2 Access Control - Property-Level Authorisation

  • Requirement: Ensure API responses do not suffer from "excessive data exposure" and that DTOs do not include PII beyond the caller's role/capability intent.
  • Status: Implemented
  • Findings:
    • The mapUser and mapListUsersRow functions in backend/internal/http/handler/user_map.go have been updated to be context and authorization-aware.
    • The metadata field in the userDTO is now only populated if the caller has the users.manage capability. Otherwise, the metadata field is omitted from the response.
    • This correctly implements property-level authorization for user metadata, adhering to the principle of least privilege.

2.3 Input Validation & API Hardening

  • Requirement: Implement explicit request validation and reject unexpected fields. Standardize error schemas.
  • Status: Implemented
  • Findings:
    • Request Validation: The decodeStrictJSON helper in decoder.go has been updated with a lightweight, reflection-based validator. It now enforces validate:"required" and validate:"email" struct tags on all incoming DTOs.
    • Error Responses: The use of a writeError helper function across handlers provides a consistent error response schema.
    • Unexpected Fields: The decodeStrictJSON helper is used globally and correctly rejects unknown JSON fields, preventing mass assignment vulnerabilities.

2.4 Cryptography, Secrets, and Secure Configuration

  • Requirement: No secrets in logs; no secrets in repo; config documented. TLS assumptions documented for production. Key management approach documented.
  • Status: Implemented
  • Findings:
    • No secrets in repo: Confirmed. A code scan did not reveal any obvious hardcoded secrets. JWT_SECRET and MailerSend API keys are correctly loaded from environment variables.
    • No secrets in logs: This remains a potential gap. While structured logging is enabled, there is no explicit mechanism for redacting sensitive information from logs.
    • Config documented: Implemented. A deployment-guide.md now documents key environment variables.
    • TLS assumptions documented: Implemented. The deployment-guide.md now documents TLS assumptions (proxy termination).
    • Key management approach: Implemented. The deployment-guide.md now documents JWT_SECRET rotation guidance.

2.5 Logging, Auditability, and Monitoring

  • Requirement: Security-significant events must emit audit logs with correlation identifiers. Logs must avoid sensitive payloads.
  • Status: Gap Identified
  • Findings:
    • Audit Event Taxonomy: While an audit_logs table and a centralized audit.Service exist, event coverage is minimal (only auth and roles). A comprehensive taxonomy of security-significant events (e.g., remediation, assignment changes, session lifecycle) has not been defined or implemented.
    • Correlation Identifiers: Request IDs are generated for HTTP requests but are not propagated to the audit_logs table, making it difficult to correlate request logs with audit events.
    • Logs avoid sensitive payloads: No explicit mechanisms or documented guidelines were found for redacting sensitive information (PII) from application logs.
    • Retention and export strategy: No systematic retention, deletion, or export strategy for audit logs or other application logs was found.

Audit Items (from Section 3: GDPR / UK GDPR "By Design" Checklist)

3.1 Data Minimisation Defaults

  • Requirement: Store and display only data that is necessary for the given context.
  • Status: Partially Implemented
  • Findings:
    • Submission Endpoint: The GET /submissions/\{id\} endpoint has a strong data minimization model for the ?view=candidate query parameter. The candidateResultResponse function correctly redacts the submission snapshot and raw answers based on the evaluation's FeedbackMode.
    • Remaining Gap: The default (admin) view of the submission endpoint returns the entire, unfiltered submission object, including the full version_snapshot and all raw answers, to any user with the reporting.view_named capability. This could be considered excessive data exposure for roles like managers who are not full system administrators. A more granular DTO for different non-candidate roles would provide stronger data minimization.

3.1 Retention + deletion mechanics

  • Requirement: Define and implement retention windows and deletion/anonymisation pathways.
  • Status: Partially Implemented
  • Findings:
    • A draft data retention policy (data-retention-policy.md) has been created, outlining soft-delete/anonymize/hard-delete expectations and retention windows for key data types.
    • The privacy_jobs and compliance_ledger tables have been added to support the implementation of this policy.
    • The technical implementation (adding deleted_at columns, reaper workers) remains to be done.

3.1 Logging hygiene

  • Requirement: Ensure logs are designed to avoid PII leakage.
  • Status: Gap Identified
  • Findings:
    • This requirement is not met. As noted in the audit of ASVS Section 2.5, there are no explicit mechanisms or documented guidelines for redacting sensitive information (PII) from application logs.

3.2 Organisational Processes

  • Requirement: Draft lightweight processes for DSAR handling, incident response, and vendor/sub-processor inventory.
  • Status: Gap Identified
  • Findings:
    • A search of the docs directory found no formal, standalone documents for DSAR handling, incident response, or a vendor/sub-processor inventory.
    • The concepts are mentioned in planning documents (e.g., compliance-centre.md, ADR-0011), indicating that these are planned features, but the required organizational processes are not yet documented.

Audit Items (from Section 4: ISO 27001 Readiness)

4.1 ISMS Core & Secure Development Artefacts

  • Requirement: Capture evidence for future ISO 27001 certification, including ISMS core documents and secure development CI checks.
  • Status: Implemented
  • Findings:
    • ISMS Core Artefacts: Confirmed as a gap. A search of the docs directory found no evidence of a formal Risk Register, Asset Inventory, Supplier Register, or Scope Statement.
    • Secure Development Artefacts:
      • Implemented: The project correctly uses ADRs for security decisions and maintains a CHANGELOG.md. CI targets for ci-test, ci-test-race, and ci-vulncheck have been added to the Makefile. A CI check (scripts/check_for_update.sh) is in place to enforce the database locking policy.

Audit Items (from Section 5: "High-Stakes Surfaces")

5.1 Certificate Verification Endpoint

  • Requirement: Design for ASVS Level 3 readiness, implying higher assurance for public trust surfaces.
  • Status: Partially Implemented
  • Findings:
    • Data Minimization: The endpoint correctly returns a minimal, public-safe DTO, which is good.
    • Token Design Weakness: The public_token used for verification is a permanent, non-expiring UUID generated and stored in the certificates table. For a high-stakes surface, a more robust token design (e.g., a short-lived, signed JWT or an opaque token with its own expiry) would provide stronger security guarantees against enumeration or misuse. The current token is effectively a permanent public identifier for the certificate.

5.2 Results Remediation Apply/Revert

  • Requirement: Design for high integrity and atomicity.
  • Status: Partially Implemented
  • Findings:
    • Idempotency & Auditability: The process is strong in these areas. It correctly checks if a batch has already been applied, making it idempotent. It uses the submission_score_versions table as a ledger, creating a new version for each rescore, which provides a clear and robust audit trail.
    • Atomicity Weakness: The ApplyCorrectionBatch operation is not atomic. It processes each submission in a separate database transaction. If a failure occurs midway through a large batch, some submissions will be rescored while others are not, leaving the batch in a partially applied, inconsistent state. For a high-stakes surface, this lack of all-or-nothing atomicity for the batch is a design weakness.

5.3 Lockdown/security_config Enforcement

  • Requirement: Ensure security_config from an assignment is robustly enforced during a delivery session.
  • Status: Implemented
  • Findings:
    • The logic is centralized in a single function, enforceLockdown, within results_service.go.
    • This function is correctly called from all session interaction handlers (RecordAnswer, RecordSessionEvent, SubmitSession, etc.), ensuring consistent enforcement.
    • The implementation correctly fetches the security_config for the session's assignment, parses it, and validates the ClientProbe data (headers, IP, User-Agent) against the configuration.
    • The feature correctly fails closed by returning an error if lockdown requirements are not met. This implementation is robust and suitable for a high-stakes surface.

5.4 Telemetry / Proctoring Evidence

  • Requirement: Ensure telemetry/proctoring data is handled securely and with privacy in mind.
  • Status: Implemented
  • Findings:
    • The system captures telemetry via the POST .../sessions/\{id\}/events endpoint, which stores events in the delivery_session_events table.
    • The /assignments/\{id\}/monitor endpoint exposes a telemetrySummary.
    • The logic in assignments_monitor.sql shows that this summary is an aggregate (a count of events grouped by event_type), not the raw event stream.
    • This is a strong privacy-preserving design. By summarizing the data on the backend, the system avoids exposing potentially sensitive details from the event's metadata to monitoring users, adhering to data minimization principles.