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.

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-MD5 for blobs uploaded in a single request or where it was set explicitly; large block blobs often have none. The content_md5 field 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 under existence scope. Use full if 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).
© 2026 ProChatFlow LLC Last updated present → absent → proven