Data Lifecycle Evidence

Evidence of data retention periods, automated deletion mechanisms, TTL configurations, and cryptographic erasure

Public

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


Executive Summary

This document provides evidence of data lifecycle controls across the Provii platform, 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 Anonymization and Pseudonymization
  • 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)
  • Backup procedures with scheduled exports (KV to R2)
  • Cryptographic erasure via zeroize crate
  • No PII stored server-side (zero knowledge architecture)

Table of Contents

  1. UC-017 & UC-102: Retention Limitation & Data Retention Policies
  2. UC-103: Automated Data Deletion
  3. UC-104: Data Anonymization and Pseudonymization
  4. UC-105: Data Portability
  5. UC-128: Access Reviews and Recertification
  6. UC-129: Just-In-Time (JIT) Access
  7. 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 TypeRetention PeriodJustificationStorage LocationControl Method
Audit logs (including IP addresses)90 days; critical security event logs are retained for up to 365 daysAnti-abuse, diagnostics, security investigationCloudflare Workers Logs (shipped to Grafana Loki), Cloudflare KVAutomatic expiry / TTL-based deletion
Operational telemetry90 daysPerformance monitoringCloudflare Workers Logs (shipped to Grafana Loki)Automatic expiry (Loki-side retention)
Challenge state5 minutesActive challenge lifetimeKVAuto-expires
Nonce records5 minutesReplay protectionKVAuto-expires
Contracts7 years after expirationLegal requirement (AU)External systemsManual retention
Financial records7 yearsLegal requirement (AU)External systemsManual retention
ISMS documentsCurrent + 3 yearsISO 27001, auditsGit repositoryVersion control
Incident reports3 yearsLessons learnedISMS documentationManual 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 Worker with Retention Cleanup

File: provii-backup/wrangler.toml

[vars]
# Backup retention configuration (days)
INCREMENTAL_RETENTION_DAYS = "7"
DAILY_RETENTION_DAYS = "30"
WEEKLY_RETENTION_DAYS = "90"

[triggers]
crons = [
  "0 * * * *",     # Every hour - Full KV backup
  "0 2 * * *",     # Daily at 2am UTC - Full KV snapshot
  "0 3 * * SUN"    # Weekly Sunday 3am UTC - Complete backup (KV + DO + R2)
]

Reference: Lines 15-223 of provii-backup/wrangler.toml

Implementation:

// Cleanup old backups (only on weekly backups to save CPU)
if (backupType === 'weekly') {
    console.log('[Worker] Running backup cleanup');
    await cleanupOldBackups(env);
}

Reference: Lines 33-36 of provii-backup/src/index.ts

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-IP header
  • 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 Anonymization and Pseudonymization

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 backup worker
  • Manual export possible for admin requests

Current State: Users hold credential data in their wallet app. No server-side personal data requires export. Backup exports exist for admin use.

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:

  1. Retention Principles (Lines 11-17)
  • Minimization
  • Purpose Limitation
  • Transparency
  • Security
  • Disposal
  1. Retention Periods (Lines 19-48)
  • Operational Data
  • Development Data
  • Business Records
  1. What We DON’T Collect (Lines 49-64)
  • Zero knowledge architecture benefits
  1. Disposal Procedures (Lines 65-108)
  • Digital Data
  • Cryptographic Material
  • Physical Media
  1. Automated Deletion (Lines 109-131)
  • Audit Logs including IP addresses (Cloudflare automatic + KV TTL)
  • Challenges and Nonces (KV TTL)
  1. Legal Holds (Lines 133-142)
  • Suspension procedures
  • Data preservation
  1. 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 EncryptedSecret
  • provii-verifier/src/security/hash.rs: Zeroizing wrapper for API keys

Key Management Policy Reference:

Zeroization: Securely overwriting sensitive data in memory to prevent recovery (implemented via Rust zeroize crate).”

Reference: Line 1455 of provii-verifier/docs/security/KEY_MANAGEMENT_POLICY.md


Summary of Evidence

Compliance Matrix

ControlStandardStatusEvidence Location
UC-017: Retention LimitationGDPR Art. 5(1)(e), ISO 27701Implemented/security/data-retention.mdx
UC-018: Privacy PolicyUK Children’s Code, ISO 27701Implementedmaelstrom.au/trust
UC-102: Retention PoliciesGDPR Art. 5(1)(e), ISO 27001Implementedprovii-verifier/src/storage/retention.rs
UC-103: Automated DeletionGDPR Art. 17, ISO 27001:2022 A.8.10ImplementedChallenge TTL, Cleanup workers
UC-104: AnonymizationGDPR Art. 25, Privacy by DesignImplementedRandom IDs, no user tracking
UC-105: Data PortabilityGDPR Art. 20, CCPAImplementedUsers hold credential data in wallet; nothing to export server-side
UC-128: Access ReviewsISO 27001 A.5.18, SOC 2ImplementedISMS quarterly reviews
UC-129: JIT AccessISO 27001 A.8.2, CSA CCMDeferredWill implement when team grows beyond sole operator

Key Architectural Facts

  1. Zero Server-Side PII: No personal data stored on servers (dates of birth processed ephemerally during issuance, immediately discarded; never transmitted during verification)
  2. Minimal IP Retention: 90 days for anti-abuse only
  3. Ephemeral Challenges: 5-minute maximum lifetime with automatic deletion
  4. Cryptographic Erasure: Zeroize crate is used to clear secrets from memory
  5. Automated Cleanup: Cron workers, TTL-based expiration, scheduled backups with retention enforcement

Gaps Identified

  1. UC-129 (JIT Access): Deferred. Will implement when team grows beyond sole operator

Recommendations

  1. Implement User Data Export API (UC-105)
  • Priority: High
  • Effort: Medium
  • Timeline: Q2 2026
  1. Implement JIT Access System (UC-129)
  • Priority: Medium
  • Effort: High
  • Timeline: Q2 2026
  1. 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)
  1. Document Backup Verification Procedures (UC-102)
  • Backup worker exists but restoration testing not documented
  • Priority: Medium
  • Timeline: Q1 2026

Document Information

FieldValue
Version1.0
Created2026-02-14
OwnerMaelstrom AI
ClassificationPublic
Next ReviewAfter Phase 2 completion