WORM Configuration Guide¶
Manual setup for Write Once Read Many (WORM) immutable storage policies on Azure Blob Storage (StorageV2) for SEC 17a-4 compliance.
WARNING: IRREVERSIBLE ACTION¶
WORM policies are PERMANENT once locked. This action CANNOT be undone.
- Locked WORM policies CANNOT be unlocked
- Data protected by locked WORM CANNOT be deleted until retention period expires
- Storage accounts with locked WORM containers CANNOT be deleted
- Test in a non-production storage account first
This is why WORM configuration is excluded from the provision.py automation script. Manual configuration with explicit confirmation prevents accidental production lockdown.
Overview¶
WORM (Write Once Read Many) policies enable immutable storage for Azure Blob Storage, meeting SEC 17a-4(f) requirements for non-erasable, non-rewritable storage of broker-dealer communications and records.
Cohasset Validation: Microsoft Azure Blob Storage immutable storage has been validated by Cohasset Associates for compliance with: - SEC Rule 17a-4(f) - FINRA Rule 4511(c) - CFTC Rule 1.31(c)-(d)
Prerequisites¶
Before configuring WORM policy:
- Storage account exists: Provisioned by
provision.py(StorageV2, hierarchical namespace disabled) - Export containers exist: Azure Diagnostic Settings auto-creates one blob container per enabled log category on first telemetry export (NOT created by
provision.py). Thetemplates/diagnostic-settings.jsontemplate in this solution enables four categories, producing four containers (see Which containers must be protected below) - Diagnostic Settings configured: Data is flowing to the containers
- Verify test environment: Confirm you are NOT in production (first time)
Which containers must be protected¶
Azure Monitor diagnostic-settings export to a storage account writes blobs under the container naming convention insights-logs-{log category name} (lowercased), one container per enabled log category. Immutability (WORM) policies in Azure Blob Storage are scoped per container, so a policy applied to one container does not protect the others.
The templates/diagnostic-settings.json template enables four Application Insights log categories. After telemetry begins flowing, expect these containers:
| Log category | Container | Holds |
|---|---|---|
AppEvents |
insights-logs-appevents |
Primary audit-of-record. Copilot Studio custom events (BotMessageSend, BotMessageReceived, GenerativeAnswers, topic/action events) that the KQL query library and compliance evidence depend on. |
AppTraces |
insights-logs-apptraces |
Trace/diagnostic messages |
AppRequests |
insights-logs-apprequests |
Request telemetry |
AppExceptions |
insights-logs-appexceptions |
Exception telemetry |
Required for SEC 17a-4(f) coverage: Apply the WORM time-based retention policy (with
allowProtectedAppendWritesenabled) to each container that holds audit-of-record data. At minimum,insights-logs-appeventsmust be protected, because it — notinsights-logs-apptraces— holds the Copilot Studio interaction events used as books-and-records evidence. Protecting onlyinsights-logs-apptracesleaves the primary audit container unprotected. Repeat Steps 2–8 below for each container, and run the verification script once per container.The portal steps and CLI examples below use
insights-logs-apptracesas a worked example. Substitute the container name for each container you protect, starting withinsights-logs-appevents.
Container naming convention reference: Azure Monitor — send diagnostic data to Azure Storage.
Step-by-Step Portal Instructions¶
Step 1: Navigate to Storage Account¶
- Open Azure Portal
- Navigate to Storage accounts
- Select your telemetry storage account (e.g.,
sagentobservability)
Step 2: Open Container Access Policy¶
- In the left menu, click Containers
- Click on the
insights-logs-apptracescontainer - In the container blade, click Access policy in the left menu
Step 3: Add Time-Based Retention Policy¶
- Under Immutable blob storage, click + Add policy
- Select Time-based retention policy
- Configure policy parameters:
- Policy state: Unlocked (initially)
- Retention period: Enter retention in days
- Allow protected append writes: ENABLED (REQUIRED for diagnostic export pipelines)
CRITICAL: Azure Monitor diagnostic settings export telemetry by appending to existing blobs. A locked time-based policy without
allowProtectedAppendWriteswill block these appends and silently halt telemetry export — the storage account remains immutable but no longer receives data, creating an audit gap. Always enable protected append writes for any container that receives Azure Monitor diagnostic export.
Retention Period Reference: | Regulatory Requirement | Minimum Days | Recommended | |------------------------|--------------|-------------| | SEC 17a-4(b)(4) - Communications | 1095 (3 years) | 1095 | | SEC 17a-4(a) - Financial records | 2190 (6 years) | 2555 (7 years) | | FINRA 4511 - Books and records | 2190 (6 years) | 2555 (7 years) |
- Click OK to save the policy
Note: The policy is now in Unlocked state. Data is protected by the retention period, but the policy can still be modified or deleted.
Step 4: Review Policy State¶
After saving, verify the policy shows: - Policy state: Unlocked - Retention period: Your configured days - Immutability scope: Container
Step 5: Test in Unlocked State¶
Before locking the policy, verify it works as expected:
-
Upload a test blob:
-
Attempt to delete the test blob (should SUCCEED in Unlocked state):
-
Expected result: Delete succeeds (Unlocked allows deletion)
Step 6: Verify Compliance Requirements Before Locking¶
Before locking, confirm:
- Storage account is the correct production account
- Retention period matches regulatory requirements
- You understand this action is IRREVERSIBLE
- Compliance/Legal team has approved the configuration
- You have documented this configuration decision
Step 7: Lock the Policy (IRREVERSIBLE)¶
THIS ACTION CANNOT BE UNDONE
- Return to Container > Access policy
- Click on your time-based retention policy
- Click Lock policy
- Read the confirmation warning carefully
- Type the confirmation text if required
- Click OK to lock
The policy state will change to Locked.
Step 8: Test Locked Policy¶
Verify immutability is enforced:
-
Upload a new test blob:
-
Attempt to delete the test blob (should FAIL):
-
Expected result: Delete fails with error:
Verification¶
Run the verification script to confirm WORM compliance status. The script verifies one container per run, so run it once for each protected container, starting with the primary audit-of-record container insights-logs-appevents:
# Primary audit-of-record container (Copilot Studio interaction events)
python scripts/verify_worm.py --storage-account <storage-account> --container-name insights-logs-appevents
# Repeat for each remaining protected container
python scripts/verify_worm.py --storage-account <storage-account> --container-name insights-logs-apptraces
python scripts/verify_worm.py --storage-account <storage-account> --container-name insights-logs-apprequests
python scripts/verify_worm.py --storage-account <storage-account> --container-name insights-logs-appexceptions
A run exits non-zero if the named container's policy is missing, unlocked, has insufficient retention, or has protected append writes disabled. Treat the WORM posture as adequate only when every audit-of-record container passes.
The script performs read-only verification without modifying any policies.
Compliance States¶
| State | SEC 17a-4 Compliant | Description |
|---|---|---|
| No policy | No | No immutability protection; data can be deleted |
| Unlocked | No | Policy exists but can be modified or deleted |
| Locked, retention < 6y | No | Policy is permanent but retention does not meet SEC 17a-4(a) (recommended ≥ 2555 days) |
| Locked, append writes disabled | Partial | Existing data is immutable but new diagnostic-export appends will be blocked, creating an audit gap |
| Locked, retention ≥ 6y, append writes enabled | Yes | Data is immutable for retention period and Azure Monitor export continues to land |
The shipped verify_worm.py checks all four conditions (state, retention, protected append writes, container present) and exits with code 2 if any of them fails.
Policy Management After Locking¶
Extending Retention Period¶
You CAN extend the retention period on a locked policy:
- Navigate to Container > Access policy
- Click on the locked policy
- Increase the retention period value
- Click OK
Note: You can only EXTEND retention, never reduce it.
Legal Hold¶
In addition to time-based retention, you can apply legal hold for litigation or investigation:
- Navigate to Container > Access policy
- Under Legal hold, click + Add
- Add a legal hold tag (e.g., "Investigation-2026-001")
- Click OK
Legal holds: - Can be added/removed without affecting WORM policy - Prevent deletion regardless of retention period - Require explicit removal when legal matter concludes
Troubleshooting¶
"Cannot delete storage account"¶
Cause: WORM-locked containers prevent storage account deletion.
Resolution: - Wait for all retention periods to expire - OR contact Azure Support for exceptional circumstances - Prevention: Use separate storage accounts for test and production
"Policy shows Unlocked"¶
Cause: Policy was created but not explicitly locked.
Resolution: Follow Step 7 to lock the policy. Unlocked policies do NOT meet SEC 17a-4 requirements.
"Delete operation succeeded on locked container"¶
Possible Causes: - Blob was uploaded BEFORE policy was locked - Blob retention period has expired - Legal hold was removed
Verification: Check blob immutability status in portal or via Azure CLI.
"Cannot modify retention period"¶
Cause: Attempting to reduce retention on locked policy.
Resolution: Retention can only be extended on locked policies. Plan retention periods carefully before locking.
Cost Implications¶
Locked WORM storage has cost implications:
| Consideration | Impact |
|---|---|
| Storage duration | Cannot delete data before retention expires; costs accumulate |
| Storage account deletion | Cannot delete account; must wait for all containers to expire |
| Testing mistakes | Accidentally locked test data remains until expiration |
Mitigation: - Use short retention periods for testing (e.g., 1 day) - Maintain separate test and production storage accounts - Document all WORM configurations with expiration dates
Related Resources¶
- verify_worm.py - Verification script (read-only)
- Azure Immutable Storage Overview
- SEC 17a-4 Compliance Assessment
- Cohasset Compliance Assessment
WORM Configuration Guide version: 1.2.0 Last updated: February 2026