Skip to main content

Security

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

1. Authentication

  • JWT from identity-service (CF); tid claim required on all endpoints except public verification.
  • S2S: API keys for worker ingress (SCORM import pipeline, internal orchestrations).
  • Webhooks from SCORM import partners: HMAC-SHA256 + 5-min timestamp.

2. Authorization

  • RBAC/ABAC via tenant-service policy bundle.
  • Resources: content:package, content:bundle, content:export, content:import.
  • Required roles:
    • content:package:build → author/publisher (owner tenant) or platform_admin.
    • content:bundle:create → internal (triggered by enrollment.created.v1).
    • content:bundle:download → authenticated user with matching enrollment + device binding.
    • content:package:revoke → provider_admin, platform_admin.
    • content:export:scorm|html|xapi → author/publisher.

3. Multi-Tenant Enforcement

LayerRule
APIJWT tid must match X-Tenant-Id header; 403 authz.tenant_not_a_member on mismatch
DomainPlayPackage.tenantId, PlayPackageBundle.tenantId, LicenseEnvelope all anchored to TenantId VO
PostgresRLS USING (tenant_id = current_setting('app.tenant_id')::uuid) on every table
Storage (S3)Per-tenant prefix tenants/{tid}/play-packages/..., tenants/{tid}/bundles/...; bucket policy denies cross-prefix access
Signed URLsScoped per-bundle + per-caller-device; 10-min TTL

4. Data Classification & Encryption

ArtifactClassificationAt-restIn-transit
PlayPackage (unencrypted)ConfidentialAES-256 KMS-sharedTLS 1.3
PlayPackage BundleRestricted (offline-bundled)AES-256-GCM per-device-derived keyTLS 1.3
LicenseEnvelopeRestrictedAES-256 + signed JWSTLS 1.3
SCORM import stagingInternalAES-256TLS 1.3 + mTLS internal
Answer keys (inside package)RestrictedAES-256 with separate KEKTLS 1.3

4.1 Bundle Encryption Key Derivation

DEK = HKDF(
master = KMS-wrapped tenant signing key,
info = "bundle|" || bundleId || "|device|" || devicePublicKeyHash,
length = 32
)
# Encrypt bundle content with AES-256-GCM(DEK, iv = random 96-bit)
  • DEK derivable only by server (with KMS) and device (with its private key counterpart).
  • Device private key never leaves device (secure enclave / Keystore).
  • Bundle tamper → signature verification fails → unmount + content.bundle.tamper_detected.v1.

5. LicenseEnvelope Enforcement (Offline)

Bundle mount steps (player → content-service SDK):

  1. Verify bundle SHA-256 matches manifest.
  2. Verify JWS signature (tenant signing key; cached in device).
  3. Verify LicenseEnvelope.signature.
  4. Verify expiresAt > now.
  5. Verify deviceId matches bound device.
  6. Enforce feature flags: aiTutor, assessments, certificate, copyDownloadable.
  7. Mount read-only; decrypt asset blobs on-demand.

Failure at any step → unmount + diagnostic event.

5.1 Revocation

  • content.play_package.bundle.revoked.v1 propagates via sync → device unmounts within 60s online.
  • Offline devices: license expires at expiresAt; cannot be extended offline.

6. SCORM Import Sandbox

  • Imports run in isolated worker (seccomp + AppArmor; no network; memory cap; CPU cap; 5-min timeout).
  • Manifest (imsmanifest.xml) validated against XSD before extraction.
  • No JS execution during import; all content treated as data.
  • Signed origin allowlist for referenced external resources.
  • Imports quarantined until AV + content-safety scan passes.

7. JWS Signing

  • Tenant signing keys: HSM-backed; rotated annually or on compromise.
  • Signing algorithm: EdDSA Ed25519 (identity-parity) or ES256 (wider client compat). Selected per tenant.
  • Signatures embed kid; rotation overlap ≥ 2 days.
  • Public verification keys distributed via /.well-known/content-keys.json (CDN-cached).

8. Audit Logging

Append-only audit entries for:

  • content.play_package.built / .revoked
  • content.play_package.bundle.published / .revoked
  • License envelope issuance
  • SCORM import completion
  • Tamper detection events

Daily Merkle-anchored root emitted as audit.merkle.anchored.v1.

9. Threat Model

ThreatMitigation
Bundle piracy (share across devices)Per-device key derivation; device binding
Bundle tamperSHA-256 + JWS signature verification on mount
License forgeryJWS signed by tenant HSM key; device verifies
SCORM RCESandbox + manifest validation + no eval
Cross-tenant bundle accessTenant prefix + signed URL scope
Replay attack on signed URLShort TTL (10 min); single-use nonce
Revocation bypass (offline)expiresAt cap; tamper flag persists; next online sync unmounts
AI tutor context leak (via assistant config in manifest)Per-tenant config; no cross-tenant RAG

10. Compliance

  • Part of GDPR erasure saga participation: delete packages, bundles, envelopes tied to erased user.
  • Certificates (in completed courses) retained per tenant policy (may require legal hold).
  • EU AI Act: assistant config provenance tracked.