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. At signup, every customer organization is assigned an internal tenant identifier, and that identifier — not your Azure DevOps organization GUID — is the boundary every layer of the system enforces. Your organization GUID is recorded too, but only as a lookup value that resolves to your tenant identifier server-side. 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 a partitioned database. Every collection that holds tenant data — review records, findings, usage counters, encrypted credentials, and configuration — is partitioned by your internal tenant identifier, not by your Azure DevOps organization GUID.
- 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 data occupies its own logical partition with its own storage envelope.
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. When that credential is a personal access token, it is encrypted before it ever lands in our database. With service-principal auth (available on Business plan and above) there is no customer credential to store at all — access is brokered by your Microsoft Entra directory and the backend mints short-lived tokens on demand.
We use envelope encryption with two key tiers:
- Key Encryption Key (KEK). A single RSA key kept in a managed key-management service. The KEK never leaves that service — wrap and unwrap operations happen inside the service boundary.
- 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 record, asks the key-management service 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 the key-management service.
Webhook authentication scoped to your tenant
Azure DevOps service hooks don't sign their payloads, so ADO Pilot issues a per-tenant credential at onboarding, via our API gateway's native subscription-key mechanism, and stores it as a confidential subscription input.
- Edge validation. Our API gateway validates the credential before the request reaches our backend. A forged, missing, or revoked credential is rejected at the edge.
- Tenant binding. The credential is unique to your tenant and is bound to your organization at the gateway: an event whose payload names a different organization than the credential's is rejected before it reaches our backend. Our backend then independently resolves the event's organization to your tenant and drops any event from an organization it doesn't recognize. A credential leaked from one tenant cannot be replayed against another tenant's events.
- Revocation, not rotation. There is no shared secret to rotate and no fixed expiry to track — if you cancel your plan or uninstall the extension, ADO Pilot revokes your tenant's credential immediately at the gateway, with no separate cleanup step.
The same tenant 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 — our API gateway rejects unsigned or malformed traffic before it reaches the backend.
- At the application — the backend resolves the caller's organization to a tenant identifier and verifies the request targets that same tenant, which must be active and within scope.
- At the database — every query and patch is partitioned on
/tenantId, 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 our key-management service can unwrap.
If any one layer fails, the layers above and below it still hold the boundary.