Status: pre-launch. This evidence reflects implemented code and deployed infrastructure. Provii is not yet serving end-user production traffic, so production operational metrics and audit history are not yet available.
API Security Evidence Collection
Control Coverage: UC-044 through UC-051 Date Generated: 2026-02-14 Status: Complete Repositories Analysed: 4 backend services (provii-verifier, provii-issuer, provii-management, provii-credit-management)
Executive Summary
This document provides evidence for API security controls across the Maelstrom AI backend infrastructure. All services demonstrate defence-in-depth strategies including TLS enforcement, HMAC authentication, RBAC authorisation, security headers, rate limiting, and input validation.
Key Findings:
- TLS enforcement via Cloudflare across all services (UC-044)
- Multi-layered authentication: HMAC-SHA256, JWT sessions, API keys (UC-045)
- RBAC and BOLA protection (UC-046)
- Security headers on all services (13 headers on provii-verifier)
- Rate limiting via shared-rate-limit library and per-endpoint middleware
- Input validation using Zod schemas (TypeScript) and strict newtype wrappers (Rust)
- Audit logging to KV with 90-day retention; critical security event logs are retained for up to 365 days
Table of Contents
- UC-044: Encryption in Transit (TLS)
- UC-045: Authentication Mechanisms
- UC-046: Authorisation Controls
- UC-047: Incident Response
- UC-048: Vulnerability Management
- UC-049: Security Monitoring
- UC-050: Penetration Testing
- UC-051: Security Awareness Training
- API Security Best Practices
- Evidence Artifacts
UC-044: Encryption in Transit (TLS)
Implementation
Cloudflare Universal SSL/TLS: All Maelstrom AI backend services are deployed on Cloudflare Workers, which provides:
- TLS 1.2+ enforcement (TLS 1.3 preferred)
- Automatic certificate management and renewal
- HTTP Strict Transport Security (HSTS)
- Automatic HTTP to HTTPS redirects
Evidence
1. HSTS Headers (All Services)
provii-verifier (src/security/headers.rs):
headers.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")?;
provii-management (src/middleware/security-headers.ts:48):
// Always enabled (Cloudflare Workers run on HTTPS by default)
c.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
provii-credit-management (src/utils/security-headers.ts:27):
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
2. TLS Configuration Evidence
Deployment Platform:
- All services:
wrangler.toml-> Cloudflare Workers deployment - Cloudflare enforces TLS 1.2+ by default
- No plaintext HTTP endpoints exposed to public internet
Local Development:
- Development environments use
http://localhostonly - Production/staging enforce HTTPS exclusively
3. Certificate Management
Cloudflare Automatic Certificate Management:
- Universal SSL certificates auto-issued for all
*.provii.appand*.zerokp.iddomains - Automatic renewal (60-90 days before expiration)
- Certificate transparency logging enabled
- OCSP stapling for revocation checking
Evidence Location:
- Cloudflare Dashboard -> SSL/TLS -> Edge Certificates
- HSTS preload list submission:
hstspreload.org(pending)
UC-045: Authentication Mechanisms
Overview
Maelstrom AI backend services implement multiple authentication mechanisms:
- HMAC-SHA256 per-request signatures (provii-verifier, provii-issuer, provii-management)
- JWT session tokens (provii-management via Logto)
- API key authentication (provii-issuer, provii-credit-management)
1. HMAC-SHA256 Authentication
provii-verifier
Implementation: provii-verifier/src/security/auth.rs
Canonical Message Format:
// From src/routes/challenge.rs lines 245-273
fn create_canonical_message_for_challenge(
method: &str,
path: &str,
timestamp: u64,
body: &CreateChallengeRequest,
) -> String {
let payload = json!({
"code_challenge": body.code_challenge.to_string(),
"method": body.method,
"requested_cutoff_days": body.requested_cutoff_days.map(|c| c.get()),
"verifying_key_id": body.verifying_key_id.map(|v| v.get()),
"expires_in": body.expires_in.get(),
"proof_direction": body.proof_direction,
});
format!("{}:{}:{}:{}", timestamp, method, path, payload.to_string())
}
Security Properties:
- 256-bit HMAC secret (CSPRNG-generated)
- Replay protection via timestamp validation (300-second window)
- Nonce-based duplicate request detection
- Constant-time signature verification (
ct_eqin auth.rs) - Request integrity protection (method + path + body)
Documentation: provii-verifier/docs/security/AUTHENTICATION_PATHWAYS.md
provii-issuer
Implementation: provii-issuer/src/security.rs
Request Signature:
// Lines 43-56
pub struct RequestSignature {
pub method: String,
pub path: String,
pub query_params: Vec<(String, String)>,
pub content_type: String,
pub body_hash: String,
pub timestamp: u64,
}
impl RequestSignature {
pub fn to_canonical_string(&self) -> String {
let mut sorted_params = self.query_params.clone();
sorted_params.sort_by(|a, b| a.0.cmp(&b.0));
let query_string = sorted_params
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("&");
format!(
"{}\n{}\n{}\n{}\n{}\n{}",
self.method, self.path, query_string, self.content_type, self.body_hash, self.timestamp
)
}
}
Verification:
// Lines 89-98
pub fn verify(&self, secret: &[u8], signature: &[u8]) -> Result<bool> {
type HmacSha256 = Hmac<Sha256>;
let mut mac = HmacSha256::new_from_slice(secret)
.map_err(|e| ApiError::CryptoError(format!("Invalid HMAC key: {}", e)))?;
mac.update(self.to_canonical_string().as_bytes());
// Constant-time verification
Ok(mac.verify_slice(signature).is_ok())
}
provii-management
Implementation: provii-management/src/middleware/auth.ts
Canonical Message:
// Lines 131-133 of auth.ts
const method = c.req.method;
const path = c.req.path + (c.req.url.includes('?') ? '?' + new URL(c.req.url).search.slice(1) : '');
const canonicalMessage = createCanonicalMessage(timestamp, method, path, bodySha256);
// from utils/hmac.ts:
export function createCanonicalMessage(timestamp: number, method: string, path: string, bodyHash: string): string {
return `${timestamp}:${method}:${path}:${bodyHash}`;
}
HMAC Verification:
// auth.ts line 136-162
const secret = await c.env.MANAGEMENT_HMAC_KEY.get(); // Secrets Store binding (async)
const isValidSignature = await verifyHMAC(secret, signature, canonicalMessage);
if (!isValidSignature) {
console.warn('Authentication failed: Invalid HMAC signature', {
sessionId,
path,
method,
timestamp,
canonicalMessage,
receivedSignature: signature.substring(0, 30) + '...',
});
throw new HTTPException(401, { message: 'Invalid request signature' });
}
2. JWT Session Tokens
provii-management (Durable Object SessionManager)
Session Retrieval and Validation:
// Lines 168-177 from auth.ts
const session = await getAdminSessionFromDO(c.env.ADMIN_PORTAL_SESSION_MANAGER, sessionId);
if (!session) {
throw new HTTPException(401, {
message: 'Invalid or expired session',
cause: AuthErrorType.SESSION_NOT_FOUND,
});
}
// Validate session expiration
if (session.expiresAt < now) {
throw new HTTPException(401, { message: 'Session has expired' });
}
Session Binding Validation:
// Lines 210-214
// CRITICAL-04 FIX: Validate session binding
await validateSessionBinding(c);
// HIGH-05 FIX: Validate session expiration
await validateSessionExpiration(c);
3. API Key Authentication
provii-issuer
Implementation: provii-issuer/src/security.rs:119-260
pub async fn verify_api_key(env: &Env, api_key: &str) -> Result<ClientRegistration> {
let kv = env
.kv("ISSUER_CLIENTS")
.map_err(|e| ApiError::StorageError(format!("Failed to get KV namespace: {}", e)))?;
let list_result = kv
.list()
.execute()
.await
.map_err(|e| ApiError::StorageError(format!("Failed to list clients: {}", e)))?;
// Try each client (with Argon2id hash verification)
for (idx, key_info) in list_result.keys.iter().enumerate() {
if let Ok(Some(data)) = kv.get(&key_info.name).text().await {
match serde_json::from_str::<ClientRegistration>(&data) {
Ok(client) => {
if !client.active {
continue;
}
// Verify Argon2id hash (constant-time via Argon2 library)
}
}
}
}
Err(ApiError::Unauthorized("Invalid API key".to_string()))
}
Security Properties:
- API keys hashed with Argon2id
- Constant-time comparison (built into Argon2id verification)
- Active/inactive status checking
- KV-based storage with encryption at rest (hashes encrypted before storage)
UC-046: Authorisation Controls
RBAC Implementation
provii-management
Role Definitions:
// From types/index.ts line 211
export type AdminRole = 'super_admin' | 'admin' | 'viewer';
// From types/index.ts lines 365-404
export interface AdminSession {
sessionId: string;
userId: string;
email: string;
role: AdminRole;
createdAt: number;
expiresAt: number;
last_activity: number;
ip_hash: string;
user_agent_hash: string;
allowed_environments?: string[];
organization_id?: string;
}
Role Checking:
// From middleware/auth.ts lines 328-344
function isValidSession(data: unknown): data is AdminSession {
if (!data || typeof data !== 'object') {
return false;
}
const session = data as Partial<AdminSession>;
return (
typeof session.sessionId === 'string' &&
typeof session.userId === 'string' &&
typeof session.email === 'string' &&
typeof session.role === 'string' &&
['viewer', 'admin', 'super_admin'].includes(session.role) &&
typeof session.createdAt === 'number' &&
typeof session.expiresAt === 'number'
);
}
BOLA Protection (Broken Object Level Authorisation)
provii-verifier
Implementation: provii-verifier/src/routes/redeem.rs:168-257
// SECURITY: Mandatory ownership verification (OWASP API1:2023, CWE-639)
match &cached.client_id {
Some(owner_id) if owner_id == &client_id => {
console_log!(
"[SECURITY] redeem_challenge: Ownership verified for challenge {} by client {}",
redact_challenge_id(&sid.to_string()),
client_id
);
}
Some(owner_id) => {
console_log!(
"[SECURITY] redeem_challenge: Ownership verification failed - challenge {} owned by '{}', accessed by '{}'",
redact_challenge_id(&sid.to_string()),
owner_id,
client_id
);
return ApiError::Forbidden("Access denied: you do not own this challenge").to_response();
}
None => {
console_log!(
"[SECURITY] redeem_challenge: Ownership verification failed - challenge {} has no owner",
redact_challenge_id(&sid.to_string())
);
return ApiError::Forbidden("Access denied: challenge ownership not established").to_response();
}
}
Documentation: provii-verifier/docs/security/AUTHENTICATION_PATHWAYS.md (Lines 449-558)
CVSS Score: 7.5 (High) - Mitigated
Migration Status:
- Phase 1 (Complete): All new challenges include
client_id - Phase 2 (In Progress): Monitor legacy challenge usage
- Phase 3 (Month 4-5): 60-day grace period with enforcement date
- Phase 4 (Month 10): Strict enforcement, reject all legacy challenges
Principle of Least Privilege
provii-verifier - Asymmetric Security Design
Public Endpoint (GET /v1/challenge/{id}):
- No authentication required (UUID serves as capability credential)
- Returns minimal data:
challenge_id,short_code,rp_challenge,cutoff_days,verifying_key_id,submit_secret,expires_at,proof_direction,status_url,verify_url submit_secretis a single-use 32-byte bearer token- Sensitive data (origin, client_id, PKCE) requires authentication via separate poll endpoint
Authenticated Endpoints:
POST /v1/redeem. Requires PKCE code_verifier + submit_secretPOST /v1/challenge. Requires HMAC authentication + Client ID extractionGET /v1/challenge/{id}/poll. Requires HMAC authentication + ownership verification
Rationale: Users need to retrieve challenges by short code (accessibility), but sensitive operations require strong authentication and authorisation.
UC-047: Incident Response
Audit Logging
provii-management
Audit Middleware: provii-management/src/middleware/audit.ts
Log Format:
// From types/index.ts lines 219-265
export interface AuditEvent {
eventId: string;
timestamp: number;
action: string;
category: string;
adminUser: {
userId: string;
email: string;
role: string;
};
target?: {
type: string;
id: string;
};
request: {
ip: string;
userAgent: string;
requestId: string;
sessionId?: string;
correlationId?: string;
};
success: boolean;
error?: string;
metadata?: Record<string, any>;
statusCode?: number;
durationMs?: number;
}
All API Calls Logged:
// Applied globally (src/index.ts:149)
app.use('*', auditMiddleware);
provii-verifier
Security Logging Examples:
BOLA Attempt Detection:
// redeem.rs lines 228-233
console_log!(
"[SECURITY] redeem_challenge: Ownership verification failed - challenge {} owned by '{}', accessed by '{}'",
redact_challenge_id(&sid.to_string()),
owner_id,
client_id
);
Authentication Failures:
// auth.rs - HMAC mismatch logging with CWE-778 reference
console_log!("[Auth] HMAC mismatch for client_id={}", client.client_id);
// Structured logging with failure_type: "hmac_signature_mismatch"
Incident Response Procedures
Documentation: src/content/trust/security/incident-response.mdx
Incident Classification:
- Critical (P0): Authentication bypass, data breach, service outage
- High (P1): BOLA attempts, rate limit abuse, suspicious traffic patterns
- Medium (P2): Failed authentication attempts, input validation errors
- Low (P3): Configuration warnings, certificate expiry notices
Incident Tracking:
- All audit events logged to
MANAGEMENT_AUDIT_LOGSKV namespace (90-day retention; critical security event logs are retained for up to 365 days) - Slack webhook integration exists for admin-portal alerts
- Post-incident review procedures defined in incident-response.mdx
UC-048: Vulnerability Management
Dependency Scanning
Automated Scanning:
- npm audit for TypeScript services (provii-management, provii-credit-management)
- cargo audit for Rust services (provii-verifier, provii-issuer)
- Dependabot enabled on all GitHub repositories
- Semgrep for SAST analysis
- Trivy for vulnerability scanning
- Gitleaks for secret detection
Evidence:
# Security workflows vary by repo type:
# Rust repos: .github/workflows/security-audit.yml
# TypeScript repos: .github/workflows/security.yml
# All use SHA-pinned actions (e.g., actions/checkout@v6.0.2)
Penetration Testing
Planned Testing:
- Internal Testing: Quarterly security assessments
- External Pentest: Annual third-party penetration test
- Responsible Disclosure: security@maelstrom.au
Vulnerability Disclosure
Contact: security@maelstrom.au
Disclosure Policy:
- Report submitted -> 48-hour acknowledgment
- Initial assessment -> 5 business days
- Fix timeline -> 30 days for critical, 90 days for high/medium
- Public disclosure -> Coordinated with reporter
UC-049: Security Monitoring
Rate Limiting
All rate limiting uses a simple KV counter pattern (non-atomic increment with TTL, fail-open on KV errors). Tier-based per-customer limits are loaded from RATE_LIMIT_CONFIG KV and cached per-isolate for 60 seconds. The shared-rate-limit library and its Durable Object have been retired and archived.
Architecture:
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: Cloudflare Edge (WAF rate limiting, 5,000/10min/IP)│
├─────────────────────────────────────────────────────────────┤
│ Layer 2: Per-service KV counters (hourly, tier-based) │
│ - Per-customer quota (authenticated, from tier config) │
│ - Per-IP limit (short code enumeration, provii-verifier) │
│ - Per-issuer limit (blind issuance, provii-issuer) │
│ - In-memory auth limiter (admin-portal only) │
└─────────────────────────────────────────────────────────────┘
Per-Service Limits:
| Service | Limit Type | Default | Identifier | Configurable Via |
|---|---|---|---|---|
| provii-verifier | Per-customer hourly quota | DEFAULT_QUOTA_PER_HOUR env var | Authenticated client ID | KV tier config |
| provii-verifier | Short code enumeration | SHORT_CODE_LIMIT_PER_HOUR (60/hr) | CF-Connecting-IP | wrangler.toml env var |
| provii-issuer | Per-customer hourly quota | DEFAULT_QUOTA_PER_HOUR env var | Authenticated client ID | KV tier config |
| provii-issuer | Blind issuance per-issuer | BLIND_ISSUANCE_LIMIT_PER_HOUR (1,000/hr) | Issuer ID | wrangler.toml env var |
| provii-verifier (hosted mode) | Per-customer hourly quota | DEFAULT_QUOTA_PER_HOUR env var | Public key (pk_) | KV tier config |
| admin-portal | Auth brute-force | 5/min per IP | CF-Connecting-IP | In-memory Map |
| provii-management | None (internal, behind admin-portal auth) | . | . | . |
| provii-credit-management | None (internal, service-to-service only) | . | . | . |
HTTP 429 Response Format:
Retry-Afterheader (seconds remaining in current hour)- JSON body:
{ "error": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded", "retry_after": <seconds> }
Nonce Replay Prevention:
NonceDO (src/durable_objects/nonce_do.rs) handles nonce replay detection separately from rate limiting. Nonces expire after 5 minutes (300 seconds).
Alerting
Implemented:
- Slack webhook integration for admin-portal events
- Audit log storage in
MANAGEMENT_AUDIT_LOGSKV (90-day retention; critical security event logs are retained for up to 365 days) - Cloudflare Workers Logs (shipped to Grafana Loki) for real time metrics
Planned (not yet implemented):
- PagerDuty integration for critical alerts
- Email daily digest
- Bespoke Grafana dashboards for rate limit monitoring
- ML-based anomaly detection
UC-050: Penetration Testing
Testing Schedule
Frequency:
- Internal security testing: Quarterly
- External penetration testing: Annual
- Responsible disclosure: Continuous (security@maelstrom.au)
Test Scope
In-Scope:
- All API endpoints (provii-verifier, provii-issuer, provii-management, provii-credit-management)
- Authentication mechanisms (HMAC, JWT, API keys)
- Authorisation controls (RBAC, BOLA)
- Rate limiting effectiveness
- Input validation
- Session management
- Cryptographic implementations
Out-of-Scope:
- Physical security
- Social engineering
- Denial of service attacks (without pre-approval)
- Third-party services (Cloudflare, Logto, Stripe)
Methodology
Standards:
- OWASP API Security Top 10 (2023)
- OWASP ASVS 5.0.0 (Level 3)
- NIST SP 800-115 (Technical Guide to Information Security Testing)
Phases:
- Reconnaissance: Identify attack surface, enumerate endpoints
- Vulnerability Discovery: Automated scanning + manual testing
- Exploitation: Attempt to exploit discovered vulnerabilities
- Privilege Escalation: Test lateral movement and privilege escalation
- Reporting: Document findings with CVSS scores and remediation guidance
Previous Testing Results
provii-verifier ASVS 5.0.0 Hardening (2025-11-07):
Critical fixes implemented as part of ASVS alignment:
- V3.5.8. Sec-Fetch header validation (fail-closed enforcement)
- V14.2.5. Cache headers (cache deception prevention)
- V13.3.4. Secret expiry enforcement (fail-fast logic)
- V3.4.7. CSP endpoint rate limiting (DoS prevention)
- V11.7.2. Debug redaction (secret leakage prevention)
UC-051: Security Awareness Training
Training Program
Target Audience:
- All ISMS roles (sole operator. handles development, DevOps, security, and product)
- Contractors with access to backend services (when engaged)
Training Modules:
- Secure Coding Practices:
- Input validation and sanitisation
- Output encoding
- XSS prevention
- CSRF protection
- Authentication and session management
- API Security:
- OWASP API Security Top 10
- HMAC signature generation and validation
- Rate limiting implementation
- BOLA/IDOR prevention
- Secure error handling
- Cryptography:
- Key management best practices
- Encryption at rest and in transit
- Hash function selection (Argon2id for passwords)
- Random number generation (CSPRNG)
- Incident Response:
- Security incident classification
- Reporting procedures
- Escalation paths
- Post-incident review process
Training Delivery
Format:
- Quarterly training sessions (1-2 hours)
- Annual security awareness day
- On-demand video modules
Documentation:
src/content/trust/security/security-awareness.mdsrc/content/trust/security/roles-responsibilities.md
Tracking:
- Training completion tracked in HR system
- Annual refresher required for all team members
- Security champions program for cross-functional teams
API Security Best Practices
Security Headers
All backend services implement security headers:
provii-verifier
Implementation: provii-verifier/src/security/headers.rs
Default Headers Applied (13 total):
-
Content-Security-Policy:
default-src 'none'; script-src 'none'; style-src 'none'; img-src 'none'; font-src 'none'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; object-src 'none'; form-action 'none'; report-uri /v1/csp-report; report-to csp-endpoint -
Report-To: CSP reporting endpoint configuration
-
Cross origin-Opener-Policy:
same-origin -
Cross origin-Embedder-Policy:
require-corp -
Cross origin-Resource-Policy:
same-origin -
X-Frame-Options:
DENY -
X-Content-Type-Options:
nosniff -
X-XSS-Protection:
1; mode=block -
Strict-Transport-Security:
max-age=31536000; includeSubDomains; preload -
Referrer-Policy:
no-referrer -
Permissions-Policy:
accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=() -
Cache-Control:
no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0 -
X-Permitted-Cross-Domain-Policies:
none
Additional headers (Pragma: no-cache, Expires: 0) are applied to API-specific endpoints via api_security_headers().
provii-management
Implementation: provii-management/src/middleware/security-headers.ts (lines 35-89)
Headers Applied:
c.header('X-Content-Type-Options', 'nosniff');
c.header('X-Frame-Options', 'DENY');
c.header('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none'");
c.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
c.header('Referrer-Policy', 'no-referrer');
c.header('Permissions-Policy',
'accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), ' +
'cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), ' +
// ... 27 directives total (all denied)
);
provii-credit-management
Implementation: provii-credit-management/src/utils/security-headers.ts (lines 23-119)
Dynamic Headers Based on Path Sensitivity:
export function isSensitivePath(path: string): boolean {
const sensitivePaths = [
'/v1/credits/balance',
'/v1/credits/consume',
'/v1/admin/entities',
'/v1/admin/transactions',
];
return sensitivePaths.some(sensitive => path.startsWith(sensitive));
}
// If sensitive:
headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, private';
headers['Pragma'] = 'no-cache';
headers['Expires'] = '0';
Input Validation
provii-management
Implementation: provii-management/src/utils/validation.ts
Zod-Based Validation:
export function validateRequest<T>(schema: z.ZodSchema<T>) {
return async (body: unknown): Promise<
{ success: true; data: T } |
{ success: false; error: string; details: any[] }
> => {
try {
const data = await schema.parseAsync(body);
return { success: true, data };
} catch (error) {
if (error instanceof z.ZodError) {
const details = error.issues.map((e: z.ZodIssue) => ({
path: e.path.join('.'),
message: e.message,
}));
return { success: false, error: details[0]?.message ?? 'Validation failed', details };
}
return { success: false, error: 'Invalid request body', details: [] };
}
};
}
Common Validators:
export const ShortString = z.string().min(1).max(256);
export const MediumString = z.string().min(1).max(1000);
export const LongString = z.string().min(1).max(10000);
export const Email = z.string().email().max(256);
export const PositiveInteger = z.number().int().positive();
export const NonNegativeInteger = z.number().int().min(0);
export const UUID = z.string().uuid();
export const SecureURL = z.string()
.url()
.max(500)
.refine(
(url) => url.startsWith('https://') || url.startsWith('http://localhost'),
{ message: 'URL must use HTTPS (or HTTP for localhost)' }
);
provii-verifier
Implementation: provii-verifier/src/types/strict.rs
Strict Newtype Wrappers for Input Validation:
B64Url32: Validates base64url-encoded 32-byte valuesB64Url192: Validates base64url-encoded 192-byte valuesShortCode: Validates 12-digit numeric short codesCutoffDays: Validates age in days (-25,000 to 54,750 range, i32)ExpiresIn: Validates time-to-live (30-300 seconds, u64)PkceMethod: Enum accepting only “S256”
CreateChallengeRequest:
#[derive(Debug, Clone, Deserialize)]
pub struct CreateChallengeRequest {
pub code_challenge: B64Url32,
pub method: PkceMethod, // Enum, not free-form String
pub requested_cutoff_days: Option<CutoffDays>,
pub verifying_key_id: Option<VkId>,
pub expires_in: ExpiresIn,
pub proof_direction: String,
pub authorizer: Authorizer,
}
CSRF Protection
provii-management Implementation:
Single-Use Token Generation:
// csrf.ts lines 31-49
export async function generateCSRFToken(env: Env, sessionId: string): Promise<string> {
const token = crypto.randomUUID();
const key = `${CSRF_TOKEN_PREFIX}${sessionId}:${token}`;
await env.ADMIN_SESSIONS.put(
key,
JSON.stringify({
session_id: sessionId,
created_at: Date.now(),
expires_at: Date.now() + (CSRF_TOKEN_TTL * 1000),
}),
{ expirationTtl: CSRF_TOKEN_TTL } // 3600 seconds (1 hour)
);
return token;
}
Token Validation and Deletion:
// csrf.ts lines 65-104
// Validates session ownership, checks expiration, deletes after use (single-use pattern)
Middleware Application:
// index.ts line 146
app.use('*', csrfProtection());
CSRF Exemption for HMAC-Authenticated Requests: CSRF checks are skipped for unauthenticated requests (no session), as HMAC authentication provides inherent CSRF protection.
CORS Configuration
provii-management
Strict Origin Validation:
// index.ts lines 112-137
app.use('*', cors({
origin: (origin) => {
if (!origin) {
return '*'; // Service Binding calls (worker-to-worker)
}
const allowedOrigins = [
'https://admin.zerokp.id',
'https://admin-staging.zerokp.id',
'http://localhost:5173',
];
if (!allowedOrigins.includes(origin)) {
throw new HTTPException(403, { message: 'Origin not allowed' });
}
return origin;
},
credentials: true,
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'X-Admin-Session-ID', 'X-Timestamp', 'X-Signature'],
maxAge: 86400, // 24 hours
}));
provii-verifier
Origin Policy Enforcement:
- Origin policies stored in KV namespace (
VERIFIER_KV_CONFIGwithorigin:*key pattern) - Only approved origins can create challenges
- HMAC authentication required for challenge creation and redemption endpoints
- Public GET endpoints (challenge lookup, JWKS) do not require HMAC
Evidence Artifacts
Configuration Files
provii-verifier:
provii-verifier/wrangler.toml. Cloudflare Workers configurationprovii-verifier/src/security/headers.rs. Security headers implementationprovii-verifier/src/security/auth.rs. HMAC authentication (ClientAuthenticator)provii-verifier/src/durable_objects/nonce_do.rs. Nonce replay prevention
provii-issuer:
provii-issuer/wrangler.toml. Cloudflare Workers configurationprovii-issuer/src/security.rs. HMAC verification, API key auth
provii-management:
provii-management/wrangler.toml. Cloudflare Workers configurationprovii-management/src/index.ts. Main application with middleware stackprovii-management/src/middleware/auth.ts. HMAC + session authenticationprovii-management/src/middleware/security-headers.ts. Security headersprovii-management/src/middleware/csrf.ts. CSRF protectionprovii-management/src/middleware/audit.ts. Audit loggingprovii-management/src/utils/validation.ts. Input validation
provii-credit-management:
provii-credit-management/wrangler.toml. Cloudflare Workers configurationprovii-credit-management/src/utils/security-headers.ts. Security headersprovii-credit-management/src/utils/validation.ts. Input validation
Documentation
Security Policies (in provii-verifier/docs/security/):
RATE_LIMITING.md(41 KB)AUTHENTICATION_PATHWAYS.md(44 KB)DATA_CLASSIFICATION.md(64 KB)CRYPTOGRAPHIC_INVENTORY.md(85 KB)KEY_MANAGEMENT_POLICY.md(51 KB)VALIDATION_RULES.md(103 KB)
Compliance:
src/content/trust/compliance/requirements/unified-control-matrix.md. Control matrixsrc/content/trust/compliance/requirements/evidence-mapping.md. Evidence mapping
Monitoring and Alerting
Audit Logs:
- KV namespace:
MANAGEMENT_AUDIT_LOGS - Retention: 90 days (configured in wrangler.toml and audit.ts); critical security event logs are retained for up to 365 days
- Format: JSON structured logs (AuditEvent interface)
Workers Logs in Grafana Loki:
- Real-time request metrics derived from structured JSON log lines
- HTTP status code distribution
- Geographic distribution
- Rate limit violation trends
Control Mapping Summary
| Control | Description | Status | Evidence Location |
|---|---|---|---|
| UC-044 | Encryption in Transit (TLS) | ✅ Implemented | Cloudflare Universal SSL, HSTS headers in all services |
| UC-045 | Authentication Mechanisms | ✅ Implemented | HMAC-SHA256 (provii-verifier, provii-issuer, provii-management), JWT (provii-management), API keys (provii-issuer) |
| UC-046 | Authorisation Controls | ✅ Implemented | RBAC (provii-management), BOLA protection (provii-verifier), ownership verification |
| UC-047 | Incident Response | ✅ Implemented | Audit logging to KV, incident response playbooks |
| UC-048 | Vulnerability Management | ✅ Implemented | npm audit, cargo audit, Dependabot, Semgrep, Trivy, Gitleaks |
| UC-049 | Security Monitoring | ✅ Implemented | Rate limiting via per-service KV counters, Cloudflare Workers Logs (Grafana Loki) |
| UC-050 | Penetration Testing | 🟡 Planned | Annual external pentest, responsible disclosure programme |
| UC-051 | Security Awareness Training | ✅ Implemented | Quarterly training, annual security awareness day, security champions program |
Additional API Security Controls:
- Security Headers (13 headers on provii-verifier, on all services)
- Input Validation (Zod schemas for TypeScript, strict newtype wrappers for Rust)
- CSRF Protection (Single-use tokens with session binding)
- CORS Configuration (Strict origin validation)
- Secret Management (Cloudflare Secrets Store)
- Cryptographic Agility (Documented in CRYPTOGRAPHIC_INVENTORY.md)
Recommendations
Short-Term (1-3 months)
- Implement CSP Reporting Dashboard:
- Store CSP violation reports in KV/DO
- Build visualisation dashboard
- Set up alerting for unusual patterns
- Enhance Rate Limit Monitoring:
- Deploy Grafana dashboards
- Implement anomaly detection algorithms
- Create runbooks for common attack patterns
Medium-Term (3-6 months)
- External Penetration Test:
- Engage third-party security firm
- Focus on OWASP API Security Top 10
- Validate ASVS Level 3 compliance claims
- Automated Security Testing:
- Integrate OWASP ZAP into CI/CD
- Implement API fuzzing with RESTler
- Set up continuous security scanning
- Advanced Threat Detection:
- Deploy machine learning-based anomaly detection
- Implement behavioural analysis for authentication attempts
- Create automated incident response workflows
Long-Term (6-12 months)
- Post-Quantum Cryptography Migration:
- Follow PQC migration roadmap
- Implement hybrid cryptography (classical + PQC)
- Monitor NIST PQC standardisation progress
- Zero Trust Architecture:
- Implement mutual TLS for service-to-service communication
- Deploy service mesh for microservices
- Enhance identity verification at every layer
- Security Certification:
- Pursue SOC 2 Type II certification
- Pursue ISO 27001 certification when commercially justified
- Complete PCI DSS compliance (if accepting card payments)
Conclusion
The Maelstrom AI backend infrastructure demonstrates a defence-in-depth approach to API security. Controls are implemented across all services and are designed to provide protection against common API vulnerabilities. The combination of Cloudflare’s edge security, HMAC authentication, RBAC authorisation, per-service KV counter rate limiting, and security headers is designed to address common API attack vectors.
Compliance Status:
- OWASP API Security Top 10 (2023): Controls implemented across all services
- OWASP ASVS 5.0.0 Level 3: Hardening fixes applied to provii-verifier
- NIST SP 800-63B (Authentication): HMAC + PKCE authentication implemented
- GDPR (Data Protection): Audit logging and data minimisation designed to support GDPR obligations
Evidence Collection Date: 2026-05-21 Next Review: 2026-11-21 (Quarterly) Document Owner: Security Lead Approver: ISMS Owner
Document Classification: Public Review Schedule: Quarterly Version: 2.0