CORS, Rate Limiting, Audit

View as Markdown

The platform stacks several controls on top of authentication and RBAC. Each is implemented in packages/api/internal/middleware/* and is composable — the production server enables all of them.

13.7.1 CORS

CORS is configured in the router using Chi’s cors middleware. The default production configuration:

1cors.Handler(cors.Options{
2 AllowedOrigins: []string{"https://adid.dev", "https://wallet.adid.dev"},
3 AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
4 AllowedHeaders: []string{"Authorization", "X-API-Key", "Content-Type", "X-Request-Id"},
5 ExposedHeaders: []string{"X-RateLimit-Limit", "X-RateLimit-Remaining", "X-Request-Id"},
6 AllowCredentials: true,
7 MaxAge: 300,
8})

VERIFY: confirm the exact CORS config block in router.go. Dev origins: http://localhost:5173 (Portal Vite) and http://localhost:8081 (Expo dev). Add only what you need; never use * with AllowCredentials: true (browsers will reject it).

13.7.2 Rate limiting

Verified in packages/api/internal/middleware/ratelimit.go. Implements a token bucket per client:

SettingDefaultConfigurable via
Refill rate100 req/sRateLimiterConfig.DefaultRate
Burst capacity200RateLimiterConfig.DefaultBurst
Cleanup interval (idle bucket eviction)10 minutesRateLimiterConfig.CleanupInterval

Client identity (priority order, see identifyClient in ratelimit.go:159-181):

  1. X-API-Key header (most specific — gives the partner their own bucket)
  2. Authorization header (first 20 chars used as key)
  3. X-Forwarded-For IP
  4. X-Real-IP
  5. RemoteAddr

Response headers (always set, even on success):

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1745672461

On limit exceeded (429 Too Many Requests):

1HTTP/1.1 429 Too Many Requests
2Retry-After: 1
3Content-Type: application/json
4
5{ "error": "rate limit exceeded", "status": 429 }

Per-key limits (programmatic at startup):

1rateLimiter.SetKeyLimit("trusted-partner", 1000, 2000) // 1000 req/s, burst 2000

Recommended production settings:

  • Public endpoints (/credentials/verify, /dids/resolve): 50 req/s burst 100 per IP.
  • Authenticated user endpoints: 100 req/s burst 200 per JWT.
  • Admin / API-key callers: 500 req/s burst 1000 per key.

13.7.3 Audit logging

The platform ships with two layers of logging (verified in middleware/logging.go):

Layer 1 — Request log (Zap, structured JSON):

1{
2 "level": "info",
3 "ts": "2026-04-26T13:45:12.183Z",
4 "msg": "request completed",
5 "method":"POST",
6 "path": "/api/v1/credentials/issue",
7 "status":201,
8 "latency_ms": 84,
9 "remote_addr": "203.0.113.42",
10 "request_id": "req_01HXPKR7Y7M2ZP",
11 "auth_scheme": "bearer",
12 "auth_role": "issuer",
13 "auth_subject":"8b1f-...-c2e0"
14}

Every line includes a request_id (UUIDv7 / ULID) propagated via X-Request-Id so a client log can be tied to a server log without grepping.

Layer 2 — Audit events (table audit_events, append-only):

1INSERT INTO audit_events (id, occurred_at, actor_subject, actor_role, action, resource_type, resource_id, request_id, ip, metadata)
2VALUES (gen_random_uuid(), now(), '8b1f...', 'issuer', 'credentials.revoke',
3 'credential', '67e5...c8', 'req_01HXPKR7Y7M2ZP', '203.0.113.42',
4 '{"reason":"compromised"}');

Recommended audit-worthy actions (issuer/verifier-side):

Action keyTriggered by
auth.login.success / .failureOTP verify / OAuth callback
auth.token.refreshRefresh endpoint
auth.logoutLogout endpoint
did.create / .update / .deactivate / .rotateDID CRUD
credentials.issue / .revoke / .batch_revokeIssuer flows
verifier.trusted_issuer.add / .removeTrust framework
agent.register / .update / .decommissionAgent CRUD
agent.delegateIBCT issued
zkp.proof.submit / .verifyZKP flows

VERIFY: confirm whether audit_events is the canonical table or whether logging is solely via Zap. If only Zap, recommend adding the table for compliance retention. Retention: 90 days hot in Postgres, then archive to S3 / object storage. Compliance environments (EU AI Act, ISO 27001) typically require 12–24 months.

13.7.4 Security headers

The Portal nginx config (packages/portal/nginx.conf) and the API edge are responsible for:

HeaderRecommended value
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preload
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
Referrer-Policystrict-origin-when-cross-origin
Content-Security-Policydefault-src 'self'; connect-src 'self' https://adid.dev; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; frame-ancestors 'none'
Permissions-Policycamera=(), microphone=(), geolocation=(self)

CSP note: the Portal’s React build inlines a small CSS critical block — 'unsafe-inline' for styles is the minimum compromise; remove it once styled-components or nonce is wired up.

13.7.5 TLS & certificate hygiene

  • TLS 1.2 minimum at the edge (1.3 preferred). Disable RC4, 3DES, SHA-1.
  • Wildcard cert: *.adid.dev covers Portal, API, Wallet OTA channel. Renew via cert-manager + Let’s Encrypt (HTTP-01 or DNS-01).
  • HSTS preload: submit adid.dev to the HSTS preload list once you are confident TLS is permanent.
  • mTLS for partner integrations: if you accept high-volume partner traffic, terminate mTLS at the LB and forward X-Client-Cert-Subject to the API.