Skip to content

fix(aws): pin the customer role to read-only actions, document auditing Maple in CloudTrail - #2528

Merged
Makisuo merged 1 commit into
mainfrom
fix/aws-role-read-only-guard
Nov 12, 2026
Merged

Makisuo merged 1 commit into
mainfrom
fix/aws-role-read-only-guard

Conversation

@Makisuo

@Makisuo Makisuo commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

The AWS role a customer deploys is meant to be read-only, and nothing enforced that. Adding a module action like iam:PassRole or attaching a broad AWS managed policy would have shipped silently, since AWS_MODULE_SPECS generates the CloudFormation and the Terraform.

Changes

  • aws/permissions.test.ts builds the setup plan with every module on and asserts:
    • every granted action uses a read verb (Get, List, Describe, Search, BatchGet), and there are no wildcards
    • managed policies are only AWS's read-only ones (ViewOnlyAccess, SecurityAudit, *ReadOnly*)
    • the trust policy accepts only AssumeRoleWithWebIdentity from Maple's provider, for this connection's aud and sub
  • The AWS docs say what the role cannot read, and add an "Audit what Maple does" section: the AssumeRoleWithWebIdentity CloudTrail event, the maple-<id> session name, the expected cadence, and how to cut Maple off.

Verification

  • The new test passes. Adding iam:PassRole to the cost module makes it fail with expected [ 'iam:PassRole' ] to deeply equal [].
  • packages/backend typecheck and the CI effect lint config both pass on the changed files.

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

…ng Maple in CloudTrail

A test fails if any module, or the role's self-read, grants a non-read action, attaches an AWS
managed policy that is not read-only, or widens the trust past one connection's subject. The docs
say what the role cannot read and how to audit and cut off Maple's access.
@maple-review-bot

maple-review-bot Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 8/10 · likely safe to merge. Held at 8 because a warning is open.
1 warning −1 · 1 note · tests covered · risk low

Adds a test pinning the generated AWS setup plan to read-only actions, read-only managed policies and a single-connection trust policy, plus a docs section on auditing Maple in CloudTrail. Sound guards, but the trust-policy check inspects only StringEquals.

  • permissions.test.ts pins the role's actions, managed policies and trust policy
  • AWS docs gain an "Audit what Maple does" CloudTrail section

Findings

🟠 Warning · F1 · Trust assertion ignores condition operators other than StringEquals

tests · packages/backend/src/services/integrations/aws/permissions.test.ts:47-50

expect(statement.Condition?.StringEquals).toEqual(...) reads one operator, so a StringLike clause added beside it — {"api.maple.test/oidc/aws:sub": "aws-connection:*"} — lets another connection's token assume this role and the test still passes. IamStatement.Condition (packages/domain/src/aws.ts:204) admits any operator map.

			expect(statement.Condition).toEqual({
				StringEquals: {
					"api.maple.test/oidc/aws:aud": "sts.amazonaws.com",
					"api.maple.test/oidc/aws:sub": awsOidcSubject(connectionId),
				},
			})
🔵 Note · F2 · granted may be empty and the read-only check still passes

tests · packages/backend/src/services/integrations/aws/permissions.test.ts:26

expect(filter(granted, …)).toEqual([]) holds for an empty granted: a spec whose actions list is emptied, or a plan that stops emitting module statements, keeps the suite green while the role reads nothing. The test never asserts it saw any action.

		expect(granted.length).toBeGreaterThan(0)
		expect(Arr.filter(granted, (action) => !READ_VERB.test(action))).toEqual([])
🤖 Prompt to fix all 2 findings with an AI agent
Findings from an automated review of commit 571dd2e48f54d7d5bb4b7e39a2c04f6da50a8331. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Warning · tests · packages/backend/src/services/integrations/aws/permissions.test.ts:47-50
Trust assertion ignores condition operators other than `StringEquals`
`expect(statement.Condition?.StringEquals).toEqual(...)` reads one operator, so a `StringLike` clause added beside it — `{"api.maple.test/oidc/aws:sub": "aws-connection:*"}` — lets another connection's token assume this role and the test still passes. `IamStatement.Condition` (`packages/domain/src/aws.ts:204`) admits any operator map.
Replace those lines with:
			expect(statement.Condition).toEqual({
				StringEquals: {
					"api.maple.test/oidc/aws:aud": "sts.amazonaws.com",
					"api.maple.test/oidc/aws:sub": awsOidcSubject(connectionId),
				},
			})

---

F2 · Note · tests · packages/backend/src/services/integrations/aws/permissions.test.ts:26
`granted` may be empty and the read-only check still passes
`expect(filter(granted, …)).toEqual([])` holds for an empty `granted`: a spec whose `actions` list is emptied, or a plan that stops emitting module statements, keeps the suite green while the role reads nothing. The test never asserts it saw any action.
Replace those lines with:
		expect(granted.length).toBeGreaterThan(0)
		expect(Arr.filter(granted, (action) => !READ_VERB.test(action))).toEqual([])

571dd2e · What was checked, coverage and telemetry details are on the Maple / review check. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Explain Change →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@Makisuo
Makisuo merged commit 94afb57 into main Nov 12, 2026
36 of 37 checks passed
@Makisuo
Makisuo deleted the fix/aws-role-read-only-guard branch November 12, 2026 13:44

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 inline note from Maple's review. The score and summary are in the review comment above.

Comment on lines +47 to +50
expect(statement.Condition?.StringEquals).toEqual({
"api.maple.test/oidc/aws:aud": "sts.amazonaws.com",
"api.maple.test/oidc/aws:sub": awsOidcSubject(connectionId),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Trust assertion ignores condition operators other than StringEquals

F1 · Warning · tests

expect(statement.Condition?.StringEquals).toEqual(...) reads one operator, so a StringLike clause added beside it — {"api.maple.test/oidc/aws:sub": "aws-connection:*"} — lets another connection's token assume this role and the test still passes. IamStatement.Condition (packages/domain/src/aws.ts:204) admits any operator map.

Assert the whole `Condition` object so any added operator fails.
Suggested change
expect(statement.Condition?.StringEquals).toEqual({
"api.maple.test/oidc/aws:aud": "sts.amazonaws.com",
"api.maple.test/oidc/aws:sub": awsOidcSubject(connectionId),
})
expect(statement.Condition).toEqual({
StringEquals: {
"api.maple.test/oidc/aws:aud": "sts.amazonaws.com",
"api.maple.test/oidc/aws:sub": awsOidcSubject(connectionId),
},
})
🤖 Prompt to fix with an AI agent
In `packages/backend/src/services/integrations/aws/permissions.test.ts:47-50`: Trust assertion ignores condition operators other than `StringEquals`.

`expect(statement.Condition?.StringEquals).toEqual(...)` reads one operator, so a `StringLike` clause added beside it — `{"api.maple.test/oidc/aws:sub": "aws-connection:*"}` — lets another connection's token assume this role and the test still passes. `IamStatement.Condition` (`packages/domain/src/aws.ts:204`) admits any operator map.

Replace those lines with:

			expect(statement.Condition).toEqual({
				StringEquals: {
					"api.maple.test/oidc/aws:aud": "sts.amazonaws.com",
					"api.maple.test/oidc/aws:sub": awsOidcSubject(connectionId),
				},
			})

Verify the problem exists at that location before changing it, and keep the fix to those lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant