Skip to main content

Security

:::info Source Sourced from services/delivery-service/SECURITY_MODEL.md in the documentation repo. :::

Companion: 13 Security, Compliance & Tenancy · DOMAIN_MODEL · AI_INTEGRATION

1. Threat Model (Service-Specific)​

ThreatSurfaceMitigation
Unauthorized session accessREST APIJWT verification, ownership check on every session operation
Cross-tenant session leakREST API, event consumersRLS on all tables, tenant check in use cases, two-tenant simulator in CI
Offline bundle tamperDevice-side storageJWS license envelope verification, AES-GCM content encryption, checksum validation on mount
License replayLicense envelopeEnvelope includes device-bound nonce + expiry; one-time-use licenses for individual grants
Tutor prompt injectionTutor turn endpointai-gateway prompt-injection classifier; scoped system prompt
Tutor PII exfiltrationTutor turn endpointPII redaction before cloud inference; no-train flag; local inference preferred for sensitive tenants
AI curriculum driftTutor responsesOutput classifier flags off-topic; rate limit on per-session turns
Session hijack via stolen tokenREST APIShort JWT TTL (15 min); device binding; IP anomaly detection
DoS via navigation spamPATCH /navigate120 requests/minute/session rate limit; optimistic concurrency prevents queue buildup
SSE connection exhaustionTutor streamConnection limits per user (5 concurrent); auto-close on inactivity
Bundle download replaymount-offline endpointIdempotency-Key required; mount is unique per (device, bundle)

2. Authentication​

All endpoints require a valid JWT issued by identity-service:

  • Header: Authorization: Bearer <jwt>
  • Algorithm: EdDSA Ed25519
  • Expiry: 15 minutes (refresh handled by client)
  • Required claims: sub (userId), tid (tenantId), did (deviceId), scope

Public endpoints: none. All delivery endpoints require authentication.

3. Authorization​

3.1 Required Permissions​

EndpointPermission
POST /play-sessionsdelivery.play_session:create
PATCH /{id}/navigatedelivery.play_session:navigate (owner only via ABAC)
POST /{id}/pausedelivery.play_session:manage (owner only)
POST /{id}/completedelivery.play_session:manage (owner only)
POST /{id}/abandondelivery.play_session:manage (owner only)
GET /{id}/statedelivery.play_session:read (owner or instructor)
POST /{id}/tutor/turndelivery.tutor:use (owner only)
POST /{id}/mount-offlinedelivery.offline:mount (owner only)
POST /{id}/unmount-offlinedelivery.offline:unmount (owner only)

3.2 ABAC Predicates​

resource.tenant_id == ctx.tenant_id
resource.user_id == ctx.user.id // owner-only ops
resource.enrollment.user_id == ctx.user.id // for navigate/complete

Instructors with instructor.sessions:read scope may view sessions for learners in their assignment groups — enforced by additional predicate:

ctx.user.role in [instructor, org_manager]
AND resource.enrollment.assignment.owner_id == ctx.user.id

4. Multi-Tenant Isolation​

Every table has tenant_id and RLS policy. The application layer also:

  1. Sets app.tenant_id from JWT on every request (via PgBouncer hook).
  2. Rejects any request where X-Tenant-Id header does not match JWT tid.
  3. Validates aggregate tenant membership at construction (domain invariant).
  4. Runs a "two-tenant simulator" integration test in CI that verifies all endpoints refuse cross-tenant access.

5. Offline Bundle Security​

5.1 License Envelope​

License Envelope = JWS(signed by tenant key, content = {
tenantId, userId, deviceId, bundleId, courseVersionId,
issuedAt, expiresAt, nonce
})

On mount, delivery-service:

  1. Verifies JWS signature against tenant public key.
  2. Confirms deviceId in envelope matches JWT did.
  3. Confirms expiresAt > now().
  4. Confirms tenantId, userId match JWT.
  5. Confirms bundleId corresponds to a known bundle.
  6. Validates bundle checksum against content-service's published checksum.

5.2 Bundle Encryption​

Bundle content is encrypted with AES-GCM using a key derived from (tenantKey, devicePubKey, bundleId). Delivery does not handle decryption; the client runtime does. Delivery only verifies integrity.

5.3 Tamper Detection​

If a client reports tampered content (via content.bundle.tamper_detected.v1), delivery-service:

  1. Force-unmounts all affected OfflineMounts.
  2. Pauses any active sessions using those mounts.
  3. Forwards tamper report to content-service for broader investigation.
  4. Alerts security team (PagerDuty P2).

6. Input Validation​

All request bodies validated via class-validator (NestJS DTOs):

FieldValidation
enrollmentIdULID format, exists, user-owned
courseVersionIdULID format, known version
deviceIdULID format, matches JWT did
cursor.moduleIdExists in manifest
cursor.lessonIdExists in manifest, part of moduleId
prompt (tutor)Max 2000 chars, non-empty, UTF-8 valid
licenseEnvelopeValid JWS format, signature verifies
bundleChecksumSHA-256 format

7. Rate Limiting​

Per API_CONTRACTS §4. Implementation uses Redis counters with token-bucket algorithm:

Key: delivery:ratelimit:{endpoint}:{scope}
Value: { tokens: int, lastRefill: timestamp }
TTL: 2x window

Rate-limit exceeded returns 429 Too Many Requests with Retry-After header.

8. Secrets Management​

SecretStorageRotation
JWT verification key (JWKS)KMS via identity-service JWKS endpointAuto-rotated by identity
Database passwordAWS Secrets Manager / Vault90 days
Redis passwordAWS Secrets Manager / Vault90 days
NATS credentialsAWS Secrets Manager / Vault90 days
AI Gateway tokenAWS Secrets Manager / Vault30 days
Tenant signing keys (for envelope)KMS, per-tenant180 days

All secrets loaded at startup; never logged, never returned in API responses.

9. Audit Logging​

The following operations produce audit events sent to the audit sink:

OperationEvent
Session startaudit.delivery.session_started
Offline mountaudit.delivery.offline_mounted (includes license hash)
Offline unmount with tamperaudit.delivery.tamper_response
Cross-tenant attemptaudit.security.cross_tenant_rejected
Unauthorized accessaudit.security.unauthorized_access

Audit events include actor.id, tenant_id, trace_id, source IP, user agent, and action-specific metadata.

10. Compliance​

RegulationDelivery-Specific Requirements
GDPRParticipate in subject deletion via gdpr.subject_request.received.v1; delete sessions and tutor turns for subject
COPPAVerify tenant COPPA flag before tutor turns for minors; redact names in prompts
FERPAEducational institution tenants; strict audit logging for session access by non-owners
SOC 2Audit trail for all session lifecycle operations
HIPAANot applicable (no PHI in delivery context)

11. Security Testing​

TestFrequency
SAST (Semgrep)Every PR
DAST (OWASP ZAP)Nightly against staging
Dependency scan (Snyk / npm audit)Every PR + daily
Pen testAnnual + after major releases
Tenant isolation testEvery PR (CI)
Offline bundle tamper testWeekly
JWT fuzzingMonthly
License envelope fuzzingMonthly

12. Incident Response​

SeverityConditionResponse
P1Cross-tenant data leak confirmedImmediate service halt, legal/compliance notification, incident bridge
P2Tamper detection spikeInvestigate bundle, rotate tenant keys, alert content team
P3AI tutor prompt injection succeedingUpdate classifier, review affected turns, notify ai-gateway team
P3Rate-limit bypassPatch, deploy, monitor

All P1/P2 incidents require post-mortem with action items tracked in platform backlog.