GrantLock

A committed lockfile for who your AI agent stack runs as — API keys, OAuth grants, service accounts, MCP server secrets, CI tokens, cloud roles — and a CI gate on semantic changes to those grants. Secret values are never recorded.

Research prototype published for evaluation. The pre-registered acceptance gate on sealed, independently written holdouts (GATE 2) has not been passed; see the gate history below. Everything on this page was computed ahead of time; the page makes no requests.

Precomputed scans on public GrantBench cases

Deploy job swaps OIDC for stored static AWS keys

A GitHub Actions deploy job that assumed an IAM role through OIDC now reads an access key pair from secrets.

What changed
--- before/.github/workflows/deploy.yml
+++ after/.github/workflows/deploy.yml
@@ -18,5 +18,4 @@
     permissions:
       contents: read
-      id-token: write
     steps:
     - name: Check out repository
@@ -25,5 +24,6 @@
       uses: aws-actions/configure-aws-credentials@v4
       with:
-        role-to-assume: arn:aws:iam::020674995309:role/data-analyst-agent-deploy
+        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
         aws-region: ap-northeast-1
     - name: Deploy
ClassSeverityChangeLocationMessage
G6warningintroduced.github/workflows/deploy.yml:26gha_workflow 'Deploy/deploy' uses long-lived aws credential AWS_ACCESS_KEY_ID; GitHub Actions OIDC -> AWS IAM role (aws-actions/configure-aws-credentials role-to-assume)
G7criticalintroduced.github/workflows/deploy.yml:26gha_workflow 'Deploy/deploy' uses aws:AWS_ACCESS_KEY_ID, which is not in grantlock.json
G8warningremoved.github/workflows/deploy.ymlaws:oidc:arn:aws:iam::020674995309:role/data-analyst-agent-deploy is locked for gha_workflow 'Deploy/deploy' but no component references it any more

grantlock check exit code: 1

Kubernetes Role widened to verbs: ["*"]

An existing Role bound to the agent's ServiceAccount is widened from get/list to every verb.

What changed
--- before/deploy/k8s/rbac.yaml
+++ after/deploy/k8s/rbac.yaml
@@ -11,6 +11,5 @@
   - configmaps
   verbs:
-  - get
-  - list
+  - '*'
 - apiGroups:
   - coordination.k8s.io
ClassSeverityChangeLocationMessage
G2criticalintroduceddeploy/k8s/rbac.yaml:7k8s_rbac 'data-analyst-agent-reader': kubernetes:serviceaccount:agents-prod/data-analyst-agent gained rbac:ns:*:configmaps
G3criticalintroduceddeploy/k8s/rbac.yaml:7k8s_rbac 'data-analyst-agent-reader': kubernetes:serviceaccount:agents-prod/data-analyst-agent holds wildcard scope 'rbac:ns:*:configmaps'

grantlock check exit code: 1

Least-privilege stack (clean)

compose + Kubernetes + Terraform with federated identities, owners and minimal grants: nothing to report.

No findings.

grantlock check exit code: 0

GrantBench results

CorpusGrantLock G1–G8 recallClean false positivesDiff accuracygitleaks G1trufflehog G1Baselines G2–G8
dev (frozen, 120 cases)10/10 every class0/2416/166/107/100/10 per class
dev-ext (68 cases)6/6 every class0/128/85/66/60

Development corpora share an author with the detector: consistency checks, not field results.

Gate history

Bar: recall on G2, G3, G7 each ≥ 80% and mean ≥ 90%; clean false positives ≤ 5%.

StepResultOutcome
Sealed holdout v1 (independent author)G2 6/7, G3 5/7, G7 8/8 (mean 85.7%); clean false positives 0/16Fail (recall)
Fix round 1Four blind spots fixed—
Sealed holdout v2 (second independent author)G2 7/8, G3 7/8, G7 8/8 (mean 91.7%); G1 6/8 (one detector exception); clean false positives 5/16; baselines G1 5/8, G2–G8 0Fail (false positives)
Fix round 2One compose rule narrowed; format-table / schema mismatch fixed—
Re-verification on disclosed v2 (not a gate)Clean false positives 0/16, G1 7/8, diff 8/8; G2, G3, G7 unchangednot a pass

The pre-registered GATE 2 has not been passed.