Azure Blob Storage Integration Guide
Connection Configuration
BurnLedger connects to Azure Blob Storage using a connection string or SAS token with read-only permissions.
Configuration fields:
| Field | Required | Description |
|---|---|---|
container |
Yes | The blob container name. |
connection_string |
Yes* | Azure Storage connection string. |
account_name |
Yes* | Storage account name. Only used together with sas_token; the blob host is built as https://<account_name>.blob.core.windows.net/. |
sas_token |
Yes* | SAS token with read-only permissions. Only used together with account_name. |
read_only |
Yes — must be true |
Asserts the credential cannot write. Azure exposes no data-plane permission introspection, so construction is refused without it. See Read-Only Access Setup. |
Provide either connection_string or* both account_name and sas_token. Supplying account_name without sas_token (or the reverse) is treated as supplying neither, and the connection is refused.
These are the exact JSON keys the connector reads. They are snake_case in both SDKs — do not write accountName, sasToken, connectionString, or readOnly; the connection object is sent to the API verbatim and unknown keys are ignored.
TypeScript
const system = await bl.registerSystem({
name: "user-files-azure",
connectorType: "azure_blob",
connectionConfig: {
container: "user-uploads",
account_name: "myappstorage",
sas_token: process.env.AZURE_SAS_TOKEN,
read_only: true,
},
subjectQuery: "users/{identifier}/",
hashScope: "existence",
});
Python
system = bl.register_system(
name="user-files-azure",
connector_type="azure_blob",
connection_config={
"container": "user-uploads",
"account_name": "myappstorage",
"sas_token": os.environ["AZURE_SAS_TOKEN"],
"read_only": True,
},
subject_query="users/{identifier}/",
hash_scope="existence",
)
Blob Prefix Pattern
Same syntax as S3 and GCS. Use {identifier} as the subject placeholder. The
expanded string is used verbatim as the blob-name prefix — there are no
wildcards, and * is passed through as a literal character that will match
nothing. A template with no {identifier} is rejected with
INVALID_QUERY_TEMPLATE rather than silently matching zero blobs.
users/{identifier}/
uploads/{identifier}-
{identifier}/documents/
Read-Only Access Setup
Azure Blob Storage has no reliable data-plane permission simulation: BurnLedger cannot ask Azure "would this credential be allowed to write?" without attempting a write, which it never does. Because of this, the "read_only": true assertion in the connector config is the primary verification mechanism for Azure — you must set it to confirm the credential is read-only (BurnLedger only ever reads regardless). Without "read_only": true, construction is refused (fails closed), even if the SAS token or role you supplied is in fact read-only. Scope the credential read-only as shown below and set the assertion.
A corollary worth stating plainly: because nothing is inspected or probed, the Azure Blob connector can never raise WRITE_ACCESS_DETECTED. A write-capable credential paired with read_only: true is accepted silently. The verification steps below are the only thing standing between a mis-scoped SAS token and a verification record that rests on it — run them.
SAS Token (recommended)
Generate a SAS token scoped to the container with read and list permissions only:
az storage container generate-sas \
--account-name myappstorage \
--name user-uploads \
--permissions rl \
--expiry 2027-01-01 \
--output tsv
Permissions: r (read) + l (list). No w (write), d (delete), or c (create).
Pass the token without a leading ? — exactly as --output tsv prints it. The connector builds the container URL as https://<account_name>.blob.core.windows.net/<container>?<sas_token>, so a token that already starts with ? produces a malformed URL and the connection is refused. The token's own %-encoding is already correct as generated; do not re-encode or unescape it.
RBAC (Azure AD)
Assign the Storage Blob Data Reader built-in role:
az role assignment create \
--assignee burnledger-app-id \
--role "Storage Blob Data Reader" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/myappstorage/blobServices/default/containers/user-uploads
Do not assign Storage Blob Data Contributor or Storage Blob Data Owner.
Verify
# Should succeed
az storage blob list --container-name user-uploads --account-name myappstorage --prefix "users/test/" --sas-token "$SAS"
# Should fail
az storage blob upload --container-name user-uploads --account-name myappstorage --name test-write --file /dev/null --sas-token "$SAS"
Retained copies and soft delete
A deleted object that is still recoverable has not been deleted, so BurnLedger will not certify zero over it. Retention state is probed at construction:
- Retention state cannot be determined (the credential lacks permission to read it, or the API call fails) → construction is refused. It is not assumed to be "not retained": assuming the safe-looking answer is exactly how a verification record gets signed over data that is still recoverable.
- Recoverable copies exist for the subject → attestation fails rather than reporting zero. Purge them, then re-run.
Soft delete is ON BY DEFAULT on this store, so a bucket or container nobody has explicitly configured will hit this. That is the control working — the data really is still recoverable — but it is a change in behaviour: previously only live objects were counted, and a subject with soft-deleted blobs would have been certified as having none.
Hash Scope Options
| Scope | Behavior |
|---|---|
existence (default) |
Hashes five canonical fields — container, content_length, content_md5 (hex-encoded), key (blob name), and last_modified (UTC, microsecond precision). Does not download content. |
full |
Downloads and hashes complete blob content, bounded by the system's max_bytes. |
Azure only populates
Content-MD5for blobs uploaded in a single request or where it was set explicitly; large block blobs often have none. Thecontent_md5field is then the empty string and the hash rests on the remaining four fields — meaning a same-size, same-timestamp content swap would not be detected underexistencescope. Usefullif that matters.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
CONNECTION_FAILED |
Invalid connection string or expired SAS token | Regenerate the SAS token or check the connection string. |
CONNECTION_FAILED with "cannot verify the credential is read-only" |
read_only is missing or false |
Azure offers no data-plane privilege introspection, so the connector fails closed. Verify the credential really is read-only with the steps above, then set "read_only": true. |
CONNECTION_FAILED with "requires either connection_string or (account_name + sas_token)" |
Only one half of the SAS pair was supplied | Provide both account_name and sas_token, or a connection_string instead. |
WRITE_ACCESS_DETECTED |
Not produced by the Azure Blob connector. It has no way to detect write access (no data-plane permission introspection), so it never reaches this verdict — the read_only assertion above is the only read-only gate |
If you see this code, it came from another system in the same attestation, not from Azure Blob. |
INVALID_QUERY_TEMPLATE |
Prefix pattern contains no {identifier} |
Add the {identifier} placeholder. A template without it would match nothing and certify an empty result, so it is refused. |
CONNECTION_FAILED with "container not found" |
Wrong container name | Verify the container name (case-sensitive). |