Live demo

Watch one permission become an admin path.

The real engine on bundled synthetic snapshots. Pick a scenario; everything below is computed from the files, not scripted.

The pull request

Check failed

The same one-line change as a pull request on snapshots/app-prod.json, checked by the command the repository's workflow runs on every pull request. The note on the changed line and the log are that command's real output.

PassRole widened to the deployment admin role
1 line changed in snapshots/app-prod.json
snapshots/app-prod.json
26 {"id": "f-ci-pass-app-runtime", "predicate": "iam_pass_role_to_lambda", "subject": "p-ci-deployer", "object": "r-app-runtime", "state": "true"},
27- {"id": "f-ci-pass-deploy-admin", "predicate": "iam_pass_role_to_lambda", "subject": "p-ci-deployer", "object": "r-deploy-admin", "state": "false"},
27+ {"id": "f-ci-pass-deploy-admin", "predicate": "iam_pass_role_to_lambda", "subject": "p-ci-deployer", "object": "r-deploy-admin", "state": "true"},
New path to a protected rolePermission check / AttackGraph AI

CI deploy user (build pipeline) can now run code as Deployment admin role. The proposal changes one condition: CI deploy user (build pipeline) can pass Deployment admin role to Lambda (false to true). With it, all 7 conditions on this route hold. Revoking f-ci-pass-deploy-admin on a copy removes the path and keeps 2 of 2 normal access checks. Verified in this model. Nothing was deployed or executed.

28 {"id": "f-ci-create-build-hook", "predicate": "lambda_create_function", "subject": "p-ci-deployer", "object": "l-build-hook", "state": "true"},
Permission check / AttackGraph AI exit status 1

1 new high-risk finding in the proposal.

Show the check's log
$ python -m attackgraph --github -- base-branch/snapshots/app-prod.json snapshots/app-prod.json
AttackGraph AI 0.1.0 (attackgraph-rules-v1). Static check of synthetic snapshots; no AWS calls.
Baseline: base-branch/snapshots/app-prod.json
Proposal: snapshots/app-prod.json

Added · High · CI deploy user (build pipeline) can now run code as Deployment admin role
  Route: p-ci-deployer → l-build-hook → r-deploy-admin
  Changed: f-ci-pass-deploy-admin, false → true at /facts/1 (line 27)
  Fix: revoke f-ci-pass-deploy-admin. Verified in this model; normal access kept 2 of 2.

Coverage: complete
Result: 1 new high-risk finding in the proposal.
(exit status 1)

The pull request

Check passed

The same one-line change as a pull request on snapshots/app-prod.json, checked by the command the repository's workflow runs on every pull request. The note on the changed line and the log are that command's real output.

PassRole widened to the deployment admin role
Fix commit: revoke f-ci-pass-deploy-admin, the verified fix. 1 line changed in snapshots/app-prod.json
snapshots/app-prod.json
26 {"id": "f-ci-pass-app-runtime", "predicate": "iam_pass_role_to_lambda", "subject": "p-ci-deployer", "object": "r-app-runtime", "state": "true"},
27- {"id": "f-ci-pass-deploy-admin", "predicate": "iam_pass_role_to_lambda", "subject": "p-ci-deployer", "object": "r-deploy-admin", "state": "true"},
27+ {"id": "f-ci-pass-deploy-admin", "predicate": "iam_pass_role_to_lambda", "subject": "p-ci-deployer", "object": "r-deploy-admin", "state": "false"},
28 {"id": "f-ci-create-build-hook", "predicate": "lambda_create_function", "subject": "p-ci-deployer", "object": "l-build-hook", "state": "true"},
Permission check / AttackGraph AI exit status 0

No new modelled high-risk access.

Show the check's log
$ python -m attackgraph --github -- base-branch/snapshots/app-prod.json snapshots/app-prod.json
AttackGraph AI 0.1.0 (attackgraph-rules-v1). Static check of synthetic snapshots; no AWS calls.
Baseline: base-branch/snapshots/app-prod.json
Proposal: snapshots/app-prod.json

No modelled high-risk access in either snapshot.

Coverage: complete
Result: No new modelled high-risk access.
(exit status 0)
Inside the check

The engine's analysis of the change as submitted: the route it opens, the condition that flipped and the fix it verified.

1 new path to a protected role

CI deploy user (build pipeline) can now run code as Deployment admin role. Nothing was deployed: this is a static check of two synthetic snapshots.

Facts changed
1
f-ci-pass-deploy-admin
New risky paths
1
Baseline 0, proposed 1
Coverage
Complete
Every relationship resolved
Verified fix
1 revocation
Normal access kept: 2 of 2
Facts changed
1
f-ci-pass-deploy-admin
New risky paths
1
Baseline 0, proposed 1, after simulated fix 0
Coverage
Complete
Every relationship resolved
Verified fix
1 revocation
Normal access kept: 2 of 2

The path it opens

CI deploy user (build pipeline) can now run code as Deployment admin role. Follow the arrows: each one is an established relationship.

In the baseline the same route was blocked because f-ci-pass-deploy-admin was false.

The path, closed by the simulated fix

f-ci-pass-deploy-admin is revoked on a copy of the proposal. The route no longer reaches the protected role.

In the baseline the same route was blocked because f-ci-pass-deploy-admin was false.

The change

New path

The proposal flips this condition. Nothing else on the route changed.

CI deploy user (build pipeline) can pass Deployment admin role to Lambda
falsetrue
Fact f-ci-pass-deploy-admin at /facts/1
What is iam:PassRole?

It is the permission to hand an IAM role to an AWS service. If someone can hand a powerful role to a Lambda function they write and run, their code runs with that role's permissions. That is why this one permission matters.

Why the route works

7 of 7 hold

A route only counts when every condition holds. The changed one was the missing piece.

  • CI deploy user (build pipeline) can pass Deployment admin role to Lambda
    Changed: false to truef-ci-pass-deploy-admin
  • CI deploy user (build pipeline) can create Post-build hook function
    f-ci-create-build-hook
  • CI deploy user (build pipeline) can invoke Post-build hook function
    f-ci-invoke-build-hook
  • CI deploy user (build pipeline) controls the code that Post-build hook function runs
    Scenario assumptionf-ci-controls-build-hook-code
  • Deployment admin role trusts the Lambda service
    f-deploy-admin-trusts-lambda
  • Everything is in one AWS account
  • No unresolved policy restrictions apply

What this means

Amazon Bedrock

Amazon Nova Pro explains the evidence in plain English. It sees placeholder IDs only and cannot change the result.

Recorded AI explanation

The proposed change allows principal CI deploy user (build pipeline) to pass role Deployment admin role to Lambda workload Post-build hook function, enabling CI deploy user (build pipeline) to run code as Deployment admin role. This is modeled as a high-severity impact because Deployment admin role is classified as a privileged role. Fix candidate revoke/f-ci-pass-deploy-admin, which revokes fact f-ci-pass-deploy-admin (iam_pass_role_to_lambda from CI deploy user (build pipeline) to Deployment admin role), removes this finding without affecting expected accesses ea-ci-reads-build-artifacts and ea-ci-deploys-app-runtime.

Limitations: This is a synthetic model. No changes were made to any AWS accounts. The result does not indicate a real compromise. The scenario assumes CI deploy user (build pipeline) is controlled by the change proposer, and fact f-ci-controls-build-hook-code is assumed, not observed.

Model
apac.amazon.nova-pro-v1:0
Region
ap-southeast-1
Request
ebf66418-50b4-4e8f-8ee2-8675d76a6fb0
Tokens
1,637 in, 229 out
Latency
1.4 s
Recorded
2026-09-30T08:59:28Z
Prompt
explain-v2

This hosted page never calls Bedrock. The reply was recorded from a live call and checked claim by claim; run the app locally to generate a new one.

Deterministic summary for comparison

CI deploy user (build pipeline) can now run code as Deployment admin role. The proposal changes one condition: CI deploy user (build pipeline) can pass Deployment admin role to Lambda (false to true). With it, all 7 conditions on this route hold. Revoking f-ci-pass-deploy-admin on a copy removes the path and keeps 2 of 2 normal access checks. Verified in this model. Nothing was deployed or executed.

What the model sees
{
 "analysis": {
  "model_version": "attackgraph-rules-v1",
  "data": "synthetic configuration JSON",
  "comparison": "baseline versus proposed snapshot",
  "coverage_complete": true
 },
 "finding": {
  "id": "A1",
  "impact": "privileged_role_use",
  "severity": "High",
  "comparison_status": "added",
  "provisional": false,
  "entry": "P1",
  "target": "R1",
  "target_classification": "privileged_role",
  "baseline_state": "unreachable",
  "proposal_state": "reachable"
 },
 "nodes": [
  {
   "alias": "P1",
   "kind": "principal"
  },
  {
   "alias": "R1",
   "kind": "role"
  },
  {
   "alias": "L1",
   "kind": "lambda"
  }
 ],
 "witness": [
  {
   "step": 1,
   "rule": "lambda_pass_role",
   "from": "P1",
   "to": "R1",
   "via": "L1",
   "state": "true",
   "prerequisites": [
    {
     "id": "F1",
     "predicate": "iam_pass_role_to_lambda",
     "subject": "P1",
     "object": "R1",
     "baseline_state": "false",
     "proposal_state": "true",
     "changed": true
    },
    {
     "id": "F2",
     "predicate": "lambda_create_function",
     "subject": "P1",
     "object": "L1",
     "baseline_state": "true",
     "proposal_state": "true",
     "changed": false
    },
    {
     "id": "F3",
     "predicate": "lambda_invoke_function",
     "subject": "P1",
     "object": "L1",
     "baseline_state": "true",
     "proposal_state": "true",
     "changed": false
    },
    {
     "id": "F4",
     "predicate": "controls_workload_code",
     "subject": "P1",
     "object": "L1",
     "baseline_state": "true",
     "proposal_state": "true",
     "changed": false,
     "scenario_assumption": true
    },
    {
     "id": "F5",
     "predicate": "role_trusts_lambda_service",
     "subject": "R1",
     "baseline_state": "true",
     "proposal_state": "true",
     "changed": false
    },
    {
     "id": "D1",
     "check": "same_account",
     "state": "true"
    },
    {
     "id": "D2",
     "check": "policy_controls_resolved",
     "state": "true"
    }
   ]
  }
 ],
 "changed_facts": [
  {
   "id": "F1",
   "baseline_state": "false",
   "proposal_state": "true"
  }
 ],
 "assumptions": [
  "P1 is treated as controlled by the party proposing the change (scenario assumption).",
  "Fact F4 is a scenario assumption, not an observed permission."
 ],
 "fix_candidates": [
  {
   "id": "X1",
   "action": "set this fact to false on an in-memory copy of the proposal",
   "revokes_fact": "F1",
   "removes_this_finding": true,
   "verified_in_model": true,
   "coverage_complete_after": true,
   "high_risk_findings_after": 0,
   "expected_access_after": [
    {
     "id": "E1",
     "relationship": "object_read",
     "before_fix": "pass",
     "after_fix": "pass"
    },
    {
     "id": "E2",
     "relationship": "role_use",
     "before_fix": "pass",
     "after_fix": "pass"
    }
   ]
  }
 ],
 "rules": {
  "s3_direct_read": "Established when the subject has an effective s3:GetObject fact for the object and policy controls are resolved.",
  "lambda_pass_role": "Established only when all are true for the same subject, role and workload: the subject can pass the role to Lambda, create and invoke the workload, and controls its code; the role trusts the Lambda service; all three are in one account; policy controls are resolved. The subject can then run code as the role."
 },
 "predicates": {
  "s3_get_object": "subject has effective s3:GetObject on object",
  "iam_pass_role_to_lambda": "subject can pass role object to Lambda (iam:PassRole)",
  "lambda_create_function": "subject can create Lambda workload object",
  "lambda_invoke_function": "subject can invoke Lambda workload object",
  "controls_workload_code": "subject controls the code of object",
  "role_trusts_lambda_service": "subject trust policy allows the Lambda service"
 }
}

The fix

Revoke f-ci-pass-deploy-admin (CI deploy user (build pipeline) can pass Deployment admin role to Lambda) on an in-memory copy of the proposal, then rerun the whole analysis.

Normal access the fix must keep

  • CI deploy user (build pipeline) reads artifacts/release.zip
    Proposed: pass
  • CI deploy user (build pipeline) can use App runtime execution role
    Proposed: pass

Normal access the fix must keep

  • CI deploy user (build pipeline) reads artifacts/release.zip
    Proposed: pass, after the fix: pass
  • CI deploy user (build pipeline) can use App runtime execution role
    Proposed: pass, after the fix: pass

The route after the fix

High-risk paths
1 → 0
Proposed, then after the fix
Normal access
2 of 2
Expected-access checks that still pass

Verified in this model The path is gone and every relationship stayed resolved. The input files are unchanged.

Evidence for reviewers: every condition, pointer and check

Every condition and its JSON pointer

Step 1. p-ci-deployer runs code as r-deploy-admin through Lambda workload l-build-hook (Rule B): candidate B:p-ci-deployer->r-deploy-admin@l-build-hook is true

StateConditionEvidenceJSON pointer
truep-ci-deployer can pass role r-deploy-admin to Lambda (iam:PassRole)f-ci-pass-deploy-admin changed false to true/facts/1
truep-ci-deployer can create Lambda workload l-build-hookf-ci-create-build-hook/facts/2
truep-ci-deployer can invoke Lambda workload l-build-hookf-ci-invoke-build-hook/facts/3
truep-ci-deployer controls the code of l-build-hookf-ci-controls-build-hook-code scenario assumption/facts/4
truer-deploy-admin trust policy allows the Lambda servicef-deploy-admin-trusts-lambda/facts/6
truep-ci-deployer, r-deploy-admin and l-build-hook are in the same synthetic accountderived/nodes/0/account_id, /nodes/2/account_id, /nodes/3/account_id
truepolicy restrictions are declared resolved for this scenarioderived/coverage/policy_controls
  • Entry principal p-ci-deployer is treated as controlled by the party proposing the change. This is a scenario assumption, not an inferred compromise.
  • p-ci-deployer controls the code of l-build-hook (scenario assumption, fact f-ci-controls-build-hook-code).
  • Declared facts stand for resolved synthetic model inputs; they are not evidence that AWS would authorise the same request in a real account.

Coverage

SnapshotCoverageRule A: direct S3 readRule B: role use via LambdaPolicy controlsUnmodelled mechanisms
Baselinecomplete2 established, 2 blocked, 0 unresolved1 established, 2 blocked, 0 unresolvedall 5 resolvednone
Proposedcomplete2 established, 4 blocked, 0 unresolved2 established, 2 blocked, 0 unresolvedall 5 resolvednone

Every fact that changed

FactStatementBaselineProposedPointer
f-ci-pass-deploy-adminp-ci-deployer can pass role r-deploy-admin to Lambda (iam:PassRole)falsetrue/facts/1

Full report

Download the Markdown report with every finding, pointer, coverage issue and assumption. Engine 0.1.0, model attackgraph-rules-v1, analysis 56bc16da787997e8.