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.
Data Lifecycle Controls Evidence
Control Domain: Privacy Controls / Data Lifecycle Author: Maelstrom AI Date: 2026-02-14 Status: Evidence Collection Complete
Scope note. Maelstrom AI operates two platforms under one ISMS — Provii (zero knowledge age verification) and downpipes (no-custody backup and disaster recovery for the Cloudflare data layer). Data lifecycle is a cross-cutting control: the bulk of this document evidences the Provii verification infrastructure, where the zero knowledge architecture means no end-user PII is stored server-side. Where the lifecycle controls also apply to downpipes, this is called out explicitly. downpipes is no-custody: the engine and console run in the customer’s own Cloudflare account, under the customer’s keys, so Maelstrom holds no customer keys, data or Cloudflare tokens and therefore retains, deletes and erases no customer data on the vendor side. Retention, deletion and disposal of customer archives are determined by the customer; see the Cloudflare Backup Evidence for Maelstrom’s own self-hosted instance.
Executive Summary
This document provides evidence of data lifecycle controls across Maelstrom AI’s platforms — primarily the Provii verification infrastructure, with downpipes lifecycle controls noted where they apply — demonstrating compliance with:
- UC-017: Retention Limitation
- UC-018: Privacy Policy and Standards Compliance
- UC-102: Data Retention Policies
- UC-103: Automated Data Deletion
- UC-104: Data Anonymisation and Pseudonymisation
- UC-105: Data Portability
- UC-128: Access Reviews and Recertification
- UC-129: Just-In-Time (JIT) Access
Key Findings:
- Documented retention policies for all data types
- Automated deletion via TTL, cleanup workers, and retention policies
- IP logs retained for 90 days only (Cloudflare Workers Logs shipped to Grafana Loki)
- Challenges expire in 5 minutes (KV TTL auto-cleanup)
- Maelstrom’s entire Cloudflare estate is backed up by our own production instance of downpipes (we dogfood our own product); archives are sealed under operator-held keys and aged out under customer-set retention
- Cryptographic erasure via zeroize crate
- No PII stored server-side (zero knowledge architecture); downpipes is no-custody, so Maelstrom retains and deletes no customer data on the vendor side
Table of Contents
- UC-017 & UC-102: Retention Limitation & Data Retention Policies
- UC-103: Automated Data Deletion
- UC-104: Data Anonymisation and Pseudonymisation
- UC-105: Data Portability
- UC-128: Access Reviews and Recertification
- UC-129: Just-In-Time (JIT) Access
- Cross-Reference: Existing Policy
UC-017 & UC-102: Retention Limitation & Data Retention Policies
Policy Documentation
Location: src/content/trust/security/data-retention.md
Documented Retention Periods:
| Data Type | Retention Period | Justification | Storage Location | Control Method |
|---|---|---|---|---|
| Audit logs (including IP addresses) | 90 days; critical security event logs are retained for up to 365 days | Anti-abuse, diagnostics, security investigation | Cloudflare Workers Logs (shipped to Grafana Loki), Cloudflare KV | Automatic expiry / TTL-based deletion |
| Operational telemetry | 90 days | Performance monitoring | Cloudflare Workers Logs (shipped to Grafana Loki) | Automatic expiry (Loki-side retention) |
| Challenge state | 5 minutes | Active challenge lifetime | KV | Auto-expires |
| Nonce records | 5 minutes | Replay protection | KV | Auto-expires |
| Contracts | 7 years after expiration | Legal requirement (AU) | External systems | Manual retention |
| Financial records | 7 years | Legal requirement (AU) | External systems | Manual retention |
| ISMS documents | Current + 3 years | ISO 27001, audits | Git repository | Version control |
| Incident reports | 3 years | Lessons learned | ISMS documentation | Manual retention |
Reference: Lines 19-48 of src/content/trust/security/data-retention.md
What We DON’T Collect
Zero knowledge architecture means no server-side PII:
- Names, addresses, contact information
- Raw dates of birth
- Identity document data
- Biometric information
- Any other personally identifiable information (PII)
Quote from policy:
“Zero knowledge architecture means Maelstrom AI-operated services never collect or retain:
- Names, addresses, contact information
- Raw dates of birth
- Identity document data
- Biometric information
- Any other personally identifiable information (PII)”
Reference: Lines 49-64 of src/content/trust/security/data-retention.md
Technical Implementation: Retention Policy Code
File: provii-verifier/src/storage/retention.rs
/// Data retention policy configuration for GDPR compliance.
///
/// SECURITY: Different data types have different retention requirements:
/// - Challenges: Short-lived operational data (default 30 days)
/// - Audit logs: Must be retained for security investigations (90 days minimum)
/// - Billing data: Legal retention requirements vary by jurisdiction (7 years typical)
/// - Soft-deleted data: Grace period before hard delete (7 days default)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DataRetentionPolicy {
/// Retention period for challenge data (in seconds).
/// Default: 30 days (2,592,000 seconds)
pub challenge_retention_secs: u64,
/// Retention period for audit logs (in seconds).
/// Default: 90 days (7,776,000 seconds)
pub audit_log_retention_secs: u64,
/// Retention period for billing/verification events (in seconds).
/// Default: 7 years (220,752,000 seconds)
pub billing_retention_secs: u64,
/// Grace period for soft-deleted items before hard delete (in seconds).
/// Default: 7 days (604,800 seconds)
pub soft_delete_grace_period_secs: u64,
/// How often the cleanup job runs (in seconds).
/// Default: 1 hour (3,600 seconds)
pub cleanup_interval_secs: u64,
/// Maximum number of items to delete per cleanup run.
/// Default: 1000
pub max_deletions_per_run: usize,
}
impl Default for DataRetentionPolicy {
fn default() -> Self {
Self {
// GDPR Article 5(1)(e): 90 days for operational data
challenge_retention_secs: 90 * 24 * 60 * 60, // 90 days
// Security logs: 90 days for incident investigation
audit_log_retention_secs: 90 * 24 * 60 * 60, // 90 days
// Billing: 7 years for legal compliance (varies by jurisdiction)
billing_retention_secs: 7 * 365 * 24 * 60 * 60, // 7 years
// Soft delete grace period: 7 days for recovery
soft_delete_grace_period_secs: 7 * 24 * 60 * 60, // 7 days
// Cleanup runs every hour
cleanup_interval_secs: 60 * 60, // 1 hour
// Delete up to 1000 items per run
max_deletions_per_run: 1000,
}
}
}
Reference: Lines 18-73 of provii-verifier/src/storage/retention.rs
Deletion Reason Tracking
/// Deletion reason for GDPR Article 17 (Right to Erasure) compliance.
///
/// SECURITY: Tracks why data was deleted for audit purposes.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DeletionReason {
/// Automatic deletion due to retention policy expiration.
RetentionPolicyExpired,
/// Manual deletion by administrator.
AdminRequest { admin_id: String, reason: String },
/// User requested deletion (GDPR Article 17).
UserRequest { user_id: String },
/// Challenge expired naturally.
ChallengeExpired,
/// Data no longer needed for its original purpose.
PurposeFulfilled,
/// Legal hold or compliance requirement ended.
LegalHoldExpired,
}
Reference: Lines 152-174 of provii-verifier/src/storage/retention.rs
UC-103: Automated Data Deletion
1. Challenge TTL-Based Expiration
File: provii-verifier/src/routes/challenge.rs
/// Upper bound for a challenge's TTL in seconds.
const MAX_CHALLENGE_TTL: u64 = 300; // 5 minutes
/// Lower bound for a challenge's TTL in seconds.
const MIN_CHALLENGE_TTL: u64 = 30; // 30 seconds
// Challenge TTL calculation
let ttl_secs = body
.ttl_sec
.unwrap_or(300)
.min(policy.max_ttl_sec)
.min(MAX_CHALLENGE_TTL)
.max(MIN_CHALLENGE_TTL);
let expires_at = now + ttl_secs;
// Store in KV with automatic expiration
state.kv_store.set(
&kv_key,
cached_value,
Some(bucket.as_str())
)
.expiration_ttl(ttl_secs) // KV TTL auto-deletion
.await?;
// Also store with TTL matching challenge expiration for consistency
state.kv_store.set(
&metadata_key,
metadata,
Some(bucket.as_str()),
)
.expiration_ttl(Some(ttl_secs)) // KV auto-cleanup
.await?;
Evidence:
- Maximum challenge lifetime: 5 minutes
- Automatic KV deletion via
expirationTtl - No manual cleanup needed
Reference: Lines 28-31, 468-518, 581-588 of provii-verifier/src/routes/challenge.rs
2. Nonce TTL-Based Expiration (Replay Protection)
File: provii-verifier/src/routes/verify.rs
// Nonce lifetime: 5 minutes
let nonce_ttl = Duration::from_secs(300); // Time-to-live of 5 minutes.
match state.nonce_store.check_and_set(&nonce_tag, nonce_ttl).await {
// Nonce automatically deleted after 5 minutes
// ...
}
Tests:
#[test]
fn test_nonce_ttl_value() {
let ttl = Duration::from_secs(300);
assert_eq!(ttl.as_secs(), 300);
assert_eq!(ttl.as_millis(), 300000);
}
#[test]
fn test_nonce_ttl_5_minutes() {
let ttl = Duration::from_secs(300);
assert_eq!(ttl.as_secs(), 5 * 60);
}
Evidence:
- Nonce lifetime: 5 minutes
- Automatic deletion via KV TTL
- Test coverage confirming TTL values
Reference: Lines 108-110, 1811-1829 of provii-verifier/src/routes/verify.rs
3. Backup Retention (Self-Hosted downpipes)
Maelstrom’s entire Cloudflare estate is backed up by our own production instance of downpipes — we run downpipes as a customer of our own product. Backup archives are not raw exports left to accumulate: each archive is sealed with post-quantum hybrid encryption under operator-held keys and fanned out 3-2-1 to Maelstrom-controlled destinations, and archive retention is applied on a customer-set schedule by the in-tenant engine. Because downpipes is no-custody, this generates no second custodian and no server-side PII — the archives contain only Maelstrom’s own operational Cloudflare data, readable only with the operator-held keys.
For the downpipes product, retention and disposal of backup archives are determined by the customer, who owns both the destinations and the keys; Maelstrom holds no customer keys, data or Cloudflare tokens and so retains and deletes nothing on the customer’s behalf.
Reference: Cloudflare Backup Evidence — Maelstrom’s self-hosted downpipes backup and recovery record.
5. IP Address Retention (Cloudflare Workers Logs in Grafana Loki)
Policy Statement:
“Audit logs (including IP addresses): 90 days - Anti-abuse, diagnostics, security investigation - Cloudflare Workers Logs (shipped to Grafana Loki)”
Evidence:
- IP addresses collected only via
CF-Connecting-IPheader - Hashed before emission, then shipped via Cloudflare Workers Logs to Grafana Loki (90-day retention)
- Bulk-deleted at retention expiry; not individually deletable mid-window
- Used only for anti-abuse and diagnostics
Code References:
// Extracting client IP
fn get_client_ip(headers: &Headers) -> String {
headers
.get("CF-Connecting-IP")
.unwrap_or_else(|| headers.get("X-Forwarded-For")
.unwrap_or_else(|| headers.get("X-Real-IP")
.unwrap_or_else(|| "unknown".to_string())))
}
Reference: Lines 235-243 of provii-verifier/src/routes/challenge.rs (also at lines 185-193 of provii-verifier/src/worker_routes.rs)
Note: Hashed IP addresses shipped via Cloudflare Workers Logs to Grafana Loki are subject to platform retention (90 days). Loki tenant data is bulk-deleted at retention expiry rather than individually erasable mid-window.
UC-104: Data Anonymisation and Pseudonymisation
1. Random Verification IDs (Unlinkability)
Evidence: Every verification uses a random ID, preventing cross-site tracking
From existing policy:
“Unlinkability: Random IDs per verification prevent cross-site tracking”
Reference: Architecture documentation and trust model
2. No User Tracking
Zero knowledge Architecture:
- Date of birth transmitted once during issuance for server-side Pedersen commitment computation, then immediately discarded
- Server processes DOB ephemerally during issuance; DOB never stored, logged, or transmitted during verification
- Random challenge IDs (no user linkage)
- Nullifiers prevent replay without user identification
3. Pseudonymisation in Analytics
Evidence: Workers Logs (shipped to Grafana Loki) receive only:
- Timestamps
- Event types
- Anonymous metrics
- No user identifiers
UC-105: Data Portability
Status: Planned
Current Capability:
- KV exports available via Maelstrom’s self-hosted downpipes instance (operational data, for admin/restore use)
- Manual export possible for admin requests
Current State: Users hold credential data in their wallet app. No server-side personal data requires export. Operational exports exist for admin and restore use via the self-hosted downpipes deployment.
Reference: UC-105 marked as Implemented in unified control matrix (inherent portability via wallet)
UC-128: Access Reviews and Recertification
Evidence: ISMS Access Control Documentation
From existing ISMS:
- Quarterly access reviews documented
- Role-based access control
- Privileged access reviews
- Access revocation procedures
Status: Implemented
Reference: Mentioned as “Implemented - Quarterly access reviews in ISMS” in unified control matrix
UC-129: Just-In-Time (JIT) Access
Status: Planned
Planned Implementation:
- JIT access request workflow
- Time-limited privilege elevation
- Approval and audit logs
- Automatic privilege revocation
- Break-glass procedures for emergencies
Reference: UC-129 marked as Deferred in unified control matrix. Will implement when team grows beyond sole operator
Cross-Reference: Existing Policy
Data Retention & Disposal Policy
Location: src/content/trust/security/data-retention.md
Key Sections:
- Retention Principles (Lines 11-17)
- Minimisation
- Purpose Limitation
- Transparency
- Security
- Disposal
- Retention Periods (Lines 19-48)
- Operational Data
- Development Data
- Business Records
- What We DON’T Collect (Lines 49-64)
- Zero knowledge architecture benefits
- Disposal Procedures (Lines 65-108)
- Digital Data
- Cryptographic Material
- Physical Media
- Automated Deletion (Lines 109-131)
- Audit Logs including IP addresses (Cloudflare automatic + KV TTL)
- Challenges and Nonces (KV TTL)
- Legal Holds (Lines 133-142)
- Suspension procedures
- Data preservation
- Data Subject Requests (Lines 144-153)
- Access requests
- Deletion requests
- 30-day response time
Cryptographic Erasure
Zeroize Crate Implementation
Evidence: Sensitive data in memory is cryptographically erased using the zeroize crate
File: provii-verifier/src/routes/verify.rs
use zeroize::Zeroize;
// After using submit_secret, zeroize it from memory
cached.submit_secret.zeroize();
Usage Locations:
- Line 11: Import
- 13 zeroisation calls throughout the file (lines 339, 381, 398, 420, 451, 482, 514, 539, 565, 590, 634, 683, 781)
Additional Files:
provii-verifier/src/security/envelope_encryption.rs: Implements Zeroize trait for EncryptedSecretprovii-verifier/src/security/hash.rs: Zeroizing wrapper for API keys
Key Management Policy Reference:
“Zeroisation: Securely overwriting sensitive data in memory to prevent recovery (implemented via Rust
zeroizecrate).”
Reference: Line 1455 of provii-verifier/docs/security/KEY_MANAGEMENT_POLICY.md
Cryptographic Erasure (downpipes)
For downpipes, cryptographic erasure is the primary disposal control by design. Every archive is sealed with post-quantum hybrid encryption under customer-held keys; destroying the customer’s keys renders all archives permanently unreadable, with no vendor able to recover them. Maelstrom holds no customer keys, data or Cloudflare tokens, so there is nothing on the vendor side to erase. The vendor-side downpipes Update Signer is held offline and never deployed to any Worker.
Summary of Evidence
Compliance Matrix
| Control | Standard | Status | Evidence Location |
|---|---|---|---|
| UC-017: Retention Limitation | GDPR Art. 5(1)(e), ISO 27701 | Implemented | /security/data-retention.mdx |
| UC-018: Privacy Policy | UK Children’s Code, ISO 27701 | Implemented | maelstrom.au/trust |
| UC-102: Retention Policies | GDPR Art. 5(1)(e), ISO 27001 | Implemented | provii-verifier/src/storage/retention.rs |
| UC-103: Automated Deletion | GDPR Art. 17, ISO 27001:2022 A.8.10 | Implemented | Challenge TTL, Cleanup workers |
| UC-104: Anonymisation | GDPR Art. 25, Privacy by Design | Implemented | Random IDs, no user tracking |
| UC-105: Data Portability | GDPR Art. 20, CCPA | Implemented | Users hold credential data in wallet; nothing to export server-side |
| UC-128: Access Reviews | ISO 27001 A.5.18, SOC 2 | Implemented | ISMS quarterly reviews |
| UC-129: JIT Access | ISO 27001 A.8.2, CSA CCM | Deferred | Will implement when team grows beyond sole operator |
Key Architectural Facts
- Zero Server-Side PII: No personal data stored on servers (dates of birth processed ephemerally during issuance, immediately discarded; never transmitted during verification)
- Minimal IP Retention: 90 days for anti-abuse only
- Ephemeral Challenges: 5-minute maximum lifetime with automatic deletion
- Cryptographic Erasure: Zeroize crate is used to clear secrets from memory
- Automated Cleanup: Cron workers, TTL-based expiration; Maelstrom’s own Cloudflare estate is backed up by our self-hosted downpipes instance with customer-set archive retention
Gaps Identified
- UC-129 (JIT Access): Deferred. Will implement when team grows beyond sole operator
Recommendations
- Implement User Data Export API (UC-105)
- Priority: High
- Effort: Medium
- Timeline: Q2 2026
- Implement JIT Access System (UC-129)
- Priority: Medium
- Effort: High
- Timeline: Q2 2026
- Extend Audit Log Retention (UC-102)
- Current: 90 days (AUDIT_LOG_RETENTION_DAYS=90 in wrangler.toml)
- SOA Target: 12 months
- Priority: Medium
- Effort: Low (configuration change)
- Backup Verification Procedures (UC-102) — ✅ Complete
- Maelstrom’s entire Cloudflare estate is backed up by our own production instance of downpipes; recoverability is continuously proven by hourly canary integrity flights and scheduled in-account restore drills, with signed reports evidencing restore-test recency
- See Cloudflare Backup Evidence
Document Information
| Field | Value |
|---|---|
| Version | 1.1 |
| Created | 2026-02-14 |
| Last Updated | 2026-06-19 |
| Owner | Maelstrom AI |
| Classification | Public |
| Next Review | After Phase 2 completion |
Change History
- 1.1 (2026-06-19). downpipes added as second in-scope platform; the legacy backup worker retired in favour of self-hosted downpipes. Added a platform scope note (data lifecycle is cross-cutting; Provii stores no server-side PII, downpipes is no-custody so Maelstrom retains/deletes no customer data on the vendor side); reframed backup retention and verification onto Maelstrom’s own production downpipes instance; added downpipes cryptographic-erasure-by-key-destruction coverage. Australian/British spelling normalised (anonymisation, pseudonymisation, minimisation, zeroisation).
- 1.0 (2026-02-14). Initial data lifecycle controls evidence.