Manage GraphWarden secrets across environments

Store GraphWarden credentials in three places: Azure Key Vault for production proxies, your CI platform's secret store for pipelines, and a per-developer .env file for local work. Never commit real secrets to source. This page covers the rotation playbook and the detection of accidental commits.

Three-tier secrets model

Prerequisites

- An Azure Key Vault provisioned for your organization, with access granted to the identity that runs the proxy in production - A CI platform with a secret store — Azure DevOps variable groups OR GitHub Actions repository secrets - A `.env` convention for local development, with `.env` excluded from git via `.gitignore`

The three tiers

Each tier holds the credentials for one environment class. A credential never crosses tiers without an operator action.

Tier 1: Azure Key Vault (production)

Production proxy instances read their credentials from Azure Key Vault. The proxy authenticates to Key Vault through Managed Identity — no vault credentials live on the host.

The two Key Vault settings the proxy reads come from the proxy environment variable inventory: KeyVault:VaultUri points at the vault (https://my-vault.vault.azure.net/), and KeyVault:UseManagedIdentity is true in production (the default). The Graph client secret and the GraphWarden rule-sync HMAC secret live in the vault as named secrets; the proxy resolves them at startup and on rotation.

Access to the vault is granted to the proxy's Managed Identity through an access policy or RBAC assignment — Secret Get is the minimum permission. Humans do not need to read the vault in daily operations; the proxy and the rotation script are the only callers.

Tier 2: CI platform secret store (pipelines)

CI pipelines read their own copies of the GraphWarden App Identity credentials — a distinct Proxy Client ID and Proxy Client Secret pair issued specifically for the CI workload. The pipelines do not read from Key Vault; reading from Key Vault requires a Managed Identity that CI runners do not typically carry.

Two stores are supported:

  • Azure DevOps variable groups. Create one variable group per environment (for example, graphwarden-secrets-staging and graphwarden-secrets-production). Mark each secret as secret (the lock icon) so Azure DevOps masks the value in logs.
  • GitHub Actions repository secrets. Add each secret through Settings > Secrets and variables > Actions. Use GitHub Environments for per-environment scoping and for requiring approval before a secret-bearing job runs.

Rotate the CI credentials on the same cadence as the production credentials. If the CI workload and production share an App Identity, rotating one rotates both.

Tier 3: Developer .env (local)

A developer working on the proxy or on a GraphWarden-compatible product needs credentials that point at a non-production GraphWarden instance. The pattern that scales: each developer has their own .env file, checked against a .env.example template that is committed to source, with real values filled in locally. The .env file is listed in .gitignore so it never reaches the repository.

The credentials in .env are for a dedicated development GraphWarden deployment (a shared dev instance or a per-developer local proxy). They are not the production credentials. If a developer needs to reproduce a production-only issue, the pattern is to obtain a time-limited scoped credential from GraphWarden Admin rather than to hand over the production vault entry.

Rotation playbook

Rotate GraphWarden App Identity credentials on your organization's standard secret-rotation cadence — typically every 90 days, or immediately on any credential exposure. The steps are ordered so the rotation is zero-downtime.

  1. Rotate in Key Vault. Open the production Azure Key Vault. Add a new version of the Graph client secret and the GraphWarden HMAC secret. Key Vault keeps the previous version available until you explicitly disable it; the proxy picks up the new version on its next rule-sync poll (default 60 seconds per the proxy environment variable inventory).
  2. Update the CI secret stores. Open each Azure DevOps variable group and each GitHub Actions repository secret that holds the CI App Identity credentials. Paste the new values. The next pipeline run picks up the updated credentials.
  3. Notify developers to update .env. If the developer tier uses credentials that were rotated, post in the team channel with the new values (through your organization's secure messaging channel — not in chat that archives to a searchable log). Developers replace the values in their local .env and restart their local dev tools.
  4. Verify the next pipeline run succeeds. Trigger a pipeline on a safe branch and confirm the smoke test passes against the rotated credentials. A failure here indicates one of the three stores still holds the old value; trace the failing pipeline's secret store to find the outlier.
  5. Revoke the old secret in GraphWarden Admin. Once every store is updated and verified, open GraphWarden Admin and revoke the previous App Identity credential version. This closes the window where a leaked old credential could still authenticate. Revoke only after verification — revoking before step 4 can take down a pipeline that still reads the old value.

A rotation that skips the verification step can cause a production outage. Always verify before revoking the old secret.

Preventing accidental commits

The cheapest secret-leak incident is the one that never happens. Three layers protect the repository from accidental commits.

.gitignore

Every repository that touches GraphWarden credentials excludes local secret files. At minimum:

.env
.env.local
.env.*.local
*.pem
*.pfx
**/secrets.json
**/appsettings.Development.json

The patterns above cover the common cases. appsettings.Development.json is excluded because the .NET proxy reads it during local development and developers paste credentials there; the committed template is appsettings.json without real secrets.

Pre-commit hooks

Pre-commit hooks catch secrets before the commit lands locally. Two tools that work well with GraphWarden's credential shapes:

  • truffleHog scans for high-entropy strings and known credential patterns. Install it once per developer machine; configure it through a .pre-commit-config.yaml.
  • gitleaks runs the same kind of scan with a configurable ruleset. gitleaks ships with rules for Azure AD client secrets (the shape GraphWarden credentials follow) and for generic API tokens.

Neither tool catches everything — a sufficiently short secret or a secret embedded in a plausible-looking string can slip through. They are a cheap first line of defense, not the only line.

GitHub push protection

GitHub's secret scanning service blocks pushes that contain recognized credential patterns before the push reaches the server. Enable it for every repository that could touch GraphWarden credentials: Settings > Code security and analysis > Push protection. For secrets GitHub does not recognize by default (GraphWarden does not yet ship a canonical detector), add custom patterns that match your issuer format — both the Proxy Client ID shape and the secret shape.

Detecting leaked secrets post-commit

A secret that makes it into a git commit is not removable by deleting the file in a later commit — git history retains the value. If a secret leaks:

  1. Revoke the secret in GraphWarden Admin immediately. The leak window ends when the credential stops working, not when the commit is rewritten. Revocation is the first action.
  2. Rotate the credential in Key Vault and the CI stores. Follow the rotation playbook above. The old value is now unusable, so verification against pipelines confirms the new value propagated correctly.
  3. Rewrite history. For public repositories, use git filter-repo (the actively maintained successor to git filter-branch) to strip the secret from every commit that contains it. Force-push the rewritten history. Collaborators need to re-clone. Note that GitHub retains the old commits in the fork network; any fork or unreachable commit may still contain the secret, which is why revocation must be the first action.
  4. Scan for other copies. Run truffleHog or gitleaks across every repository your organization owns to confirm the same secret was not pasted elsewhere. A leaked credential often appears in multiple repositories because a developer copied it to "save time."
  5. Document the incident. Record the commit SHA, the duration of exposure, the remediation steps, and the verification results. Your organization's security team will want this record.

Troubleshooting

KeyVault:UseManagedIdentity is true but the proxy reports 403 on startup

The Managed Identity does not have access to the vault. Open the Key Vault in Azure Portal, navigate to Access policies (or Access control (IAM) for RBAC-configured vaults), and confirm the proxy's Managed Identity has Secret Get permission. The identity name matches the host — for an Azure VM, it is the VM's Managed Identity; for an App Service, it is the App Service's Managed Identity.

The pipeline runs but the proxy rejects every request with 401

The CI secret store holds the old credentials. A rotation updated Key Vault but skipped the pipeline variable group. Re-run the Update the CI secret stores step of the rotation playbook and re-trigger the pipeline.

.env is not loading the expected values

The local proxy or dev tool is reading a different .env file, or the .env file is in a directory the tool does not scan. For the .NET proxy, appsettings.Development.json overrides environment variables on some hosting profiles; for Node and Python tooling, the dotenv library typically reads from the current working directory. Print the resolved values at startup to confirm what the tool actually loaded.

A developer committed a .env file despite .gitignore

git add -f .env overrides .gitignore. The fix is to revoke the leaked secret, rewrite history, and follow the detection playbook above. Add a pre-commit hook that rejects the commit when .env is staged, to reduce the odds of recurrence.

The rotation verification step fails but only on one pipeline

That pipeline reads from a secret store that did not receive the updated credentials. Azure DevOps pipelines inside the same project that reference the same variable group all receive updates at the same time; pipelines in different projects each need their own variable group updated. Trace the failing pipeline to the variable group it references and confirm the values are the post-rotation ones.

Last reviewed:

Last reviewed: .