fix(aws): scope shared IAM role to functions without dedicated per-function roles#13704
fix(aws): scope shared IAM role to functions without dedicated per-function roles#13704czubocha wants to merge 2 commits into
Conversation
… roles Dedicated per-function IAM roles were missing several permissions that the shared execution role grants for the same configuration, leaving functions with dedicated roles under-permissioned: - Kinesis enhanced-fan-out consumers: consumer-variant stream actions (DescribeStreamSummary, ListShards) and kinesis:SubscribeToShard on the consumer ARN (both consumer: true and explicit consumer ARN forms) - Stream onFailure destinations: sns:Publish / sqs:ListQueues+SendMessage on the destination ARN - kms:Decrypt when kmsKeyArn is configured (function- or provider-level) Also extracts stream-name resolution from the stream event compiler into a shared util (pure refactor) so per-function role generation resolves consumer logical IDs identically.
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (27)
🚧 Files skipped from review as they are similar to previous changes (20)
📝 WalkthroughWalkthroughDedicated per-function IAM role detection now controls whether generated permissions are added to the shared Lambda execution role. Dedicated roles receive stream, KMS, VPC, and destination permissions, with expanded coverage for role creation, policy isolation, and IAM policy validity. ChangesIAM role isolation
Estimated code review effort: 5 (Critical) | ~90 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/serverless/lib/plugins/aws/package/compile/events/schedule.js (1)
384-404: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winFully isolate the shared role when every Scheduler function has a dedicated role.
The invoke policy is omitted, but the shared role still gains the Scheduler service principal.
packages/serverless/lib/plugins/aws/package/compile/events/schedule.js#L384-L404: gate the trust-policy mutation onschedulerStatement.Resource.length.packages/serverless/test/unit/lib/plugins/aws/package/compile/events/schedule.test.js#L607-L643: assert thatscheduler.amazonaws.comis absent when all Scheduler targets use dedicated roles.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/serverless/lib/plugins/aws/package/compile/events/schedule.js` around lines 384 - 404, Gate the shared role trust-policy mutation in the schedule compilation flow on schedulerStatement.Resource.length, so scheduler.amazonaws.com is added only when the shared role has invoke resources. Update packages/serverless/test/unit/lib/plugins/aws/package/compile/events/schedule.test.js lines 607-643 to assert that scheduler.amazonaws.com is absent when every Scheduler target uses a dedicated role.
🧹 Nitpick comments (3)
packages/serverless/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.js (1)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace lodash with native JavaScript.
Use optional chaining and native object checks instead of
_.getand_.isObject.As per coding guidelines, “Prefer native JavaScript over lodash.”
Also applies to: 31-50
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/serverless/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.js` at line 1, Replace the lodash import and its usages in the dedicated-per-function-role logic with native JavaScript: use optional chaining for nested property access instead of _.get, and native object checks instead of _.isObject. Preserve the existing behavior across the logic spanning the referenced lines, then remove the unused lodash import.Source: Coding guidelines
packages/serverless/lib/plugins/aws/package/lib/merge-iam-templates.js (1)
225-267: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the duplicated disableLogs-Deny construction into a helper.
The Deny-statement building logic (map function names → log-group ARNs →
mergeStatements) here duplicates the existing block a few lines above (thehasOneOrMoreCanonicallyNamedFunctionsbranch). A future edit to one copy (e.g. Action list or ARN format) risks silently missing the other.♻️ Proposed extraction
+ const emitDisableLogsDeny = (functionNames) => { + const arnOfDisableLogGroups = functionNames.map((fnName) => ({ + 'Fn::Sub': + 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${this.provider.naming.getLogGroupName(fnName)}:*`, + })) + this.mergeStatements([ + { + Effect: 'Deny', + Action: 'logs:PutLogEvents', + Resource: arnOfDisableLogGroups, + }, + ]) + } + if (hasOneOrMoreCanonicallyNamedFunctions) { ... if (disableLogsCanonicallyNamedFunctions.length > 0) { - // add deny rule for disabled logs function - const arnOfDisableLogGroups = disableLogsCanonicallyNamedFunctions.map(...) - this.mergeStatements([...]) + emitDisableLogsDeny(disableLogsCanonicallyNamedFunctions) } } ... if (skippedDedicatedLogContributions && ...) { ... if (disableLogsCanonicallyNamedFunctions.length > 0) { - const arnOfDisableLogGroups = disableLogsCanonicallyNamedFunctions.map(...) - this.mergeStatements([...]) + emitDisableLogsDeny(disableLogsCanonicallyNamedFunctions) } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/serverless/lib/plugins/aws/package/lib/merge-iam-templates.js` around lines 225 - 267, Extract the duplicated disableLogs Deny construction into a shared helper near the existing IAM merge logic, including mapping function names to log-group ARNs and calling mergeStatements with the Deny statement. Replace both the hasOneOrMoreCanonicallyNamedFunctions branch and the skippedDedicatedLogContributions branch with calls to that helper, preserving their existing conditions and behavior.packages/serverless/test/unit/lib/plugins/aws/package/lib/per-function-iam-global-role.test.js (1)
59-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated real-pipeline test harness across two new sibling files. Both files independently define byte-identical
buildServiceandrunEventCompilershelpers (construct realServerless+AwsProvider, wire functions, seed Lambda resources, run selected event compilers). One root cause: no shared test-utility module for this harness, risking future drift between the two copies.
packages/serverless/test/unit/lib/plugins/aws/package/lib/per-function-iam-global-role.test.js#L59-L125: extractbuildService/runEventCompilersinto a shared test-helper module (e.g. alongside the existing test-lib utilities) and import it here.packages/serverless/test/unit/lib/plugins/aws/package/lib/iam-empty-collection-invariant.test.js#L72-L133: import the same shared helper module instead of redefiningbuildService/runEventCompilerslocally.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/serverless/test/unit/lib/plugins/aws/package/lib/per-function-iam-global-role.test.js` around lines 59 - 125, Extract the shared buildService and runEventCompilers harness into a common test-helper module, preserving their current behavior and dependencies. In packages/serverless/test/unit/lib/plugins/aws/package/lib/per-function-iam-global-role.test.js lines 59-125, remove the local definitions and import the helpers; do the same in packages/serverless/test/unit/lib/plugins/aws/package/lib/iam-empty-collection-invariant.test.js lines 72-133, using the same shared module in both files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/serverless/lib/plugins/aws/package/lib/roles-per-function.js`:
- Around line 445-463: Update the validation around createdRoleNames and
usesDedicatedPerFunctionRole to compare predicted and created dedicated-role
names in both directions, failing packaging whenever either set contains an
entry absent from the other. Preserve the existing error behavior for
prediction/creation mismatches and add the inverse regression test covering a
role created without a corresponding prediction.
---
Outside diff comments:
In `@packages/serverless/lib/plugins/aws/package/compile/events/schedule.js`:
- Around line 384-404: Gate the shared role trust-policy mutation in the
schedule compilation flow on schedulerStatement.Resource.length, so
scheduler.amazonaws.com is added only when the shared role has invoke resources.
Update
packages/serverless/test/unit/lib/plugins/aws/package/compile/events/schedule.test.js
lines 607-643 to assert that scheduler.amazonaws.com is absent when every
Scheduler target uses a dedicated role.
---
Nitpick comments:
In `@packages/serverless/lib/plugins/aws/package/lib/merge-iam-templates.js`:
- Around line 225-267: Extract the duplicated disableLogs Deny construction into
a shared helper near the existing IAM merge logic, including mapping function
names to log-group ARNs and calling mergeStatements with the Deny statement.
Replace both the hasOneOrMoreCanonicallyNamedFunctions branch and the
skippedDedicatedLogContributions branch with calls to that helper, preserving
their existing conditions and behavior.
In
`@packages/serverless/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.js`:
- Line 1: Replace the lodash import and its usages in the
dedicated-per-function-role logic with native JavaScript: use optional chaining
for nested property access instead of _.get, and native object checks instead of
_.isObject. Preserve the existing behavior across the logic spanning the
referenced lines, then remove the unused lodash import.
In
`@packages/serverless/test/unit/lib/plugins/aws/package/lib/per-function-iam-global-role.test.js`:
- Around line 59-125: Extract the shared buildService and runEventCompilers
harness into a common test-helper module, preserving their current behavior and
dependencies. In
packages/serverless/test/unit/lib/plugins/aws/package/lib/per-function-iam-global-role.test.js
lines 59-125, remove the local definitions and import the helpers; do the same
in
packages/serverless/test/unit/lib/plugins/aws/package/lib/iam-empty-collection-invariant.test.js
lines 72-133, using the same shared module in both files.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0b6201cb-3360-42c0-9b6f-a709da6206ae
⛔ Files ignored due to path filters (1)
packages/serverless/test/unit/lib/plugins/aws/package/lib/__snapshots__/per-function-iam-global-role.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (29)
docs/sf/providers/aws/guide/iam.mdpackages/serverless/lib/plugins/aws/package/compile/events/activemq.jspackages/serverless/lib/plugins/aws/package/compile/events/kafka.jspackages/serverless/lib/plugins/aws/package/compile/events/msk/index.jspackages/serverless/lib/plugins/aws/package/compile/events/rabbitmq.jspackages/serverless/lib/plugins/aws/package/compile/events/schedule.jspackages/serverless/lib/plugins/aws/package/compile/events/sqs.jspackages/serverless/lib/plugins/aws/package/compile/events/stream.jspackages/serverless/lib/plugins/aws/package/compile/functions.jspackages/serverless/lib/plugins/aws/package/lib/merge-iam-templates.jspackages/serverless/lib/plugins/aws/package/lib/roles-per-function-permissions.jspackages/serverless/lib/plugins/aws/package/lib/roles-per-function.jspackages/serverless/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.jspackages/serverless/lib/plugins/aws/utils/get-stream-name-from-arn.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/activemq.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/kafka.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/msk.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/rabbitmq.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/schedule.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/sqs.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/stream.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/functions.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/iam-empty-collection-invariant.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/merge-iam-templates.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/per-function-iam-global-role.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function-permissions.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.lockstep.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.test.js
c554c73 to
38a9e4c
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function.test.js (1)
16-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShared test-fixture logic duplicated across two suites.
FakeServerlessError,buildContext, and theisExistingRoleProvidedmock are copy-pasted between both files, which both exercise the same dedicated-role-creation subsystem; keeping two independent copies risks the mocks drifting out of sync and masking real regressions in one suite but not the other.
packages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function.test.js#L16-L93: extractFakeServerlessError,buildContext, andisExistingRoleProvidedinto a shared test-helper module and import it here.packages/serverless/test/unit/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.lockstep.test.js#L34-L111: import the same shared helper (extending it withmergeIamTemplatesMixin/utils.readFileSyncwiring specific to this suite) instead of re-declaring the fixture.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function.test.js` around lines 16 - 93, Extract FakeServerlessError, buildContext, and the isExistingRoleProvided mock into one shared test-helper module. In packages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function.test.js lines 16-93, import and use that helper instead of the duplicated fixture. In packages/serverless/test/unit/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.lockstep.test.js lines 34-111, import the same helper and retain only the suite-specific mergeIamTemplatesMixin and utils.readFileSync wiring.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@packages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function.test.js`:
- Around line 16-93: Extract FakeServerlessError, buildContext, and the
isExistingRoleProvided mock into one shared test-helper module. In
packages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function.test.js
lines 16-93, import and use that helper instead of the duplicated fixture. In
packages/serverless/test/unit/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.lockstep.test.js
lines 34-111, import the same helper and retain only the suite-specific
mergeIamTemplatesMixin and utils.readFileSync wiring.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ad9c0c81-dcff-42b5-80c2-5b1ff606de10
⛔ Files ignored due to path filters (1)
packages/serverless/test/unit/lib/plugins/aws/package/lib/__snapshots__/per-function-iam-global-role.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (27)
docs/sf/providers/aws/guide/iam.mdpackages/serverless/lib/plugins/aws/package/compile/events/activemq.jspackages/serverless/lib/plugins/aws/package/compile/events/kafka.jspackages/serverless/lib/plugins/aws/package/compile/events/msk/index.jspackages/serverless/lib/plugins/aws/package/compile/events/rabbitmq.jspackages/serverless/lib/plugins/aws/package/compile/events/schedule.jspackages/serverless/lib/plugins/aws/package/compile/events/sqs.jspackages/serverless/lib/plugins/aws/package/compile/events/stream.jspackages/serverless/lib/plugins/aws/package/compile/functions.jspackages/serverless/lib/plugins/aws/package/lib/merge-iam-templates.jspackages/serverless/lib/plugins/aws/package/lib/roles-per-function.jspackages/serverless/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/activemq.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/kafka.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/msk.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/rabbitmq.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/schedule.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/sqs.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/events/stream.test.jspackages/serverless/test/unit/lib/plugins/aws/package/compile/functions.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/iam-empty-collection-invariant.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/merge-iam-templates.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/per-function-iam-global-role.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function-permissions.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.lockstep.test.jspackages/serverless/test/unit/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.test.js
🚧 Files skipped from review as they are similar to previous changes (23)
- packages/serverless/test/unit/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.test.js
- docs/sf/providers/aws/guide/iam.md
- packages/serverless/test/unit/lib/plugins/aws/package/compile/events/rabbitmq.test.js
- packages/serverless/test/unit/lib/plugins/aws/package/compile/events/stream.test.js
- packages/serverless/test/unit/lib/plugins/aws/package/lib/roles-per-function-permissions.test.js
- packages/serverless/lib/plugins/aws/package/compile/events/stream.js
- packages/serverless/lib/plugins/aws/package/compile/events/sqs.js
- packages/serverless/lib/plugins/aws/package/lib/roles-per-function.js
- packages/serverless/lib/plugins/aws/package/compile/events/kafka.js
- packages/serverless/lib/plugins/aws/package/compile/events/activemq.js
- packages/serverless/test/unit/lib/plugins/aws/package/compile/events/sqs.test.js
- packages/serverless/lib/plugins/aws/package/compile/events/msk/index.js
- packages/serverless/lib/plugins/aws/package/lib/uses-dedicated-per-function-role.js
- packages/serverless/test/unit/lib/plugins/aws/package/compile/events/kafka.test.js
- packages/serverless/lib/plugins/aws/package/lib/merge-iam-templates.js
- packages/serverless/lib/plugins/aws/package/compile/events/rabbitmq.js
- packages/serverless/test/unit/lib/plugins/aws/package/lib/per-function-iam-global-role.test.js
- packages/serverless/test/unit/lib/plugins/aws/package/compile/functions.test.js
- packages/serverless/lib/plugins/aws/package/compile/functions.js
- packages/serverless/test/unit/lib/plugins/aws/package/compile/events/msk.test.js
- packages/serverless/test/unit/lib/plugins/aws/package/compile/events/activemq.test.js
- packages/serverless/test/unit/lib/plugins/aws/package/lib/merge-iam-templates.test.js
- packages/serverless/test/unit/lib/plugins/aws/package/lib/iam-empty-collection-invariant.test.js
…nction roles
In per-function IAM 'selected functions' mode (some functions define
functions.<name>.iam.role.*, the rest use the shared execution role), the
shared role's inline policy kept accumulating auto-generated permissions
for every function in the service - including functions that assume their
own dedicated role. Those grants were unreachable dead weight that grew
with function count and pushed services past the 10,240-byte IAM inline
policy limit ('Maximum policy size exceeded'), so moving functions to
dedicated roles bought no relief.
A new predicate (uses-dedicated-per-function-role.js) predicts at compile
time whether a function will receive a dedicated role, staying in lockstep
with the role creation logic in roles-per-function.js. Every
per-function-growing writer to the shared role consults it and skips
dedicated functions: stream/SQS/scheduler/MSK/Kafka/ActiveMQ/RabbitMQ event
compilers, onError/kms/EFS/destination grants in the functions compiler,
and custom log-group grants in merge-iam-templates (with a wildcard
fallback so the shared role's log statements can never end up with an
empty Resource list; the fallback also emits the disableLogs deny
statements so log-disabled functions stay disabled).
Safety:
- The predicate returns false (behavior unchanged, byte-identical
templates) when the external serverless-iam-roles-per-function plugin is
installed, for functions using the role: property, for existing-role
references, for provider.role configs, and for services without
per-function IAM.
- A divergence guard at the end of createRolesPerFunction fails packaging
loudly if a function predicted to get a dedicated role did not receive
one.
- Destination grants no-op cleanly when no shared role exists instead of
erroring while compiling event invoke config.
- Composed regression tests pin shared-resource retention, policy-size
monotonicity, byte-identity for unaffected configs, scheduler RoleArn
wiring, and perFunction-mode output.
Functions remaining on the shared role keep exactly the permissions they
had; grants removed from the shared role exist on the dedicated roles
(reaching parity in the previous commit).
38a9e4c to
cea4644
Compare
Summary
onError/kmsKeyArn/EFS/destination grants, custom log-group grants)kinesis:SubscribeToShard, streamonFailureSNS/SQS destination grants, andkms:DecryptforkmsKeyArn(these were broken for dedicated-role functions before this PR)uses-dedicated-per-function-role.js) kept in lockstep with role creation, plus a divergence guard that fails packaging loudly if a function predicted to receive a dedicated role did not get oneprovider.iam.role.mode: perFunction, services using the externalserverless-iam-roles-per-functionplugin, functions with therole:property, andprovider.roleconfigsRoot cause
Event compilers and the functions compiler write per-function permissions into the shared
IamRoleLambdaExecutionrole duringpackage:compileEvents/package:compileFunctions, while dedicated per-function roles are created later (before finalize). Nothing skipped functions destined for a dedicated role, so their grants remained on the shared role as unreachable duplicates. The shared role's inline policy grew with every function and event source, eventually exceeding the 10,240-byte IAM inline policy limit ("Maximum policy size exceeded") — and moving functions to dedicated roles provided zero relief, defeating the feature's documented purpose.Test plan
role:property, existing-role reference,provider.role,perFunctionmode, statements/legacy/managedPolicies shapes)onFailure, and KMS grants mirroring the shared-role writers exactlyTarget.RoleArnpinned to the dedicated role,perFunction-mode outputdisableLogsdeny statements verified emitted on the wildcard-fallback path; destination grants no-op cleanly when no shared role existsSummary by CodeRabbit