Audit Logging in TEE
TEE audit logging combines two capabilities that are impossible in standard deployments: cryptographic proof that a log was produced by a specific code version (attestation signing), and an immutable append-only log chain that detects any gap or modification. Together these create audit records suitable for regulatory compliance, legal evidence, and security investigations.
TEE Audit Log Architecture
Append-Only Chain
Each audit log entry includes a hash of the previous entry — forming a hash chain. Deleting or modifying any entry breaks the chain, which is detectable during audit verification. Sequence numbers detect inserted or missing entries.
Attestation Signing
Each batch of audit records is signed by the TEE-bound private key (see Attestation-Based Log Integrity). The signature proves the record came from attested code, not from a modified or impersonating process.
Immutable Egress
Audit records are pushed to an S3-compatible store with Object Lock (WORM) enabled. Once written, records cannot be modified or deleted — even by infrastructure administrators — until the retention period expires.
Dual-Copy Strategy
Audit records are kept both inside the TEE (in-TEE Loki, for investigation) and outside the TEE (WORM S3, for compliance). The external copy is sanitized; the internal copy has full detail accessible via attested channel.
Audit Record Schema
// TEE Audit Record — emitted for every significant workflow event
// This record is part of the attestation-signed log chain
{
// Chain integrity fields
"seq": 1042, // Monotonically increasing within TEE lifetime
"prev_hash": "sha256:a1b2c3...f9", // SHA256 of previous record (hash chain)
"record_hash": "sha256:b2c3d4...fa", // SHA256 of this record (before signing)
// TEE identity
"tee_measurement": "sha384:c3d4e5...fb", // PCR[2]: code version that produced this record
"tee_instance_id": "nitro-001-us-east", // TEE instance identifier
// Event
"timestamp_utc": "2026-05-25T10:30:00.000Z",
"event_type": "workflow.completed", // Enum: workflow.started, node.completed, hil.approved, etc.
"execution_id": "exec-abc-123", // GUID — no payload encoded
"workflow_type": "credit-assessment", // Enum — not user-supplied string
"tenant_hash": "sha256:tenant-hash", // Hash of tenant ID
// Operational context (no payload data)
"duration_ms": 1423,
"node_count": 7,
"outcome": "completed", // Enum: completed, failed, timeout, cancelled
// Signature (covers all above fields)
"signature": "base64:MEQCIBx...",
"signing_cert_thumbprint": "sha256:d4e5f6..."
}
Audit Event Types
| Event Type | When Emitted | Key Fields |
|---|---|---|
| workflow.started | Workflow execution begins | executionId, workflowType, tenantHash, triggeredBy (system/api/schedule) |
| workflow.completed | Workflow completes successfully | executionId, durationMs, nodeCount, outcome |
| workflow.failed | Workflow terminates with failure | executionId, errorCode, failedNodeType, durationMs |
| node.started | Individual node begins execution | executionId, nodeType, nodeIndex, tenantHash |
| node.completed | Individual node completes | executionId, nodeType, nodeIndex, durationMs, outcome |
| hil.requested | Human-in-loop approval requested | executionId, approvalType, tenantHash — NOT the approval context data |
| hil.approved | HIL decision recorded | executionId, approverRoleHash (hashed role, not name), decision (approved/rejected) |
| tee.started | TEE instance initialized and attested | teeInstanceId, teeMeasurement, attestationCertThumbprint |
| tee.policy.changed | TEE egress or attestation policy updated | policyVersion, changeType, authorizedBy (role hash) |
| audit.chain.verified | Audit chain integrity check performed | checkedFrom, checkedTo, result (intact/gap-detected) |
WORM S3 Storage Configuration
# S3 bucket for TEE audit records — WORM (Write Once, Read Many)
# Object Lock prevents modification or deletion during retention period
aws s3api create-bucket \
--bucket bizfirstgo-tee-audit-logs \
--object-lock-enabled-for-bucket
aws s3api put-object-lock-configuration \
--bucket bizfirstgo-tee-audit-logs \
--object-lock-configuration '{
"ObjectLockEnabled": "Enabled",
"Rule": {
"DefaultRetention": {
"Mode": "COMPLIANCE", # COMPLIANCE = even bucket owner cannot delete
"Years": 7 # 7-year retention for SOX/financial compliance
}
}
}'
# Audit records written by the external OTel Collector → S3 exporter:
exporters:
awss3:
s3_uploader:
region: us-east-1
s3_bucket: bizfirstgo-tee-audit-logs
s3_prefix: "tee-audit/"
s3_partition: minute # One object per minute; efficient for range queries
marshaler: otlp_json
# When writing audit records:
# - Object key: tee-audit/2026/05/25/10/30/seq-1042.json
# - Object lock: inherited from bucket default (7-year COMPLIANCE)
# - Cannot be deleted or modified until 2033-05-25
Audit Chain Verification Query
# LogQL: Find audit records for a specific execution
{job="tee-audit"} | json | execution_id = "exec-abc-123"
| line_format "{{.seq}} {{.event_type}} {{.timestamp_utc}} {{.outcome}}"
# LogQL: Detect sequence gaps (potential log deletion)
{job="tee-audit", tee_instance_id="nitro-001-us-east"}
| json
| seq > 1000 and seq < 2000
# Compare seq values in Grafana visualization — gaps indicate tampering
# Programmatic audit chain verification:
# 1. Fetch all records sorted by seq from S3
# 2. For each record: SHA256(record_json) == record.record_hash
# 3. For each record: SHA256(previous_record_json) == record.prev_hash
# 4. Signature verification: tee_public_key.Verify(record_hash, record.signature)
# 5. If any check fails: alert + retain record as evidence of tampering
The TEE audit log is not the same as the operational Loki log stream. Operational logs (executionId, timing, error codes) serve the operations team for debugging and alerting. The audit log (event type, chain hash, attestation signature) serves compliance, legal, and security teams. Use separate Loki streams, separate OTel Collector pipelines, and separate storage buckets — mixing them creates retention and access control conflicts.