Skip to content

CDN Access Control

Security analysis of CDN access control for the Item Image Upload feature. This document is intended for review by security auditors. It describes the threat model, the chosen solution (CloudFront signed cookies), the security properties it provides, residual risks, and operational procedures.

The Arda platform stores product images in an S3 bucket and serves them via CloudFront CDN. These images are sensitive — customers may upload photographs of internal R&D prototypes, proprietary manufacturing processes, or confidential product designs. The CDN access control mechanism must satisfy two requirements:

  1. System-generated URLs only. Only URLs generated by the Arda system should be resolvable. An attacker who guesses or constructs a URL should not be able to access image content.
  2. Tenant isolation. A user operating in Tenant A’s context must not be able to access images belonging to Tenant B, even if they know or obtain the URL.

An additional constraint is that the active tenant of a user session may change without re-authentication (multi-tenant users switching between organizations). The access control mechanism must handle this transition securely.

Design decisions: TD-11, TD-12


CloudFront signed cookies use RSA-based cryptographic signatures to restrict access to CDN content. The mechanism has three components:

  1. Key pair: An RSA key pair is generated and managed by the infrastructure team. The public key is registered with CloudFront as a trusted key. The private key is stored securely (AWS Secrets Manager) and used only by the BFF to sign cookies.

  2. Cookie issuance: When a user authenticates, the BFF generates three Set-Cookie headers containing a custom policy, its RSA signature, and the key pair ID. The custom policy specifies:

    • Resource: https://<partition>.<infra>.assets.arda.cards/<tenantId>/* — restricts access to the active tenant’s image namespace.
    • DateLessThan: Current time + TTL (default 30 minutes) — restricts access temporally.
  3. Cookie validation: On every CDN request, CloudFront validates the cookie signature against the trusted key group and checks that the requested URL matches the Resource pattern and that the current time is before DateLessThan. Invalid or missing cookies result in 403 Forbidden.

AttributeValueSecurity Rationale
Domain.arda.cardsShared registrable domain allows <img> requests from app.arda.cards to carry cookies to assets.arda.cards without cross-site restrictions. Accepted trade-off: This sends cookies to all *.arda.cards subdomains (including api.arda.cards). Scoping to <partition>.<infra>.assets.arda.cards would be more precise but browsers set the cookie domain to the full FQDN, preventing <img> tags on app.arda.cards from sending cookies to assets.arda.cards. The broad domain is required for cross-subdomain cookie delivery. The cookies are HttpOnly and carry no application secrets — the overhead is three cookie headers (~1 KB) on non-CDN requests.
Path/Broad path — the Resource field in the signed policy provides the actual path restriction.
SecureYesCookie transmitted only over HTTPS. Prevents interception over unencrypted connections.
HttpOnlyYesCookie inaccessible to JavaScript. Mitigates XSS-based cookie theft.
SameSiteLaxSufficient because app.arda.cards and assets.arda.cards are same-site (same registrable domain). <img> tags in same-site context send cookies automatically. Prevents cross-site request forgery from external domains.

PlantUML diagram


ThreatPrevention Mechanism
Unauthenticated accessCloudFront rejects requests without valid signed cookies (403). No anonymous access to any image.
Cross-tenant accessCookie policy Resource field restricts to /<tenantId>/*. Tenant A’s cookie cannot access Tenant B’s paths. CloudFront validates the requested URL against the Resource pattern.
URL guessingEven with a correct URL, the request fails without a valid signed cookie. UUID-based keys (122 bits of entropy) provide additional protection.
Cookie forgeryCookies are RSA-signed. Without the private key, an attacker cannot forge a valid signature. The private key never leaves Secrets Manager / BFF runtime.
Cookie theft via XSSHttpOnly flag prevents JavaScript access to cookies. An XSS attack cannot read the cookie values.
Cookie theft via network interceptionSecure flag ensures cookies are only sent over HTTPS.
Cross-site cookie exfiltrationSameSite=Lax prevents cookies from being sent with cross-site requests initiated by external sites.
Tenant escalation via APIThe BFF cdn-cookies endpoint extracts the tenant ID from the authenticated session, not from a client parameter. A compromised SPA cannot request cookies for a different tenant.
ThreatResidual RiskMitigation
Cookie exfiltration from a compromised browserIf an attacker has full control of the user’s browser (e.g., malicious extension, physical access), they can extract cookies from the browser’s cookie store.Short TTL (30 min) limits the window. HttpOnly prevents JS-based extraction. Browser-level compromise is outside the application’s trust boundary.
Insider threat (authenticated user sharing images)A user with valid access can screenshot, download, or share images they are authorized to view.Out of scope for CDN access control. This is a data loss prevention (DLP) concern addressed by organizational policy, watermarking, or DRM — none of which are in scope.
Signing key compromiseIf the CloudFront private key is leaked, an attacker can forge valid cookies for any tenant.Key is stored in Secrets Manager with restricted access. Key rotation procedure (OAM-S-012) enables rapid revocation. CloudFront trusted key groups support multiple active keys for zero-downtime rotation.
Tenant switch race conditionDuring the brief window between tenant switch and cookie refresh (~200-500ms), the SPA holds cookies for the old tenant. No security impact — the old cookies only grant access to the tenant the user already had access to. The SPA shows loading state during the transition.Acceptable. No data leakage occurs.

OptionDescriptionWhy Not Chosen
Public CDN (no auth)Serve all images without authentication. Rely on UUID key entropy for protection.Rejected — product images are sensitive. URL disclosure (logs, shared links, browser history) would expose content.
CloudFront signed URLs (per-image)Generate a signed URL for each image on every entity query.Higher overhead: every entity read must transform URLs. Defeats cache sharing between users (unique URLs). More complex client-side code. Signed cookies provide equivalent security with better performance.
Lambda@Edge JWT validationValidate the user’s JWT on every CDN request via Lambda@Edge.CloudFront Functions cannot verify RS256/ES256 JWTs. Lambda@Edge adds latency (~50ms) and requires us-east-1 deployment. Cookie validation is sub-ms at the CloudFront edge with no Lambda involvement.
Presigned GET URLs (bypass CDN)Generate presigned S3 GET URLs via the Backend for every image load.Eliminates CDN entirely. Every image load hits the Backend. Defeats the purpose of CDN-based delivery (TD-06).
CloudFront Functions with HMACUse a CloudFront Function to validate an HMAC-signed token.Requires custom token protocol. Less standard than CloudFront’s native signed cookie support. Same security properties but more implementation effort and a larger attack surface.

The cookie TTL balances two competing concerns:

ConcernFavorsImpact
SecurityShorter TTLReduces breach window from exfiltrated cookies. A 30-minute cookie is useless to an attacker after 30 minutes.
PerformanceLonger TTLReduces BFF load from cookie refresh requests. At 30 min TTL with proactive refresh at 15 min, each user generates ~4 cookie requests/hour.
UX on tenant switchShorter TTL is neutralTenant switch always requires an immediate cookie refresh regardless of TTL.

Recommended default: 30 minutes. This aligns with typical session token refresh intervals in the existing system. The TTL is configurable (OAM-C-002 pattern) to allow tightening for high-security deployments.

Refresh strategy: The SPA refreshes proactively at ~50% of TTL (15 minutes for a 30-minute TTL). This ensures cookies do not expire during normal use. If the proactive refresh fails (BFF unavailable), the SPA falls back to 403-triggered refresh on the next image load.


Multi-tenant users can switch the active tenant without re-authenticating. This creates a transition where CDN cookies must be re-scoped:

PhaseStateSecurity Property
Before switchCookies scoped to Tenant A.User can view Tenant A’s images. Cannot view Tenant B’s.
During switch (~200-500ms)SPA requests new cookies. Old cookies still present in browser.Old cookies still valid for Tenant A (user had legitimate access). Images for Tenant B return 403 (old cookies don’t match). SPA shows loading state.
After switchNew cookies scoped to Tenant B. Old cookies overwritten (same cookie names).User can view Tenant B’s images. Cannot view Tenant A’s (cookies replaced).

Key property: At no point during the transition does the user gain access to a tenant they are not authorized for. The BFF cdn-cookies endpoint extracts the tenant ID from the authenticated session context — it is impossible for the SPA to request cookies for a tenant the user is not authorized to operate in.


Frequency: At minimum annually, or immediately if compromise is suspected.

Procedure (zero-downtime):

  1. Generate a new RSA key pair.
  2. Store the new private key in Secrets Manager under a new version.
  3. Upload the new public key to CloudFront and add it to the trusted key group (both old and new keys are now active).
  4. Update the BFF configuration to use the new private key for signing.
  5. Wait for all existing cookies to expire (max 1 TTL period = 30 minutes).
  6. Remove the old public key from the trusted key group.
  7. Delete the old private key from Secrets Manager.

During steps 3–5, cookies signed with either key are valid. There is no downtime or user impact.

If the signing key is known to be compromised:

  1. Immediately remove the compromised public key from the CloudFront trusted key group.
  2. All existing cookies signed with the compromised key become invalid instantly.
  3. Users will see 403 on all image loads until they receive new cookies.
  4. Generate and deploy a new key pair (steps 1–4 of rotation procedure).
  5. Users’ next cookie refresh (proactive or 403-triggered) will use the new key.

Impact: Brief image outage (~15-30 seconds) while SPA detects 403 and requests new cookies. No data leakage after key removal.

MetricSourceAlert Condition
CloudFront 403 rateCloudFront metricsSpike indicates expired/missing cookies (normal during key rotation) or attack. Sustained elevation indicates misconfiguration.
BFF cdn-cookies endpoint call rateBFF application logsRate significantly exceeding expected (users × refresh_frequency) may indicate a retry storm or misconfigured TTL.
Cookie signing latencyBFF application logsP95 > 100ms indicates key loading or cryptographic performance issues.

PropertyStatus
Data classificationSensitive — customer R&D prototype images
Authentication mechanismCloudFront signed cookies (RSA-SHA1 custom policy)
Authorization scopePer-tenant path prefix (/<tenantId>/*)
Cookie securitySecure; HttpOnly; SameSite=Lax
Cookie TTL30 minutes (configurable, shorter for high-security)
Tenant switchImmediate cookie re-issuance; no cross-tenant leakage during transition
Key managementRSA key pair in Secrets Manager; zero-downtime rotation via trusted key groups
Emergency revocationKey removal from trusted group invalidates all cookies instantly
Residual risksBrowser-level compromise, insider threat, signing key compromise (see table above)
CDN cachingPreserved — cookies are not part of the cache key
Related documentsrequirements.md (FR-036–039, NFR-017–020), oam-requirements.md (OAM-S-011–015), bff-specification.md (BFF-FR-008–010), aws-specification.md (CloudFront configuration)

Copyright: (c) Arda Systems 2025-2026, All rights reserved