Security
Model
Amy's extension contains no credentials and no backend address. Each organization connects its own backend and keeps its AI provider credentials in its own secret store. The browser never receives, stores, displays or logs an AI credential.
| Boundary | Control |
|---|---|
| Extension → organization configuration | Non-secret document in Azure DevOps extension data. Strict schema with unknown fields rejected; URL, HTTPS and trusted-hostname checks on every load; change history. Storage cannot restrict writers (see ADMIN-CONFIGURATION.md), so every user confirms the backend hostname before first use and after every change. |
| Extension → backend | HTTPS. Metadata check without following redirects. Without a separate sign-in (AUTH_MODE=azure-devops), the user's Azure DevOps access token once per session, checked with Azure DevOps and discarded; with AUTH_MODE=entra, an Entra ID sign-in run by the backend. The session token is kept in the browser tab's sessionStorage until it expires (never localStorage or cookies), and discarded when the backend rejects it. Authorization and x-azdo-organization headers; no cookies (credentials: omit). |
| Extension → publisher's license service (Set up a backend) | HTTPS, accepted only from the extension's origin (CORS). Amy's app token, signed with the extension's key; the organization's ID and name; the user's Azure DevOps access token, used once to ask Azure DevOps (connectionData) whether the user belongs to that organization under that ID, then discarded, never stored or logged. Pull-only registry access per organization; a revoked organization is never re-enabled there; at most one new password per organization every 10 minutes, and the previous one keeps working once. |
| Backend authentication | Session token (HS256, SESSION_SIGNING_KEY): signature, issuer, audience (this API only), expiry and required claims. Azure DevOps token check: Azure DevOps itself confirms the user in the requested organization (connectionData); unauthenticated, anonymous and inactive identities are refused. At an Entra ID sign-in: ID token RS256 signature against the published keys, issuer, audience (the app's client ID), expiry, nonce, PKCE. |
| Backend authorization | Allowed Azure DevOps organizations (membership verified with Azure DevOps when the session starts, and the header must match the session); with an Entra ID sign-in also allowed tenants and optional allowed users, groups or app role; enabled operations. A valid token alone is not enough. CORS is not used as authentication. |
| Organization isolation | Usage counters, rate limits, concurrency, audit records and sign-in state are keyed by organization. The admin usage endpoint returns one organization at a time. |
| Backend → secrets | Reference deployment: Key Vault (RBAC) references resolved by the App Service managed identity (Key Vault Secrets User, read-only). No Key Vault SDK; no secrets in IaC, pipelines or outputs. |
| Backend → AI provider | HTTPS in production. The key is injected from validated configuration. Timeout and bounded retries. Upstream bodies are never logged or returned. |
| Extension → Azure DevOps | User identity and documented scopes (PERMISSIONS.md). No PAT. Writes only after explicit confirmation. |
| Service hook → backend | Shared secret in the x-amy-hook-secret header (compared in constant time, stored in Key Vault). Organization taken from the payload and checked against ALLOWED_ORGANIZATIONS. Completion is verified by reading the pull request again, never from the payload. Deliveries are deduplicated by event ID. |
| Backend → Azure DevOps (pull-request checks) | The App Service managed identity, with read access to the enabled repositories and Wikis and permission to comment on pull requests. It never writes Wiki pages or work items. |
| Backend → Azure DevOps (Code with Amy) | The same identity, on administrator-enabled repositories only: Read, Contribute, Create branch and Contribute to pull requests, plus View builds and Queue builds on the pipelines that validate pull requests. Only organization administrators and the teams they allow reach any of its routes, and a run starts only on repositories the requester can contribute to themselves (Git Repositories Contribute, from the repository's permissions and the requester's groups). Nothing is pushed before a person approves the plan, and only to branches under amy/code/, from the head it read; a target branch is never pushed to, and a pull request is never completed. What Amy writes runs in your pipelines before anyone reviews it, which is what validation pipelines do with any developer's branch. Its limits, enforced in code and not by the prompt: secrets files can't be read or changed; other secrets reach the model masked, and an edit is applied by exact quote only where the quote stands in the masked text too, so masked values can't be overwritten, read back or confirmed by guessing; text that looks like a secret isn't written; pipelines, agents' instructions and protected paths change only if the approved plan or the person names them, and the approval says so; .git and .amy never; commit messages can't skip pipelines. Files, documents, work items, review comments and logs are untrusted input to the model: it has no web access and no other way out than the pull request, which a person reviews and which says what changed outside the plan and which tests got weaker. |
| Backend → Azure DevOps (Repository Documentation) | The same identity, on administrator-enabled repositories only (checked again when a job is created and estimated): Read, Contribute, Create branch and Contribute to pull requests. Pushes go only to branches under amy/docs/ and amy/dev-docs/, each from the branch head it read (a moved branch is refused, never overwritten); pull requests target the default branch, so its branch policies and the administrators' required reviewers apply, or for dev documents the dev branch, where administrators may choose no reviewers: those pull requests then complete by themselves once the dev branch's policies pass. Updates after merges and scheduled dev documents have no person behind them, so their checks read every enabled repository: enable only repositories that everyone who uses Amy may read. The service hook is authenticated by PR_DOCS_HOOK_SECRET, and what it says is confirmed by reading the pull request. Code is masked for secrets before it reaches the AI model, and every code quote the documentation keeps is checked against the file. Only administrators change the settings. A job starts only for repositories the requester can read themselves (Git Repositories Read, from the repository's permissions and the requester's groups), and its checks of findings against connected services read only enabled repositories the requester can read. Runs are visible to everyone with the Code tab; only who started one or an administrator can stop, discard or retry it or download its files. |
| Backend → Azure DevOps (Ask with search, Diagnose a Ticket) | The same identity reads only the projects administrators chose and never the repositories they excluded, whatever the AI model asks for; secrets files are neither indexed nor read, and values that look like secrets are masked. |
| Mockups → browsers | Versions open at /m/<token>/<file> without sign-in (192-bit random token; a link stops working when sharing is turned off or it expires), with MOCKUP_CSP (a sandbox: own scripts only, nothing loaded from elsewhere, nothing sent, no pop-ups or navigation), noindex and no-referrer. While Amy's designer works, the page runs in hidden sandboxed frames in the user's browser with the same rules as a meta tag, and no dialogs. |
| MCP client → backend | Entra access token for this API (v2, RS256 against the published keys, issuer, audience, expiry, delegated scope Amy.Mcp), then the same tenant, organization, user, group and role policy as sessions. Azure DevOps is read with an on-behalf-of token for the user; no backend identity is used. Origins outside ALLOWED_ORIGINS are refused. Access by tab applies to each tool (the tab its feature is on), and a failed tool returns only messages written for people. |
| Stored proposals | Keyed by organization. AI-client proposals are returned and decided only for their owner. A decision applies only to the revision the user reviewed (409 otherwise). |
Reference backend controls
- Fail-fast configuration: startup fails on missing values, placeholders, non-HTTPS production URLs, credentials in URLs, unresolved Key Vault references,
AUTH_MODE=nonein production, or missingALLOWED_ORGANIZATIONS. Messages name the variable, never its value. - No secrets in responses: health, readiness and metadata expose no secrets, tenant IDs, provider names or infrastructure names, and probes never call the provider.
- Logging:
- Logged: metadata only (correlation ID, operation, organization, pseudonymous user reference, latency, outcome, status category, retries, token counts).
- Never logged: request query strings (sign-in codes), mockup share tokens (
/m/[token]/…) and client addresses. - Redacted at any depth:
authorization,cookie,api-key,x-api-key,x-admin-token,AI_PROVIDER_API_KEY,ENTRA_CLIENT_SECRET,SESSION_SIGNING_KEY,ADMIN_DIAGNOSTICS_TOKEN,PR_DOCS_HOOK_SECRET,ADO_PAT,client_secret,access_token,refresh_token,id_token,code_verifierand similar. - Errors are serialized without headers.
- Usage protection: per-user, per-organization and global request limits, daily and monthly token quotas, rate and concurrency limits, input and output-token limits, enabled operations, and an emergency switch. Automatic pull-request checks and MCP calls go through the same limits.
- Model output: schema and semantic validation with one repair attempt. Output is escaped before it is written to Azure DevOps.
Reporting a vulnerability
Report vulnerabilities privately to contact@valor-co.com. Don't include credentials or content from your organization.
Review checklist (backend operators)
-
AUTH_MODE=azure-devops(orentra), withALLOWED_ORGANIZATIONSandALLOWED_ORIGINSlimited to your organizations and the Marketplace iframe origin. -
SESSION_SIGNING_KEY,ENTRA_CLIENT_SECRET(withentraor MCP),AI_PROVIDER_API_KEY(andADMIN_DIAGNOSTICS_TOKEN) are Key Vault references, shown as resolved in the portal. - The app identity has only Key Vault Secrets User; secret administrators are separate.
- User consent to unverified applications is restricted in your Entra tenant.
- Usage limits match your budget;
AI_DISABLEDis known to on-call staff. - The data handling (DATA-HANDLING.md) matches your AI provider's terms.
- With
CODE_AGENT_ENABLED, the app identity has Contribute and Create branch only on the repositories administrators enable; target branches have branch policies with required reviewers, so nothing Amy writes is merged unreviewed; and the pipelines that validate pull requests have no more access to secrets than a developer's branch should. - With
REPO_DOCS_ENABLED, the app identity has Contribute and Create branch only on the repositories administrators enable, and the default branches have branch policies (required reviewers), so nothing Amy writes is merged unreviewed.