From 2ab6ec60339a01cdf5f8867924442cdddcb15435 Mon Sep 17 00:00:00 2001 From: InfoSecHack Date: Tue, 2 Jun 2026 19:04:59 -0500 Subject: [PATCH 1/2] Add selected PassRole finding fixture for live binding --- .../README.md | 7 + .../expected_finding.json | 46 ++++ .../scenario.json | 197 ++++++++++++++++++ ...st_passrole_lambda_live_binding_fixture.py | 136 ++++++++++++ 4 files changed, 386 insertions(+) create mode 100644 tests/fixtures/live_binding/passrole_lambda_selected_finding/README.md create mode 100644 tests/fixtures/live_binding/passrole_lambda_selected_finding/expected_finding.json create mode 100644 tests/fixtures/live_binding/passrole_lambda_selected_finding/scenario.json create mode 100644 tests/test_passrole_lambda_live_binding_fixture.py diff --git a/tests/fixtures/live_binding/passrole_lambda_selected_finding/README.md b/tests/fixtures/live_binding/passrole_lambda_selected_finding/README.md new file mode 100644 index 0000000..878c69e --- /dev/null +++ b/tests/fixtures/live_binding/passrole_lambda_selected_finding/README.md @@ -0,0 +1,7 @@ +# PassRole Lambda Selected Finding Fixture + +This sanitized local fixture selects one IAMScope `passrole_lambda` finding/path for the controlled live PassRole-to-Lambda binding follow-up. + +It is local-only and synthetic/redacted. It does not run live AWS, Terraform, AWS CLI, STS, Lambda APIs, or `iam:PassRole`. It does not commit raw live output, a real AWS account id, or a real role ARN. + +The fixture is intended only to provide a stable local IAMScope finding selector for the next checkpoint slice. It does not claim broad IAMScope correctness, broad PassRole correctness, exploitability proof, downstream authorization proof, production readiness, composite benchmark scoring, or pass/fail benchmark labeling. diff --git a/tests/fixtures/live_binding/passrole_lambda_selected_finding/expected_finding.json b/tests/fixtures/live_binding/passrole_lambda_selected_finding/expected_finding.json new file mode 100644 index 0000000..4b19160 --- /dev/null +++ b/tests/fixtures/live_binding/passrole_lambda_selected_finding/expected_finding.json @@ -0,0 +1,46 @@ +{ + "binding_status": "ready_for_next_checkpoint_comparison", + "fixture_id": "passrole_lambda_selected_live_binding_finding_001", + "next_slice": "Recommended next slice: bind selected IAMScope PassRole finding to live AWS result.", + "non_claims": { + "no_broad_iamscope_correctness": true, + "no_broad_passrole_correctness": true, + "no_composite_benchmark_score": true, + "no_downstream_authorization_proof": true, + "no_exploitability_proof": true, + "no_live_aws": true, + "no_pass_fail_benchmark_label": true, + "no_production_readiness": true + }, + "selected_finding": { + "assumptions": [ + { + "detail": "no session policy restricts lambda:CreateFunction or iam:PassRole; session policies are not visible to IAMScope collectors at collection time", + "kind": "session_policy" + } + ], + "blockers_observed_count": 0, + "expected_classification": "selected_local_iamscope_finding", + "expected_verdict": "validated", + "finding_id": "5e08bf418146ae29259115f672b8960bf3b38ce07ec5cd57c5638019e126dc50", + "finding_key": "e7aa122330b61ce55fdb3cd017139b3c9b8c941fdd95fec676e2c337cde58b1e", + "pattern_id": "passrole_lambda", + "pattern_version": "1.0.0", + "required_check_states": { + "no_boundary_blocks_lambda_create_function": "pass", + "no_boundary_blocks_passrole": "pass", + "no_identity_deny_blocks_lambda_create_function": "pass", + "no_identity_deny_blocks_passrole": "pass", + "no_scp_blocks_lambda_create_function": "pass", + "no_scp_blocks_passrole": "pass", + "passrole_condition_scoped_to_lambda_or_absent": "pass", + "source_has_lambda_create_function": "pass", + "source_has_passrole_to_target": "pass", + "target_trusts_lambda_service": "pass" + }, + "severity": "critical", + "source_principal_arn": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource", + "target_role_arn": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", + "title": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource can assume admin-equivalent role arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole via Lambda PassRole chain" + } +} diff --git a/tests/fixtures/live_binding/passrole_lambda_selected_finding/scenario.json b/tests/fixtures/live_binding/passrole_lambda_selected_finding/scenario.json new file mode 100644 index 0000000..f4361d6 --- /dev/null +++ b/tests/fixtures/live_binding/passrole_lambda_selected_finding/scenario.json @@ -0,0 +1,197 @@ +{ + "account_id": "000000000000", + "aws_calls_made": 0, + "constraints": [], + "description": "Sanitized local fixture for selecting one IAMScope PassRole-to-Lambda finding for live-result binding.", + "edge_constraints": [], + "edges": [ + { + "dst": { + "node_type": "IAMRole", + "provider": "aws", + "provider_id": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", + "region": "-" + }, + "edge_id": "bcf0f131f4a66f2a73a77bae0ad7bc4b6928c92bf924da5f93a359547425b522", + "edge_type": "lambda:CreateFunction_permission", + "features": { + "allow_controls": [ + { + "control_type": "PERMISSION", + "digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "policy_arn": "arn:aws:iam::000000000000:policy/IAMScopeLiveBindingSanitizedPolicy", + "statement_index": 0, + "summary": "lambda:CreateFunction" + } + ], + "effect": "Allow", + "has_conditions": false, + "is_wildcard_resource": false, + "layer": "permission", + "raw_conditions": {}, + "resource_pattern": "arn:aws:lambda:us-east-1:000000000000:function:iamscope-live-passrole-lambda-test-redacted", + "statement_index": 0 + }, + "region": "-", + "src": { + "node_type": "IAMUser", + "provider": "aws", + "provider_id": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource", + "region": "-" + } + }, + { + "dst": { + "node_type": "IAMRole", + "provider": "aws", + "provider_id": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", + "region": "-" + }, + "edge_id": "8fbe3500a1361fd80425ac88a46d7767c2413c97cc82b940525dead1001879b9", + "edge_type": "iam:PassRole_permission", + "features": { + "allow_controls": [ + { + "control_type": "PERMISSION", + "digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "policy_arn": "arn:aws:iam::000000000000:policy/IAMScopeLiveBindingSanitizedPolicy", + "statement_index": 1, + "summary": "iam:PassRole" + } + ], + "effect": "Allow", + "has_conditions": false, + "is_wildcard_resource": false, + "layer": "permission", + "raw_conditions": {}, + "resource_pattern": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", + "statement_index": 1 + }, + "region": "-", + "src": { + "node_type": "IAMUser", + "provider": "aws", + "provider_id": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource", + "region": "-" + } + }, + { + "dst": { + "node_type": "IAMRole", + "provider": "aws", + "provider_id": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", + "region": "-" + }, + "edge_id": "cd0dc18ac07b1cb1f9a14e2fc6f7ffa3460aa830155e82be93fff5b97db6311f", + "edge_type": "iam:*_permission", + "features": { + "allow_controls": [ + { + "control_type": "PERMISSION", + "digest": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "policy_arn": "arn:aws:iam::000000000000:policy/IAMScopeLiveBindingSanitizedAdminPolicy", + "statement_index": 0, + "summary": "iam:*" + } + ], + "effect": "Allow", + "has_conditions": false, + "is_wildcard_resource": true, + "layer": "permission", + "raw_conditions": {}, + "resource_pattern": "*", + "statement_index": 0 + }, + "region": "-", + "src": { + "node_type": "IAMRole", + "provider": "aws", + "provider_id": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", + "region": "-" + } + }, + { + "dst": { + "node_type": "IAMRole", + "provider": "aws", + "provider_id": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", + "region": "-" + }, + "edge_id": "24ad4d35b4441651fb0a4a321b53addfbdd843dc3653ff54dea29c220c478668", + "edge_type": "sts:AssumeRole_trust", + "features": { + "allow_controls": [ + { + "control_type": "TRUST", + "digest": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "policy_arn": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", + "statement_index": 0, + "summary": "lambda.amazonaws.com trust" + } + ], + "effect": "Allow", + "has_conditions": false, + "is_wildcard_principal": false, + "layer": "trust", + "principal_type": "Service", + "raw_conditions": {}, + "statement_index": 0 + }, + "region": "-", + "src": { + "node_type": "AWSService", + "provider": "aws", + "provider_id": "lambda.amazonaws.com", + "region": "-" + } + } + ], + "fixture_id": "passrole_lambda_selected_live_binding_finding_001", + "generation_mode": "local_reasoner_fixture", + "live_aws_used": false, + "nodes": [ + { + "node_id": "c23e6af725515592a63bf88a0d1bf77bbd18927e492295d573e5f058bcb84425", + "node_type": "IAMUser", + "properties": { + "account_id": "000000000000", + "sanitized": true + }, + "provider": "aws", + "provider_id": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource", + "region": "-" + }, + { + "node_id": "9a8beffce76dcd9a52962f1be19c603a4bc97287d51b54296eec70942325d4ec", + "node_type": "IAMRole", + "properties": { + "account_id": "000000000000", + "sanitized": true + }, + "provider": "aws", + "provider_id": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", + "region": "-" + }, + { + "node_id": "dd76780b75405f72452650208a1129ca4dc959c43bcea761cbc3686e576d16d0", + "node_type": "AWSService", + "properties": {}, + "provider": "aws", + "provider_id": "lambda.amazonaws.com", + "region": "-" + } + ], + "redaction": { + "raw_live_account_id_committed": false, + "raw_live_role_arn_committed": false, + "raw_tmp_result_committed": false + }, + "scenario_hash": "facefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeed", + "selected_path": { + "attempted_action": "lambda:CreateFunction", + "candidate_function_arn": "arn:aws:lambda:us-east-1:000000000000:function:iamscope-live-passrole-lambda-test-redacted", + "service_principal": "lambda.amazonaws.com", + "source_principal_arn": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource", + "target_role_arn": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole" + } +} diff --git a/tests/test_passrole_lambda_live_binding_fixture.py b/tests/test_passrole_lambda_live_binding_fixture.py new file mode 100644 index 0000000..455d3d6 --- /dev/null +++ b/tests/test_passrole_lambda_live_binding_fixture.py @@ -0,0 +1,136 @@ +from __future__ import annotations + +import json +import re +from pathlib import Path +from typing import Any + +from iamscope.models import Edge, Node, NodeRef +from iamscope.reasoner import FactGraph, PassRoleLambdaReasoner, Verdict + +REPO_ROOT = Path(__file__).resolve().parents[1] +FIXTURE_DIR = REPO_ROOT / "tests" / "fixtures" / "live_binding" / "passrole_lambda_selected_finding" +SCENARIO = FIXTURE_DIR / "scenario.json" +EXPECTED = FIXTURE_DIR / "expected_finding.json" +README = FIXTURE_DIR / "README.md" +ALLOWED_SYNTHETIC_ACCOUNT = "000000000000" +FORBIDDEN_GENERATED_NAMES = { + "result.json", + "terraform.tfstate", + "terraform.tfstate.backup", + ".terraform.lock.hcl", + "terraform.tfvars", + "terraform-outputs.json", +} + + +def _load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text()) + + +def _node_ref(payload: dict[str, Any]) -> NodeRef: + return NodeRef( + provider=payload["provider"], + node_type=payload["node_type"], + provider_id=payload["provider_id"], + region=payload["region"], + ) + + +def _fact_graph_from_scenario(payload: dict[str, Any]) -> FactGraph: + nodes = tuple( + Node( + provider=node["provider"], + node_type=node["node_type"], + provider_id=node["provider_id"], + region=node["region"], + properties=node.get("properties", {}), + ) + for node in payload["nodes"] + ) + edges = tuple( + Edge( + edge_type=edge["edge_type"], + src=_node_ref(edge["src"]), + dst=_node_ref(edge["dst"]), + region=edge["region"], + features=edge.get("features", {}), + ) + for edge in payload["edges"] + ) + return FactGraph( + nodes=nodes, + edges=edges, + constraints=(), + edge_constraints=(), + scenario_hash=payload["scenario_hash"], + edge_budget_exhausted=False, + ) + + +def test_selected_passrole_finding_is_generated_by_existing_reasoner() -> None: + scenario = _load_json(SCENARIO) + expected = _load_json(EXPECTED)["selected_finding"] + + findings = PassRoleLambdaReasoner().run(_fact_graph_from_scenario(scenario)) + + assert len(findings) == 1 + finding = findings[0] + assert finding.finding_id == expected["finding_id"] + assert finding.finding_key == expected["finding_key"] + assert finding.pattern_id == "passrole_lambda" + assert finding.verdict is Verdict.VALIDATED + assert finding.verdict.value == expected["expected_verdict"] + assert finding.severity == expected["severity"] + assert finding.source.provider_id == expected["source_principal_arn"] + assert finding.target.provider_id == expected["target_role_arn"] + assert {check.name: check.state.value for check in finding.required_checks} == expected["required_check_states"] + assert all(check.state.value == "pass" for check in finding.required_checks) + + +def test_fixture_is_passrole_lambda_specific_and_sanitized() -> None: + scenario = _load_json(SCENARIO) + selected = _load_json(EXPECTED)["selected_finding"] + + assert scenario["generation_mode"] == "local_reasoner_fixture" + assert scenario["live_aws_used"] is False + assert scenario["aws_calls_made"] == 0 + assert selected["pattern_id"] == "passrole_lambda" + assert selected["source_principal_arn"].startswith(f"arn:aws:iam::{ALLOWED_SYNTHETIC_ACCOUNT}:") + assert selected["target_role_arn"].startswith(f"arn:aws:iam::{ALLOWED_SYNTHETIC_ACCOUNT}:") + assert any(edge["edge_type"] == "lambda:CreateFunction_permission" for edge in scenario["edges"]) + assert any(edge["edge_type"] == "iam:PassRole_permission" for edge in scenario["edges"]) + assert any(edge["edge_type"] == "sts:AssumeRole_trust" for edge in scenario["edges"]) + + +def test_fixture_contains_no_raw_live_ids_or_generated_artifacts() -> None: + text = "\n".join(path.read_text() for path in FIXTURE_DIR.iterdir() if path.is_file()) + account_ids = set(re.findall(r"\b\d{12}\b", text)) + + assert account_ids <= {ALLOWED_SYNTHETIC_ACCOUNT} + assert " None: + expected = _load_json(EXPECTED) + readme = README.read_text() + + assert expected["binding_status"] == "ready_for_next_checkpoint_comparison" + assert ( + expected["next_slice"] == "Recommended next slice: bind selected IAMScope PassRole finding to live AWS result." + ) + assert expected["non_claims"] == { + "no_live_aws": True, + "no_broad_iamscope_correctness": True, + "no_broad_passrole_correctness": True, + "no_exploitability_proof": True, + "no_downstream_authorization_proof": True, + "no_production_readiness": True, + "no_composite_benchmark_score": True, + "no_pass_fail_benchmark_label": True, + } + assert "does not run live AWS" in readme + assert "does not claim broad IAMScope correctness" in readme From e37c4293c876bd58308beb0d1aed078860c0f4e5 Mon Sep 17 00:00:00 2001 From: InfoSecHack Date: Tue, 2 Jun 2026 19:20:17 -0500 Subject: [PATCH 2/2] Align PassRole live binding fixture with CreateFunction scope --- .../README.md | 2 +- .../expected_finding.json | 11 +++-- .../scenario.json | 43 ++++--------------- ...st_passrole_lambda_live_binding_fixture.py | 25 +++++++++++ 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/tests/fixtures/live_binding/passrole_lambda_selected_finding/README.md b/tests/fixtures/live_binding/passrole_lambda_selected_finding/README.md index 878c69e..e4d77bd 100644 --- a/tests/fixtures/live_binding/passrole_lambda_selected_finding/README.md +++ b/tests/fixtures/live_binding/passrole_lambda_selected_finding/README.md @@ -4,4 +4,4 @@ This sanitized local fixture selects one IAMScope `passrole_lambda` finding/path It is local-only and synthetic/redacted. It does not run live AWS, Terraform, AWS CLI, STS, Lambda APIs, or `iam:PassRole`. It does not commit raw live output, a real AWS account id, or a real role ARN. -The fixture is intended only to provide a stable local IAMScope finding selector for the next checkpoint slice. It does not claim broad IAMScope correctness, broad PassRole correctness, exploitability proof, downstream authorization proof, production readiness, composite benchmark scoring, or pass/fail benchmark labeling. +The selected path represents only service-mediated Lambda `CreateFunction` plus `iam:PassRole` plus Lambda trust. It does not include an admin-equivalent execution role edge. It does not claim Lambda invocation behavior, downstream authorization, or exploitability proof. It does not claim broad IAMScope correctness, broad PassRole correctness, production readiness, composite benchmark scoring, or pass/fail benchmark labeling. diff --git a/tests/fixtures/live_binding/passrole_lambda_selected_finding/expected_finding.json b/tests/fixtures/live_binding/passrole_lambda_selected_finding/expected_finding.json index 4b19160..bb1f340 100644 --- a/tests/fixtures/live_binding/passrole_lambda_selected_finding/expected_finding.json +++ b/tests/fixtures/live_binding/passrole_lambda_selected_finding/expected_finding.json @@ -3,11 +3,13 @@ "fixture_id": "passrole_lambda_selected_live_binding_finding_001", "next_slice": "Recommended next slice: bind selected IAMScope PassRole finding to live AWS result.", "non_claims": { + "no_admin_equivalent_execution_role_claim": true, "no_broad_iamscope_correctness": true, "no_broad_passrole_correctness": true, "no_composite_benchmark_score": true, "no_downstream_authorization_proof": true, "no_exploitability_proof": true, + "no_lambda_invocation_behavior": true, "no_live_aws": true, "no_pass_fail_benchmark_label": true, "no_production_readiness": true @@ -20,10 +22,11 @@ } ], "blockers_observed_count": 0, - "expected_classification": "selected_local_iamscope_finding", + "expected_classification": "selected_local_createfunction_passrole_finding", "expected_verdict": "validated", - "finding_id": "5e08bf418146ae29259115f672b8960bf3b38ce07ec5cd57c5638019e126dc50", + "finding_id": "dc284c673334e54974e229c9ac006684b3e928d0d03936f857fe93068dc74dc8", "finding_key": "e7aa122330b61ce55fdb3cd017139b3c9b8c941fdd95fec676e2c337cde58b1e", + "live_behavior_alignment": "service-mediated CreateFunction plus PassRole plus Lambda trust only", "pattern_id": "passrole_lambda", "pattern_version": "1.0.0", "required_check_states": { @@ -38,9 +41,9 @@ "source_has_passrole_to_target": "pass", "target_trusts_lambda_service": "pass" }, - "severity": "critical", + "severity": "high", "source_principal_arn": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource", "target_role_arn": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", - "title": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource can assume admin-equivalent role arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole via Lambda PassRole chain" + "title": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource can assume role arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole via Lambda PassRole chain" } } diff --git a/tests/fixtures/live_binding/passrole_lambda_selected_finding/scenario.json b/tests/fixtures/live_binding/passrole_lambda_selected_finding/scenario.json index f4361d6..19f81b6 100644 --- a/tests/fixtures/live_binding/passrole_lambda_selected_finding/scenario.json +++ b/tests/fixtures/live_binding/passrole_lambda_selected_finding/scenario.json @@ -75,41 +75,6 @@ "region": "-" } }, - { - "dst": { - "node_type": "IAMRole", - "provider": "aws", - "provider_id": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", - "region": "-" - }, - "edge_id": "cd0dc18ac07b1cb1f9a14e2fc6f7ffa3460aa830155e82be93fff5b97db6311f", - "edge_type": "iam:*_permission", - "features": { - "allow_controls": [ - { - "control_type": "PERMISSION", - "digest": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", - "policy_arn": "arn:aws:iam::000000000000:policy/IAMScopeLiveBindingSanitizedAdminPolicy", - "statement_index": 0, - "summary": "iam:*" - } - ], - "effect": "Allow", - "has_conditions": false, - "is_wildcard_resource": true, - "layer": "permission", - "raw_conditions": {}, - "resource_pattern": "*", - "statement_index": 0 - }, - "region": "-", - "src": { - "node_type": "IAMRole", - "provider": "aws", - "provider_id": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole", - "region": "-" - } - }, { "dst": { "node_type": "IAMRole", @@ -187,9 +152,17 @@ "raw_tmp_result_committed": false }, "scenario_hash": "facefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeedfacefeed", + "scope_boundary": { + "represents_admin_equivalent_execution_role": false, + "represents_downstream_authorization": false, + "represents_exploitability": false, + "represents_lambda_invocation": false, + "represents_service_mediated_create_function": true + }, "selected_path": { "attempted_action": "lambda:CreateFunction", "candidate_function_arn": "arn:aws:lambda:us-east-1:000000000000:function:iamscope-live-passrole-lambda-test-redacted", + "live_behavior_alignment": "service-mediated CreateFunction plus PassRole plus Lambda trust only", "service_principal": "lambda.amazonaws.com", "source_principal_arn": "arn:aws:iam::000000000000:user/IAMScopeLiveBindingSource", "target_role_arn": "arn:aws:iam::000000000000:role/IAMScopeLiveBindingLambdaExecutionRole" diff --git a/tests/test_passrole_lambda_live_binding_fixture.py b/tests/test_passrole_lambda_live_binding_fixture.py index 455d3d6..db1a511 100644 --- a/tests/test_passrole_lambda_live_binding_fixture.py +++ b/tests/test_passrole_lambda_live_binding_fixture.py @@ -82,6 +82,10 @@ def test_selected_passrole_finding_is_generated_by_existing_reasoner() -> None: assert finding.verdict is Verdict.VALIDATED assert finding.verdict.value == expected["expected_verdict"] assert finding.severity == expected["severity"] + assert finding.severity == "high" + assert "admin-equivalent" not in finding.title.lower() + assert "exploit" not in finding.title.lower() + assert "downstream" not in finding.title.lower() assert finding.source.provider_id == expected["source_principal_arn"] assert finding.target.provider_id == expected["target_role_arn"] assert {check.name: check.state.value for check in finding.required_checks} == expected["required_check_states"] @@ -98,9 +102,19 @@ def test_fixture_is_passrole_lambda_specific_and_sanitized() -> None: assert selected["pattern_id"] == "passrole_lambda" assert selected["source_principal_arn"].startswith(f"arn:aws:iam::{ALLOWED_SYNTHETIC_ACCOUNT}:") assert selected["target_role_arn"].startswith(f"arn:aws:iam::{ALLOWED_SYNTHETIC_ACCOUNT}:") + assert selected["expected_classification"] == "selected_local_createfunction_passrole_finding" + assert selected["live_behavior_alignment"] == "service-mediated CreateFunction plus PassRole plus Lambda trust only" + assert scenario["scope_boundary"] == { + "represents_admin_equivalent_execution_role": False, + "represents_downstream_authorization": False, + "represents_exploitability": False, + "represents_lambda_invocation": False, + "represents_service_mediated_create_function": True, + } assert any(edge["edge_type"] == "lambda:CreateFunction_permission" for edge in scenario["edges"]) assert any(edge["edge_type"] == "iam:PassRole_permission" for edge in scenario["edges"]) assert any(edge["edge_type"] == "sts:AssumeRole_trust" for edge in scenario["edges"]) + assert not any(edge["edge_type"] == "iam:*_permission" for edge in scenario["edges"]) def test_fixture_contains_no_raw_live_ids_or_generated_artifacts() -> None: @@ -108,7 +122,14 @@ def test_fixture_contains_no_raw_live_ids_or_generated_artifacts() -> None: account_ids = set(re.findall(r"\b\d{12}\b", text)) assert account_ids <= {ALLOWED_SYNTHETIC_ACCOUNT} + selected = _load_json(EXPECTED)["selected_finding"] + assert " None: ) assert expected["non_claims"] == { "no_live_aws": True, + "no_lambda_invocation_behavior": True, + "no_admin_equivalent_execution_role_claim": True, "no_broad_iamscope_correctness": True, "no_broad_passrole_correctness": True, "no_exploitability_proof": True, @@ -133,4 +156,6 @@ def test_fixture_documents_boundary_and_next_slice() -> None: "no_pass_fail_benchmark_label": True, } assert "does not run live AWS" in readme + assert "does not include an admin-equivalent execution role edge" in readme + assert "does not claim Lambda invocation behavior" in readme assert "does not claim broad IAMScope correctness" in readme