Get unstuck

Hint 1: The condition is the control

The external id and source keys live in the Condition block. Check each operator: StringEquals needs the context key present and equal; a missing key must fail, not pass.

Hint 2: ArnLike is a pattern, and Deny wins

aws:SourceArn uses ArnLike, so turn its * into a wildcard match rather than an equality test. And a matching Deny statement must override any Allow.

Reveal the reference patch and explanation
Fixedtrust_policy.pyPython
import re


def _as_list(value):
    return value if isinstance(value, list) else [value]


def _wildcard(pattern, value):
    parts = [re.escape(p) for p in str(pattern).split("*")]
    return re.fullmatch(".*".join(parts), str(value)) is not None


def _action_matches(spec, action):
    return any(_wildcard(a, action) for a in _as_list(spec))


def _principal_matches(spec, principal):
    if spec == "*":
        return True
    if isinstance(spec, dict):
        values = []
        for item in spec.values():
            values += _as_list(item)
        return principal in values or "*" in values
    return False


def _conditions_met(condition, context):
    for operator, mapping in (condition or {}).items():
        for key, expected in mapping.items():
            if key not in context:
                return False
            value = context[key]
            options = _as_list(expected)
            if operator in ("StringEquals", "StringEqualsIgnoreCase"):
                fold = str.lower if operator.endswith("IgnoreCase") else str
                if fold(str(value)) not in [fold(str(opt)) for opt in options]:
                    return False
            elif operator in ("ArnLike", "ArnEquals", "StringLike"):
                if not any(_wildcard(opt, value) for opt in options):
                    return False
            else:
                return False
    return True


def evaluate_trust(policy, request):
    action = request.get("action")
    principal = request.get("principal")
    context = request.get("context", {})
    allowed = False
    for statement in _as_list(policy.get("Statement", [])):
        if not _action_matches(statement.get("Action", []), action):
            continue
        if not _principal_matches(statement.get("Principal"), principal):
            continue
        if not _conditions_met(statement.get("Condition", {}), context):
            continue
        if statement.get("Effect") == "Deny":
            return "deny"
        if statement.get("Effect") == "Allow":
            allowed = True
    return "allow" if allowed else "deny"

The evaluator matches a statement only when its whole action matches (with a "*" wildcard), its principal matches ("*", or the request principal under AWS/Service, compared whole, so role/* names no role), and every condition is met. StringEquals on sts:ExternalId and aws:SourceAccount requires the context key to be present and exactly equal, so a missing external id, a wrong one and a value that merely contains the expected id are all denied — this is the confused-deputy control. ArnLike on aws:SourceArn matches a wildcard pattern, so a trail ARN in the trusted account is allowed while one in another account, or a bucket whose name only resembles logs.example.com, is denied; StringEqualsIgnoreCase accepts Payments for payments. A wrong action or an untrusted principal is denied, and any matching Deny statement overrides an Allow. AWS applies the real policy language; this evaluator is a teaching model of the parts that stop the confused deputy.

Compare it with your own fix, then rerun the tests. The editor is not changed when you open this explanation.

Inspect the actual test harness

The browser and the lab download run the same cases, including cases that catch common wrong fixes, such as comparing with one fixture value instead of the real rule. The download includes the complete test adapter and cryptographic fixtures.

Download the grader, python/cases.mjs
How the browser runs your Python

Your code runs in CPython 3.14.2 compiled to WebAssembly (Pyodide 314.0.7), in a Web Worker of its own. The test cases and the pass or fail verdict run in a second worker, which only sends your functions their inputs and reads back the values they return, so changing built-ins or the harness inside Python cannot change the result. All the cases together have a 8-second limit.

The first run downloads about 6.3 MB from this site, which your browser then keeps; later runs start in a second or two. Other pages never load it.

Available: the Python standard library, including sqlite3, ipaddress, pathlib, os.path and urllib.parse, and print(), whose output appears with your results.

Not available: network access, subprocesses and threads, and other packages from PyPI. The lab download runs the same cases with your local python3 (3.12 or newer).

Use the lesson in your own system

This is a model of AWS trust-policy evaluation, not the real engine: it covers Allow/Deny, Principal, a "*" action wildcard, and the StringEquals, StringEqualsIgnoreCase and ArnLike operators. Its ArnLike supports only the * wildcard and lets it span colons; AWS also supports ? and checks the six colon-separated ARN components separately. Real policies add many operators (…IfExists, Bool, Null, Date), case rules, policy variables, NotPrincipal/NotAction, account-root expansion, and session policies and SCP/RCP layering. AWS recommends sts:ExternalId for third-party cross-account trust and aws:SourceArn or aws:SourceAccount (or the organization keys) wherever an AWS service principal is trusted; verify effective access with IAM Access Analyzer and the policy simulator.

Your next task: Which of your role trust policies name a third party or an AWS service, and does each one pin the request with sts:ExternalId, or with aws:SourceArn and aws:SourceAccount, so the role cannot be assumed on another tenant’s behalf?

AWS IAM: the confused deputy problem (ExternalId, aws:SourceArn, aws:SourceAccount)

Related guide: Review cloud permissions and role trust together