Evaluate a role trust policy against the confused deputy

An evaluator for AWS sts:AssumeRole trust policies matches the principal and action but ignores the Condition block and does not honour an explicit Deny, so a broad principal can assume a role without the external id — the confused deputy. Repair evaluate_trust to enforce conditions and Deny.

Requires Node.js 22 or newer, which runs the tests, and Python 3.12 or newer, which runs your code (the command python3, or the program named in the PYTHON environment variable). No package installation or server needed. To work in a container instead, open the folder with VS Code Dev Containers or GitHub Codespaces: .devcontainer/devcontainer.json uses the Dev Containers Node.js 24 image, pinned by digest.
1. Unzip all files into one folder.
2. In that folder, run: node --test exercise.test.mjs
3. Edit trust_policy.py, then run the same command again.

The starter deliberately fails some tests. A successful repair passes every case, including cases that catch common wrong fixes. The browser runs this same grader with your code in Pyodide (CPython compiled to WebAssembly); locally it runs your code with python3. If you completed the optional first stage in the browser, Download my work adds your input as stage1-input.json and the tests include it. Only run code you wrote or reviewed.

1. Reproduce the confused deputy
Run the starter. A third-party principal is allowed to assume the role even when the request carries no sts:ExternalId, because the Condition block is ignored.

2. Enforce the conditions
For each matching statement, check its Condition: StringEquals requires the context key to be present and exactly equal, and StringEqualsIgnoreCase the same without regard to case; ArnLike requires the whole context value to match the pattern, where * is a wildcard and a dot is just a dot. A missing key means the condition is not met.

3. Match principal and action precisely
Match the whole action name (a "*" wildcard such as sts:* aside, sts:AssumeRole does not cover sts:AssumeRoleWithWebIdentity) and the principal ("*", or a value listed under AWS or Service, compared whole). A wrong action or an untrusted principal is denied.

4. Honour an explicit Deny
A statement whose Effect is Deny and that matches must return "deny", overriding any Allow. Allow only when an Allow statement matches and nothing denies.

SUPPLIED CONTRACT
evaluate_trust(policy, request) returns "allow" or "deny". policy has a Statement (one object or a list); each has Effect ("Allow"/"Deny"), Principal, Action and an optional Condition. request is {"principal": str, "action": str, "context": {…}}. A statement matches when its Action matches the whole request action (support a "*" wildcard, as in sts:*), its Principal matches ("*", or the request principal listed under "AWS"/"Service", compared whole: AWS does not allow a wildcard in part of a principal, so role/* is not a pattern), and every Condition is met. Support StringEquals (the context key must be present and exactly equal — used for sts:ExternalId and aws:SourceAccount), StringEqualsIgnoreCase (the same, ignoring case) and ArnLike (the whole value must match a pattern in which * is the wildcard and every other character, dots included, is literal — used for aws:SourceArn). A missing context key fails the condition, and so does an operator the evaluator does not implement. Any matching Deny returns "deny"; otherwise allow only if some Allow statement matches.

REFERENCE
reference.py contains an explained solution: 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.

SCOPE
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.

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?

SOURCE
https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html
