Tenant isolation
How ADO Pilot keeps each customer organization's data, credentials, and webhook traffic separated from every other tenant.
Last updated
ADO Pilot is multi-tenant. Every customer organization is keyed by its Azure DevOps organization GUID — orgId — and that identifier is the boundary every layer of the system enforces. One tenant cannot read, write, or replay traffic against another tenant's data, credentials, or webhook endpoint.
Per-tenant database partitioning
ADO Pilot stores its operational data in Azure Cosmos DB. Every container that holds tenant data — review records, findings, usage counters, encrypted credentials, and configuration — uses /orgId as the partition key.
- Every read and write goes through the partition key. There is no query path in the application that can fan out across tenants.
- Cross-tenant reads would require code that omits the partition key. We don't have any; integration tests verify it.
- Each tenant's logical partition has its own storage and throughput envelope, so one tenant's traffic doesn't push another tenant's reads off-budget.
Per-tenant credential encryption
ADO Pilot needs to call the Azure DevOps REST API on your behalf — to fetch PR diffs, post comments, and update status checks. The credential we use to do that (a personal access token today, OAuth or service-principal flows on the roadmap) is encrypted before it ever lands in our database.
We use envelope encryption with two key tiers:
- Key Encryption Key (KEK). A single RSA key kept in Azure Key Vault. The KEK never leaves Key Vault — wrap and unwrap operations happen inside the vault.
- Data Encryption Key (DEK). A unique AES-256 key generated for each tenant when their credential is first stored. The DEK encrypts the credential payload and is then itself wrapped by the KEK and stored alongside the encrypted credential.
To use a credential, ADO Pilot reads the wrapped DEK and the encrypted payload from your tenant document, asks Key Vault to unwrap the DEK, and decrypts the payload in memory. The plaintext credential exists only for the duration of the API call. A compromised database snapshot is unusable on its own — the KEK stays in Key Vault.
Webhook authentication scoped to your tenant
Azure DevOps service hooks don't sign their payloads, so ADO Pilot mints a per-tenant JSON Web Token (JWT) at onboarding, stores it as a confidential subscription input, and validates it on every incoming webhook.
- Edge validation. Azure API Management validates the JWT signature, issuer, audience, and expiry before the request reaches our backend. Forged or expired tokens are rejected at the edge.
- Tenant binding. The JWT carries a
subclaim equal to yourorgId. Our backend rejects any webhook wherejwt.subdoes not equal theorgIdin the request body. A token leaked from one tenant cannot be replayed against another tenant's events. - Rotation. JWTs have a 90-day lifetime and are rotated on a fixed cadence by a background job, with alerting if a tenant approaches expiry without a successful rotation.
The same orgId boundary applies to every other ingress: the admin portal, the onboarding wizard, and the in-product extension surface all bind requests to the calling user's organization and refuse cross-tenant operations.
Trust boundary, end to end
For any request, the question "is this tenant allowed to do this?" is answered the same way at every layer:
- At the edge — API Management rejects unsigned or malformed traffic before it reaches the backend.
- At the application — the backend verifies the caller's
orgIdmatches the resource'sorgIdand that the tenant is active and within scope. - At the database — every query and patch is partitioned on
/orgId, so even a bug above this layer cannot read another tenant's data. - At the credential store — secrets are wrapped with a tenant-specific DEK that only Key Vault can unwrap.
If any one layer fails, the layers above and below it still hold the boundary.