Exact permissions granted
This page is exhaustive by design: everything sprig’s installer touches, so your security review has one canonical source. The installer also has a preview mode (--what-if) that prints its plan without changing anything.
Run-time identities: control plane, gateway, and serving
Section titled “Run-time identities: control plane, gateway, and serving”Three user-assigned managed identities keep the runtimes apart. The platform API attaches only
the control-plane identity, and the gateway attaches only the gateway identity. The shared data
service attaches the serving identity, and so does the Fabric schema reader
(ca-<prefix>-fabric-connector) that an install adds when Fabric sources are enabled. No runtime
attaches two identities.
Azure attaches roles to identities rather than to workloads, so any identity shared by two containers grants each of them the union of both sets. That is why the gateway has its own: the key that unwraps its sign-in cookie ring must not be reachable from the shared data service, which has no use for it.
That principle cuts the other way for the one identity that is shared, so it is worth stating
plainly. The Fabric schema reader runs as id-<prefix>-serving, so it holds every grant in the
serving rows below, including the versioned-configuration blob read and the on-behalf-of client
secret. It uses neither. Two things bound that: both grants are read-only and scoped to a single
container and a single named secret, and enabling Fabric adds no new role assignment anywhere,
so the serving identity’s role set is identical whether or not Fabric is enabled. If you would
rather no Fabric workload be able to reach those two, leave Fabric sources unconfigured and the
container is never deployed.
The serving identity keeps its name deliberately. If you have run
CREATE USER [id-<prefix>-serving] FROM EXTERNAL PROVIDER against a source database, that grant
is unaffected by this split and needs no change. A Fabric Warehouse or lakehouse source is granted
the same way, to the same identity. That database-side grant is the whole of sprig’s access to
Fabric data: no Azure role assignment in your subscription grants any access to it, and you can
revoke it at the source without touching the installation.
| Identity | Grant | Scope | Purpose |
|---|---|---|---|
| Control plane | Storage Table Data Contributor |
The platform storage account | Write metadata and audit events |
| Control plane | Storage Blob Delegator |
The platform storage account | Create a short-lived user-delegation key for the exact staging-archive upload grant; this role does not grant blob data access by itself |
| Control plane | Storage Blob Data Owner |
Only the existing bundles container |
Verify and extract archives, tag generated files for lifecycle safety (blobs/tags data actions exist in no lesser built-in role), and delete abandoned publish data; it cannot write another container |
| Control plane | Storage Blob Data Contributor |
Only the existing dab-config container |
Publish immutable versioned data-service configuration and read it back for version discovery; no tag actions needed here |
| Control plane | Storage Blob Delegator |
The source-retention storage account | Mint short-lived, read-only user-delegation grants for owner source read-back |
| Control plane | <prefix>-source-writer-<subscription-hash> (Sprig custom role) |
The source-retention storage account | Create per-app containers and write, read, and tag source archives. The stable subscription suffix prevents tenant-wide name collisions. The role carries no blobs/delete data action, so application code structurally cannot destroy retained source |
| Control plane | <prefix> DAB Revision Restarter (Sprig custom role) |
Only the shared data service | Read and restart the existing data-service revision after selecting an immutable configuration bundle; cannot change its image, identity, environment, secrets, or traffic |
| Control plane | SQL DB Contributor |
The platform-owned Azure SQL logical server | Create/manage per-app databases; no server-level or security-admin rights |
| Control plane | Application.ReadWrite.OwnedBy (Graph app role) |
Tenant, restricted to applications the identity itself created | Historically per-app Entra registrations; superseded by per-app Entra groups (below), kept for compatibility |
| Gateway | Serving.Read (platform API app role) |
The sprig-platform-api enterprise application |
Read the small serving descriptor the gateway resolves each app from; application-only and no client secret |
| Gateway | Storage Table Data Contributor |
Only the existing DataAudit table |
Append the gateway’s signed-in data-access audit events without write access to app metadata or active-version pointers |
| Gateway | Storage Blob Data Reader |
The platform storage account | Read published app bundles; the admin console is inside the signed gateway image |
| Gateway | Storage Blob Data Contributor |
Only the existing dataprotection container |
Read and write the key ring that encrypts the sign-in cookie. The ring is rewritten as keys roll, so read alone will not do. The ring is stored wrapped, so this grant alone yields ciphertext |
| Gateway | Key Vault Crypto User |
Only the single key sprig-gateway-dataprotection |
Wrap and unwrap that key ring. Wrap/unwrap only: it cannot create, rotate, delete, or read the key material, and it cannot reach any other key in the vault |
| Serving | Storage Blob Data Reader |
The platform storage account | Read versioned data-service configuration; cannot write blobs. This is the shared data service’s only storage grant and it holds no write grant anywhere |
| All three identities | Container Registry Repository Reader |
The dedicated release ACR, with an ABAC condition restricted to repository names beginning sprig/ |
Pull only sprig/* release images; cannot list the registry catalog. This is the one serving-identity grant the Fabric schema reader does use, to pull its own image |
| Control plane | Key Vault Secrets User |
Only the <prefix>-platform-api-client-secret secret |
Resolve the platform API’s OBO client credential for source schema discovery; cannot read the serving secret |
| Control plane, when certificate binding is enabled | Key Vault Secrets User |
Only the configured wildcard certificate secret | Let Container Apps import the certificate without granting access to other secrets |
| Serving | Key Vault Secrets User |
Only the <prefix>-obo-client-secret secret in that vault |
Resolve DAB’s on-behalf-of client credential; cannot read the platform API secret or the wildcard certificate |
Whose identity reads your data
Section titled “Whose identity reads your data”Standing access differs by source kind, so the inventory has to say which is which rather than give one answer.
| Source kind | Who the database authenticates for a viewer’s query | Who reads its schema at registration | Standing sprig access to the data |
|---|---|---|---|
azure-sql (db-delegated, the default) |
The signed-in viewer, by on-behalf-of token exchange | The signed-in Ops user, by the same exchange | None. sprig holds no credential that reads this source by itself |
fabric-dw (gateway-enforced, the only mode this kind supports) |
The id-<prefix>-serving identity, with sprig filtering per viewer above the database |
The id-<prefix>-serving identity, not the Ops user |
Yes, and it is the point: whatever you granted that identity at the source, for as long as you grant it |
fabric-semantic (db-delegated, the only mode this kind supports) |
The signed-in viewer, always - a service identity would bypass the model’s row-level security, so none exists for this kind | The signed-in Ops user, over Power BI on-behalf-of; the model’s own permissions decide what the catalog shows | None. sprig holds no credential that reads a semantic model by itself |
The fabric-dw row is the recorded exception, not a hidden one. Every registration in
gateway-enforced mode is written to the audit log, the console shows the mode on the source, and
approving a share for an app backed by such a source is therefore also a data-access decision.
See Data access for what that means for viewers and approvers.
Source registration and lifecycle operations store server and database metadata only. The
source-management API receives no Key Vault role. For an installer-created Acmebot vault, the
signed-in installer receives vault-scoped Key Vault Secrets Officer so it can create and rotate
the OBO credentials, and vault-scoped Key Vault Crypto Officer so it can create the key that
wraps the gateway’s cookie key ring. Both are held by the person or service principal running the
install, never by a runtime identity: the gateway gets wrap/unwrap on one key and no key
management at all. On a bring-your-own vault, the operator supplies equivalent access. Runtime
role assignments have no separate charge and reuse the existing vault.
When Acmebot is deployed, its own managed identity receives DNS Zone Contributor on only the
apps.<domain> Azure DNS zone so it can create DNS-01 challenge records.
No subscription-scope RBAC. No Application.ReadWrite.All. No directory read of other applications. No Contributor on builder or app resources: publishing an app is a database write (a Blob version + a pointer flip), not an Azure resource operation.
Archive publishing reuses the existing storage account and bundles container. The separate
gateway and serving identities are required credential-isolation boundaries; managed identities
and Azure RBAC assignments have no separate fixed charge. The dataprotection container reuses
the existing storage account and holds a handful of small key-ring blobs, billed as ordinary
capacity and Blob operations. The wrapping key is one RSA key in the vault already provisioned,
billed per key operation, and it is used only when a gateway replica starts or the ring rolls,
so at pilot scale both are a rounding error. Publishing continues to incur only the storage
capacity and Blob operation charges described under Costs.
Entra objects created at install
Section titled “Entra objects created at install”| Object | Kind | Notes |
|---|---|---|
sprig-platform-api |
App registration (confidential client) | Token audience for the control-plane/publish API; exposes one delegated scope (access_as_user), the user-only Ops role, and the application-only Serving.Read role assigned to the serving managed identity. Holds a delegated Azure SQL user_impersonation permission - plus Power BI Dataset.Read.All only when the Fabric inputs were supplied at install time (declared and admin-consented then; removed and revoked on a reinstall without them) - and its own client secret (Key Vault only) so it can read a source’s schema as the signed-in Ops user during register_source (on-behalf-of - the SQL hop for SQL-shaped kinds, the Power BI hop for semantic models) |
sprig-gateway |
App registration | The audience for every builder/viewer token; Application ID URI is the gateway’s MCP URL; defines the builder/viewer app roles |
sprig-connector |
App registration (confidential client) | Pasted into the Claude org admin console; its only delegated permission is the gateway’s own scope |
sprig-cli |
App registration (public client) | Claude Code’s native OAuth client (loopback redirect); pre-authorized for the gateway scope so builders see no extra consent prompt |
sprig-obo |
App registration (confidential client) | DAB’s on-behalf-of token exchange toward Azure SQL; its secret lives in Key Vault only |
sprig-operators (security group) |
Entra group | Receives the platform API’s Ops role; direct membership controls who can operate the platform |
sprig-users (security group) |
Entra group | Holds the gateway’s builder/viewer app roles; membership is how you decide who can use sprig at all |
| Per published app: two Entra groups | app-<name>-viewers, app-<name>-builders |
Checked by the gateway on every request. No per-app Entra app registration: that’s the pre-gateway shape (below) |
Delegated permissions
Section titled “Delegated permissions”These permissions act only on behalf of the signed-in person. They do not give an application unattended access as itself.
| Client | Delegated permission | Purpose |
|---|---|---|
sprig-gateway |
Microsoft Graph User.Read |
Read the signed-in person’s basic profile and check that person’s per-app group memberships |
sprig-gateway |
Azure SQL Database user_impersonation |
Exchange the person’s token to query an authorized data source as that person |
sprig-gateway |
sprig-platform-api access_as_user |
Call the platform control API as the signed-in person |
sprig-platform-api |
Azure SQL Database user_impersonation |
Inspect source schema as the signed-in Ops user through the second OBO hop; tenant-wide admin consent is required |
sprig-platform-api (only when Fabric inputs are supplied at install time) |
Power BI Service Dataset.Read.All (delegated) |
Read a semantic model’s catalog as the signed-in Ops user; tenant-wide admin consent is granted at install and revoked on a reinstall without the Fabric inputs. Delegated-only: no user, no token |
sprig-obo |
Azure SQL Database user_impersonation |
Complete the shared data layer’s on-behalf-of exchange to Azure SQL as the signed-in person |
sprig-connector and sprig-cli |
sprig-gateway gateway scope |
Call the MCP gateway as the signed-in person; both clients are pre-authorized for this scope |
The installer grants the gateway-to-platform API consent required by the admin host. It also attempts tenant-wide admin consent for the gateway’s declared Graph and Azure SQL permissions. If the installing identity cannot grant that consent, the installer prints the exact command for a Global Administrator or Privileged Role Administrator to run.
Azure resources created at install
Section titled “Azure resources created at install”| Resource | Where | Purpose |
|---|---|---|
Resource group rg-sprig-platform |
Subscription | Platform footprint |
VNet vnet-<prefix>-platform and dedicated Container Apps subnet |
Platform RG | Foundation network boundary for the shared runtime and future source-VNet peering |
Storage account (+ tables, + a bundles Blob container) |
Platform RG | Metadata, audit, versioned app bundles |
| Source-retention storage account (one Blob container per app) | Platform RG | Published source archives, kept apart from the platform account so source-writing rights reach nothing else |
| Managed application runtime | Platform RG | Runs the signed release, including the Entra-protected operator endpoint and published app access |
| Dedicated Azure Container Registry (Basic) | Platform RG | Stores signed release images copied into the subscription; its public endpoint accepts authenticated imports, while admin and anonymous access stay disabled |
| Azure SQL logical server (platform-owned) | Platform RG | Hosts per-app serverless databases; the platform identity is its Entra admin |
Azure SQL database sprig-platform (Basic) |
Shared logical server | Stores platform metadata; startup migrations complete before the platform becomes ready |
| Key Vault | Platform RG (Acmebot-created by default, or your own with -k) |
The OBO client secrets (sprig-obo for DAB, sprig-platform-api for schema discovery) and the *.apps.<domain> wildcard certificate |
Azure DNS zone apps.<domain> (only with -d) |
Platform RG | One delegated zone; every app is a subdomain of it |
| Three user-assigned managed identities | Platform RG | Separate control-plane writer, gateway, and bundle-read-only serving identities |
| Fabric schema reader (only when Fabric sources are enabled) | Platform RG | Reads Fabric source schemas during discovery; never reachable from the internet. Not created at all unless you supply the Fabric installer inputs |
| App Insights + Log Analytics | Platform RG | Monitoring |
| Subscription budget | Subscription | Spend alerts |
Per builder, at onboarding
Section titled “Per builder, at onboarding”onboard_builder and sprig builders onboard <upn>
all add the person to the sprig-users group. They create no per-builder resource group,
Azure role assignment, or sandbox. The group is the authorization boundary. See
Onboarding builders.
What the person running the installer needs
Section titled “What the person running the installer needs”Once, at install time: Owner on the target subscription and Entra rights to create app registrations and grant admin consent. The installer runs under that person’s az login; it stores no credentials.
The Azure CLI session must be an interactive user. For release import verification and image
cleanup, the installer creates or reclaims one temporary Container Registry Repository Contributor assignment at the dedicated registry, conditioned to repository names beginning
sprig/. It uses that assignment only for repository metadata and cleanup, audits the effective
boundary, and revokes the Sprig-created assignment on every exit. A separately pre-existing exact
conditional assignment is never deleted. No catalog-listing role or registry credential is
granted.
What is deliberately absent
Section titled “What is deliberately absent”- No secrets on builder machines. Builders authenticate through the Claude connector (org-managed OAuth) or Claude Code’s native OAuth. Organization skill archives contain only workflow instructions and helper files: no npm bootstrap, credentials, or device-code authentication.
- No platform-held credential to an
azure-sqlsource. Every read of one runs through the gateway’s data proxy as the real, signed-in caller (on-behalf-of); the platform’s own managed identity never queries that data directly. This does not extend tofabric-dw, which is gateway-enforced and is read by the serving identity; see Whose identity reads your data above. - No outbound calls from the platform to any vendor endpoint. sprig has no telemetry service.
- No source-registry credential in the running platform. Temporary credentials are used by the operator only while importing a release into the dedicated release registry.
- No credential for the dedicated release registry. Each runtime authenticates with its
attached managed identity, which can pull only
sprig/*and cannot list the registry catalog.