Skip to content

Automate IAM User Role Policies#3996

Open
rudransh-shrivastava wants to merge 16 commits intoOWASP:feature/nest-zappa-migrationfrom
rudransh-shrivastava:feature/iam-policy-automation
Open

Automate IAM User Role Policies#3996
rudransh-shrivastava wants to merge 16 commits intoOWASP:feature/nest-zappa-migrationfrom
rudransh-shrivastava:feature/iam-policy-automation

Conversation

@rudransh-shrivastava
Copy link
Collaborator

@rudransh-shrivastava rudransh-shrivastava commented Feb 18, 2026

Proposed change

Resolves #3976

Manual Steps

Please read the entire description before starting. Thank you.

  1. Migrate State
    Note: The state S3 buckets were renamed to follow the nest-${environment}-terraform-state-<hash> pattern.
    Please backup the staging state BEFORE applying any changes:
terraform state pull > terraform.tfstate.backup

An easier alternative is to completely destroy staging/ and then backend/ and starting from scratch.

  1. Create IAM Users
  • Create nest-backend user with the policies defined in infrastructure/backend/README.md.
    Apply backend/ changes

  • Create nest-bootstrap user with the policies defined in infrastructure/bootstrap/README.md.
    Apply the changes (bootstrap) to create a role nest-staging user can assume.

  • Create nest-staging user with the policies defined in infrastructure/README.md.
    Generate a secret ID / UUID and use it as external_id while assuming above role to use the role locally.

For GitHub actions: The same ID needs to be set as AWS_ROLE_EXTERNAL_ID GitHub secret and in infrastructure/bootstrap/terraform.tfvars.

Restore the state (if required) and apply the changes. Terraform may complain that it can't access the old buckets (nest-fixtures*, nest-zappa-deployments*, etc). You may temporarily add these bucket's ARNs to bootstrap/main.tf and apply the changes to delete these buckets properly (starting from scratch prevents this step).

  1. Edit GitHub secrets

Added GitHub secrets:

  • BOOTSTRAP_AWS_ACCESS_KEY_ID
  • BOOTSTRAP_AWS_SECRET_ACCESS_KEY
  • BOOTSTRAP_TF_STATE_BUCKET_NAME
  • BOOTSTRAP_TF_STATE_DYNAMODB_TABLE_NAME
  • AWS_ROLE_EXTERNAL_ID - previously generated ID/UUID.

Removed GitHub secrets:

  • ZAPPA_LAMBDA_FUNCTION_NAME

Following GitHub secrets need to be updated:

  • TF_STATE_DYNAMODB_TABLE_NAME - table is renamed
  • TF_STATE_BUCKET_NAME - bucket is renamed

Checklist

  • Required: I followed the contributing workflow
  • Required: I verified that my code works as intended and resolves the issue as described
  • Required: I ran make check-test locally: all warnings addressed, tests passed
  • I used AI for code, documentation, tests, or communication related to this PR

@rudransh-shrivastava rudransh-shrivastava linked an issue Feb 18, 2026 that may be closed by this pull request
2 tasks
@github-actions github-actions bot added docs Improvements or additions to documentation backend ci labels Feb 18, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a bootstrap Terraform workspace and IAM roles; refactors backend state into per-environment resources; renames staging project identifiers to "nest"/"nest-staging"; updates CI/CD with a bootstrap job and expanded AWS role-assume parameters for staging flows.

Changes

Cohort / File(s) Summary
CI/CD workflow
.github/workflows/run-ci-cd.yaml
Adds bootstrap-staging-nest job; makes plan-staging-nest depend on bootstrap; extends role-assume parameters (role-duration-seconds, role-external-id, role-session-name, role-skip-session-tagging, role-to-assume) across staging jobs; replaces Lambda name with nest-staging; adjusts staging cache/image-report steps.
Bootstrap module (new)
infrastructure/bootstrap/...
infrastructure/bootstrap/main.tf, infrastructure/bootstrap/backend.tf, infrastructure/bootstrap/providers.tf, infrastructure/bootstrap/variables.tf, infrastructure/bootstrap/outputs.tf, infrastructure/bootstrap/.terraform.lock.hcl, infrastructure/bootstrap/terraform.tfbackend.example, infrastructure/bootstrap/terraform.tfvars.example, infrastructure/bootstrap/README.md
Introduces bootstrap Terraform: provider pinning, per-environment IAM roles/policies, outputs for role ARNs, backend example and lockfile, variables, and README.
Backend refactor
infrastructure/backend/main.tf, infrastructure/backend/outputs.tf, infrastructure/backend/variables.tf, infrastructure/backend/README.md
Refactors remote-state to per-environment resources using for_each over state_environments; converts S3/DynamoDB/policies/versioning/encryption/public-access to environment-scoped resources; updates outputs to maps (state_bucket_names, dynamodb_table_names).
Staging infra docs/config
infrastructure/staging/main.tf, infrastructure/staging/terraform.tfbackend.example, infrastructure/staging/README.md
Updates staging backend/DynamoDB lock table name to *-staging; adjusts fixture/zappa bucket naming; adds staging IAM permission examples and README for nest-staging.
Zappa / repo metadata
backend/zappa_settings.template.json, .gitignore, cspell/custom-dict.txt, infrastructure/README.md
Renames Zappa staging project from nest-backendnest; narrows .gitignore pattern to backend/*nest-*.tar.gz/.zip; adds replicationgroup and subgrp to cspell; expands infrastructure README with bootstrap/setup instructions and nest-staging references.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

deploy

Suggested reviewers

  • kasya
  • arkid15r
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Automate IAM User Role Policies' directly summarizes the main change: automating IAM user role provisioning and policy definitions across bootstrap, backend, and staging contexts.
Linked Issues check ✅ Passed The PR successfully addresses issue #3976 by automating IAM user role provisioning with policy definitions in documentation, updating CI/CD workflows with new secrets, and implementing minimal permissions for nest-staging to access remote Terraform state.
Out of Scope Changes check ✅ Passed All changes are scoped to IAM automation: infrastructure bootstrap/backend/staging configurations, CI/CD workflow updates, documentation for policies, and minor naming conventions. No extraneous modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description clearly describes the changeset, including proposed changes, manual steps, GitHub secret updates, and migration instructions related to IAM user role policies.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 20 files

Confidence score: 3/5

  • Security risk: infrastructure/bootstrap/main.tf grants ECR write actions on all repositories, which can allow pushing images to any repo in the account.
  • Functional risk: KMS permissions in infrastructure/backend/README.md and infrastructure/staging/README.md are incomplete or ineffective, which can block key creation or Terraform state uploads.
  • The issues are concrete and user-impacting (security scope and state write failures), so this is a moderate merge risk until addressed.
  • Pay close attention to infrastructure/bootstrap/main.tf, infrastructure/backend/README.md, infrastructure/staging/README.md - scope ECR permissions and fix KMS action coverage.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="infrastructure/backend/README.md">

<violation number="1" location="infrastructure/backend/README.md:85">
P2: `kms:CreateKey` and `kms:ListAliases` are not resource-scoped actions, so tying them to `${AWS_BACKEND_KMS_KEY_ARN}` makes those permissions ineffective and prevents the user from creating keys or listing aliases. Split non-key-specific actions into a separate statement with `"Resource": "*"`, keeping key-specific actions scoped to the key ARN.</violation>
</file>

<file name="infrastructure/staging/README.md">

<violation number="1" location="infrastructure/staging/README.md:35">
P2: KMS permissions are incomplete for an SSE-KMS encrypted Terraform state bucket. `kms:GenerateDataKey` is required for `s3:PutObject` on KMS-encrypted objects; without it, state uploads will fail.</violation>
</file>

<file name="infrastructure/bootstrap/main.tf">

<violation number="1" location="infrastructure/bootstrap/main.tf:51">
P1: Security: ECR data-plane write actions (`PutImage`, `UploadLayerPart`, etc.) are granted on `resources = ["*"]`, allowing image push to **any** ECR repository in the account. Only `ecr:GetAuthorizationToken` requires `*`. Split this into two statements: one for `GetAuthorizationToken` on `*`, and one for the repository-scoped data-plane actions on `arn:aws:ecr:*:${account_id}:repository/${project_name}-${env}-*`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (9)
.gitignore (1)

41-44: Consider adding a production-environment pattern.

The new patterns cover nest-dev and nest-staging, but if a nest-prod* / nest-production* environment is ever set up, its Zappa build artifacts would not be auto-ignored.

💡 Suggested addition
  backend/*nest-staging*.tar.gz
  backend/*nest-staging*.zip
+ backend/*nest-prod*.tar.gz
+ backend/*nest-prod*.zip
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore around lines 41 - 44, Add ignore patterns for production Zappa
artifacts analogous to the existing dev and staging entries: include patterns
like "backend/*nest-prod*.tar.gz", "backend/*nest-prod*.zip",
"backend/*nest-production*.tar.gz", and "backend/*nest-production*.zip" so
future nest-prod or nest-production builds are auto-ignored alongside the
existing backend/*nest-dev* and backend/*nest-staging* entries.
infrastructure/backend/README.md (1)

64-86: Consider removing kms:DisableKeyRotation

Granting the ability to disable key rotation weakens the long-term security posture of KMS keys. If key rotation is a project requirement, removing this permission prevents accidental or malicious disabling. The existing kms:EnableKeyRotation is sufficient for rotation management.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/backend/README.md` around lines 64 - 86, The IAM policy
statement with "Sid": "KMSManagement" currently includes
"kms:DisableKeyRotation" in its "Action" array; remove "kms:DisableKeyRotation"
from that array (leaving "kms:EnableKeyRotation" and the other KMS actions
intact) so the policy no longer grants the ability to disable key rotation for
the resource ${AWS_BACKEND_KMS_KEY_ARN}.
infrastructure/README.md (1)

58-68: touch before cat > is redundant

cat source > dest creates the destination file if it doesn't exist. The preceding touch step adds no value and can be removed throughout the file (same pattern repeats at lines 70-74 and in the staging section).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/README.md` around lines 58 - 68, Remove the redundant touch
commands that precede the redirection-based file creation (e.g., the "touch
terraform.tfvars" before "cat terraform.tfvars.example > terraform.tfvars") —
keep only the cat redirection lines to create/populate files; apply the same
change for the repeated pattern (the similar touch/cat pair in the local section
and the staging section) so the README uses only "cat {source} > {dest}" without
prior touch.
.github/workflows/run-ci-cd.yaml (1)

701-704: scan-staging-images in plan-staging-nest.needs is redundant

bootstrap-staging-nest already lists scan-staging-images in its needs, so plan-staging-nest (which depends on bootstrap-staging-nest) implicitly waits for scan-staging-images. The explicit entry is harmless but adds noise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/run-ci-cd.yaml around lines 701 - 704, The job
plan-staging-nest currently lists scan-staging-images in its needs array even
though bootstrap-staging-nest already depends on scan-staging-images, so remove
scan-staging-images from the needs of plan-staging-nest to avoid redundant
noise; edit the needs block for plan-staging-nest to only include
bootstrap-staging-nest and set-release-version (retain set-release-version) and
keep bootstrap-staging-nest as-is so the dependency on scan-staging-images
remains transitively satisfied.
infrastructure/bootstrap/terraform.tfbackend.example (1)

1-3: Consider adding encrypt = true to the backend example

The backend config example omits encrypt = true. While bucket-level SSE handles encryption regardless, explicitly setting it in the backend config is a defense-in-depth practice and is consistent with how other Terraform S3 backends recommend configuration. If the staging backend example includes it, add it here for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/terraform.tfbackend.example` around lines 1 - 3, Add
the missing encrypt flag to the Terraform S3 backend example by adding encrypt =
true to the backend configuration (alongside bucket, dynamodb_table, and region)
so the backend explicitly requests server-side encryption for state files for
defense-in-depth and consistency with other examples.
infrastructure/bootstrap/variables.tf (1)

7-11: Consider adding a validation block for consistency.

infrastructure/backend/variables.tf validates state_environments entries against an allowed list. This environments variable lacks similar validation, which means typos (e.g., "stagin") would silently create misnamed IAM resources rather than failing early.

Proposed validation
 variable "environments" {
   description = "The environments to create Terraform roles for."
   type        = list(string)
   default     = ["staging"]
+
+  validation {
+    condition = alltrue([
+      for env in var.environments : contains(["staging"], env)
+    ])
+    error_message = "Each environment must be 'staging'."
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/variables.tf` around lines 7 - 11, The variable
"environments" lacks input validation so typos can create incorrect IAM
resources; add a Terraform validation block to the variable "environments" that
checks every item is a member of your allowed environments set (the same allowed
list used by state_environments in infrastructure/backend/variables.tf) using an
expression like all(var.environments, contains(local.allowed_environments, ...))
and provide a clear error_message describing allowed values; update the
"environments" variable declaration to include that validation and reference the
same allowed list to keep both variables consistent.
infrastructure/bootstrap/main.tf (2)

424-441: KMSManagement grants ScheduleKeyDeletion and PutKeyPolicy on all KMS keys.

While kms:CreateKey requires resources = ["*"], several actions in this statement are restrictable by key ARN — notably ScheduleKeyDeletion and PutKeyPolicy, which are high-impact operations. Consider splitting CreateKey (and ListAliases which also requires "*") into a separate statement, and scoping the remaining actions to project-specific key ARNs or using a kms:ResourceAliases condition like the KMSKeyUsageAndPolicy statement does.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/main.tf` around lines 424 - 441, The KMSManagement
statement currently grants high-impact actions (ScheduleKeyDeletion,
PutKeyPolicy) on resources = ["*"]; split this into two statements: keep
kms:CreateKey and kms:ListAliases in a new statement with resources = ["*"]
(these require wildcard), and move the remaining actions (e.g.,
kms:DisableKeyRotation, kms:EnableKeyRotation, kms:GetKeyPolicy,
kms:GetKeyRotationStatus, kms:PutKeyPolicy, kms:ScheduleKeyDeletion,
kms:TagResource, kms:UntagResource) into a scoped statement that targets
project-specific KMS key ARNs or applies a kms:ResourceAliases / condition like
the existing KMSKeyUsageAndPolicy statement to restrict allowed keys.

70-82: Checkov CKV_AWS_356: several statements use "*" for restrictable actions.

Beyond the ECR/KMS issues flagged above, two additional statements use resources = ["*"] with partially restrictable actions:

  • AppAutoscalingManagement (Line 82): Mutative actions like PutScalingPolicy, RegisterScalableTarget are restrictable but the ARN format for app-autoscaling is complex (service namespace dependent).
  • ELBManagement (Line 344): Actions like CreateLoadBalancer, DeleteLoadBalancer support resource-level constraints.

These are lower priority since ELB resource-level restrictions require knowing the ARN upfront (often not possible for Create*), and app-autoscaling ARNs are unwieldy. Worth revisiting as a hardening pass.

Also applies to: 321-344

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/main.tf` around lines 70 - 82, The IAM policy
statements AppAutoscalingManagement and ELBManagement currently use resources =
["*"]; change them to enforce resource-level restrictions where possible by
replacing "*" with explicit ARNs for restrictable actions (e.g., for
PutScalingPolicy, RegisterScalableTarget use app-autoscaling ARNs built from
aws_partition/region/account/id or module outputs) and keep a separate minimal
"*" statement only for unavoidable create/delete actions that cannot be
constrained (e.g., CreateLoadBalancer). Split each statement into two if needed
(one with specific ARNs for mutative, restrictable actions and another
limited-wildcard for non-constrainable actions), and use Terraform
interpolations/data lookups (account_id, aws_region, aws_partition, resource
ids) or variables to construct those ARNs so Checkov CKV_AWS_356 is addressed
while preserving necessary functionality.
infrastructure/backend/main.tf (1)

50-74: Per-environment HTTPS-only policy looks correct.

The for_each and each.key references to aws_s3_bucket.state[each.key].arn are consistent.

Consider adding a similar HTTPS-only deny statement to the logs bucket policy (data.aws_iam_policy_document.logs) for parity — currently only the state buckets enforce HTTPS.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/backend/main.tf` around lines 50 - 74, Add the same
per-environment HTTPS-only deny statement used in
data.aws_iam_policy_document.state_https_only to the logs policy
(data.aws_iam_policy_document.logs): create a statement block in
data.aws_iam_policy_document.logs that uses the same for_each iteration
(matching the same environment keys), sets actions = ["s3:*"], effect = "Deny",
sid = "HTTPSOnly", resources to aws_s3_bucket.logs[each.key].arn and
"${aws_s3_bucket.logs[each.key].arn}/*", and the same condition (test = "Bool",
variable = "aws:SecureTransport", values = ["false"]) and principals
(identifiers = ["*"], type = "AWS") so logs buckets enforce HTTPS parity with
state buckets.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 479-482: The workflow currently hardcodes role-external-id
("nest-staging-terraform") which is predictable; replace that value with a
GitHub Actions secret (e.g., use ${{ secrets.AWS_ROLE_EXTERNAL_ID }}) wherever
role-external-id appears (the same block with role-to-assume and the other
similar blocks mentioned), ensure you create a strong randomly generated
external ID and store it in the repository/org secrets, and update the
corresponding IAM role trust policy to require that secret value so the
external-id check remains effective.
- Around line 687-689: The CI job currently runs a raw "Terraform Apply" step
(working-directory: infrastructure/bootstrap) with "terraform apply
-auto-approve" which applies IAM changes without producing or surfacing a plan;
update the job to first run "terraform plan -out=tfplan" and append "terraform
show -no-color tfplan" to $GITHUB_STEP_SUMMARY (so the plan diff is visible in
the job summary), then change the apply command to "terraform apply
-auto-approve tfplan" to ensure the exact reviewed plan is applied.

In @.gitignore:
- Around line 41-44: The .gitignore changes removed coverage for legacy
artifacts named like nest-backend-*, so re-add ignore patterns to prevent
accidental tracking: include entries matching backend/*nest-backend-*.tar.gz and
backend/*nest-backend-*.zip (mirror the existing backend/*nest-dev*.tar.gz
lines) and then verify no such files remain locally or in CI by running a
filesystem check (e.g., find backend/ for "*nest-backend-*.tar.gz" and "*.zip")
before merging.

In `@infrastructure/backend/main.tf`:
- Around line 114-160: The lifecycle rule in
aws_s3_bucket_lifecycle_configuration.logs (rule id "expire-logs") only sets
expiration for current objects and doesn't clean up versioned objects; update
that lifecycle rule to include a noncurrent_version_expiration block (e.g.,
noncurrent_version_expiration { days = var.expire_noncurrent_version_days }) so
noncurrent versions are deleted after a defined retention, and ensure you add or
reuse an appropriate variable (such as var.expire_noncurrent_version_days) for
the retention period rather than leaving it hardcoded.

In `@infrastructure/bootstrap/main.tf`:
- Around line 277-293: The ECSOrchestration IAM statement currently uses
unscoped ARNs like "${var.project_name}-*" which allows cross-environment
access; update each ARN in the statement with sid "ECSOrchestration" to include
the environment key by replacing "${var.project_name}-*" with
"${var.project_name}-${each.key}-*" (e.g., task-definition, service, cluster,
task ARNs) so the role is scoped per environment consistent with
ECSClusterManagement and ECSServiceManagement.
- Around line 209-225: The "ECRAuth" policy statement currently mixes the
resource-global action ecr:GetAuthorizationToken (which requires resources =
["*"]) with repository-scoped data-plane actions, effectively granting those
data-plane actions on all repos; split this into two statements: keep a new
statement with sid "ECRAuth" (or similar) that contains only
"ecr:GetAuthorizationToken" and resources = ["*"], and move the remaining
actions (BatchCheckLayerAvailability, BatchGetImage, CompleteLayerUpload,
GetDownloadUrlForLayer, GetRepositoryPolicy, InitiateLayerUpload, ListImages,
PutImage, UploadLayerPart) into the existing ECRManagement scoped statement so
they use repository ARNs instead of "*". Ensure the original ECRAuth actions
list and the ECRManagement block are updated accordingly.

In `@infrastructure/README.md`:
- Around line 82-84: Clarify which bucket to use by updating the README text to
instruct operators to use the specific Terraform output for the bootstrap
environment (for example, the backend module output name like
outputs.state_bucket_name["bootstrap"]) when filling terraform.tfbackend;
mention the exact output key (state_bucket_name["bootstrap"]) and show it as the
value to copy so users pick the per-environment bootstrap bucket rather than a
generic name.

In `@infrastructure/staging/README.md`:
- Around line 32-38: The IAM policy statement with "Sid": "KMSStateManagement"
currently grants only "kms:Decrypt", which prevents S3 SSE-KMS writes; update
the policy for the KMSStateManagement statement (the Action array) to include
"kms:GenerateDataKey" (and optionally "kms:Encrypt" if you prefer explicit
encrypt permissions) so Terraform/state uploads can generate data keys and
succeed.

---

Nitpick comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 701-704: The job plan-staging-nest currently lists
scan-staging-images in its needs array even though bootstrap-staging-nest
already depends on scan-staging-images, so remove scan-staging-images from the
needs of plan-staging-nest to avoid redundant noise; edit the needs block for
plan-staging-nest to only include bootstrap-staging-nest and set-release-version
(retain set-release-version) and keep bootstrap-staging-nest as-is so the
dependency on scan-staging-images remains transitively satisfied.

In @.gitignore:
- Around line 41-44: Add ignore patterns for production Zappa artifacts
analogous to the existing dev and staging entries: include patterns like
"backend/*nest-prod*.tar.gz", "backend/*nest-prod*.zip",
"backend/*nest-production*.tar.gz", and "backend/*nest-production*.zip" so
future nest-prod or nest-production builds are auto-ignored alongside the
existing backend/*nest-dev* and backend/*nest-staging* entries.

In `@infrastructure/backend/main.tf`:
- Around line 50-74: Add the same per-environment HTTPS-only deny statement used
in data.aws_iam_policy_document.state_https_only to the logs policy
(data.aws_iam_policy_document.logs): create a statement block in
data.aws_iam_policy_document.logs that uses the same for_each iteration
(matching the same environment keys), sets actions = ["s3:*"], effect = "Deny",
sid = "HTTPSOnly", resources to aws_s3_bucket.logs[each.key].arn and
"${aws_s3_bucket.logs[each.key].arn}/*", and the same condition (test = "Bool",
variable = "aws:SecureTransport", values = ["false"]) and principals
(identifiers = ["*"], type = "AWS") so logs buckets enforce HTTPS parity with
state buckets.

In `@infrastructure/backend/README.md`:
- Around line 64-86: The IAM policy statement with "Sid": "KMSManagement"
currently includes "kms:DisableKeyRotation" in its "Action" array; remove
"kms:DisableKeyRotation" from that array (leaving "kms:EnableKeyRotation" and
the other KMS actions intact) so the policy no longer grants the ability to
disable key rotation for the resource ${AWS_BACKEND_KMS_KEY_ARN}.

In `@infrastructure/bootstrap/main.tf`:
- Around line 424-441: The KMSManagement statement currently grants high-impact
actions (ScheduleKeyDeletion, PutKeyPolicy) on resources = ["*"]; split this
into two statements: keep kms:CreateKey and kms:ListAliases in a new statement
with resources = ["*"] (these require wildcard), and move the remaining actions
(e.g., kms:DisableKeyRotation, kms:EnableKeyRotation, kms:GetKeyPolicy,
kms:GetKeyRotationStatus, kms:PutKeyPolicy, kms:ScheduleKeyDeletion,
kms:TagResource, kms:UntagResource) into a scoped statement that targets
project-specific KMS key ARNs or applies a kms:ResourceAliases / condition like
the existing KMSKeyUsageAndPolicy statement to restrict allowed keys.
- Around line 70-82: The IAM policy statements AppAutoscalingManagement and
ELBManagement currently use resources = ["*"]; change them to enforce
resource-level restrictions where possible by replacing "*" with explicit ARNs
for restrictable actions (e.g., for PutScalingPolicy, RegisterScalableTarget use
app-autoscaling ARNs built from aws_partition/region/account/id or module
outputs) and keep a separate minimal "*" statement only for unavoidable
create/delete actions that cannot be constrained (e.g., CreateLoadBalancer).
Split each statement into two if needed (one with specific ARNs for mutative,
restrictable actions and another limited-wildcard for non-constrainable
actions), and use Terraform interpolations/data lookups (account_id, aws_region,
aws_partition, resource ids) or variables to construct those ARNs so Checkov
CKV_AWS_356 is addressed while preserving necessary functionality.

In `@infrastructure/bootstrap/terraform.tfbackend.example`:
- Around line 1-3: Add the missing encrypt flag to the Terraform S3 backend
example by adding encrypt = true to the backend configuration (alongside bucket,
dynamodb_table, and region) so the backend explicitly requests server-side
encryption for state files for defense-in-depth and consistency with other
examples.

In `@infrastructure/bootstrap/variables.tf`:
- Around line 7-11: The variable "environments" lacks input validation so typos
can create incorrect IAM resources; add a Terraform validation block to the
variable "environments" that checks every item is a member of your allowed
environments set (the same allowed list used by state_environments in
infrastructure/backend/variables.tf) using an expression like
all(var.environments, contains(local.allowed_environments, ...)) and provide a
clear error_message describing allowed values; update the "environments"
variable declaration to include that validation and reference the same allowed
list to keep both variables consistent.

In `@infrastructure/README.md`:
- Around line 58-68: Remove the redundant touch commands that precede the
redirection-based file creation (e.g., the "touch terraform.tfvars" before "cat
terraform.tfvars.example > terraform.tfvars") — keep only the cat redirection
lines to create/populate files; apply the same change for the repeated pattern
(the similar touch/cat pair in the local section and the staging section) so the
README uses only "cat {source} > {dest}" without prior touch.

@codecov
Copy link

codecov bot commented Feb 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.32%. Comparing base (fc6363b) to head (1924052).

Additional details and impacted files

Impacted file tree graph

@@                      Coverage Diff                      @@
##           feature/nest-zappa-migration    #3996   +/-   ##
=============================================================
  Coverage                         93.32%   93.32%           
=============================================================
  Files                               513      513           
  Lines                             15827    15827           
  Branches                           2178     2134   -44     
=============================================================
  Hits                              14770    14770           
  Misses                              695      695           
  Partials                            362      362           
Flag Coverage Δ
backend 92.88% <ø> (ø)
frontend 94.64% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fc6363b...1924052. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 6 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="infrastructure/bootstrap/terraform.tfvars.example">

<violation number="1" location="infrastructure/bootstrap/terraform.tfvars.example:2">
P2: This tfvars example won’t parse: `${AWS_ROLE_EXTERNAL_ID}` is interpolation syntax and must be inside quotes or replaced with a literal placeholder. As written, Terraform will error when reading the file.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
infrastructure/bootstrap/main.tf (2)

302-326: ELBManagement grants mutating actions on all ELB resources across all environments.

Unlike ECSClusterManagement, ECRManagement, and other environment-scoped statements, ELBManagement uses resources = ["*"] for actions like CreateLoadBalancer, DeleteLoadBalancer, DeleteTargetGroup, etc. These actions support resource-level ARN restrictions. A staging role could modify or delete load balancers belonging to other environments.

Consider scoping to environment-specific ARNs consistent with the rest of the policy, e.g.:

resources = [
  "arn:aws:elasticloadbalancing:${var.aws_region}:${data.aws_caller_identity.current.account_id}:loadbalancer/app/${var.project_name}-${each.key}-*/*",
  "arn:aws:elasticloadbalancing:${var.aws_region}:${data.aws_caller_identity.current.account_id}:targetgroup/${var.project_name}-${each.key}-*/*",
  "arn:aws:elasticloadbalancing:${var.aws_region}:${data.aws_caller_identity.current.account_id}:listener/app/${var.project_name}-${each.key}-*/*",
  "arn:aws:elasticloadbalancing:${var.aws_region}:${data.aws_caller_identity.current.account_id}:listener-rule/app/${var.project_name}-${each.key}-*/*",
]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/main.tf` around lines 302 - 326, The ELBManagement
IAM statement currently uses resources = ["*"] giving broad mutating rights;
replace the wildcard with environment-scoped ELB ARNs (instead of "*") so only
load balancers, target groups, listeners and listener-rules for the given
environment can be modified—use patterns built from var.aws_region,
data.aws_caller_identity.current.account_id, var.project_name and each.key
(e.g., loadbalancer/app/..., targetgroup/..., listener/app/...,
listener-rule/...) to mirror the scoping used by
ECSClusterManagement/ECRManagement; update the ELBManagement statement's
resources array accordingly so actions like CreateLoadBalancer,
DeleteLoadBalancer, DeleteTargetGroup, CreateListener, and related actions are
limited to those ARNs.

139-187: EC2Management grants broad mutating permissions on all EC2 resources.

This statement allows creating/deleting VPCs, subnets, security groups, NAT gateways, etc. across the entire account without environment scoping. While many EC2 actions don't support resource-level permissions, some do (e.g., security group, subnet, and VPC operations). If cross-environment isolation is a concern, consider adding tag-based conditions (aws:RequestTag / aws:ResourceTag) to limit mutations to environment-tagged resources.

This is noted as informational given the complexity of EC2 resource-level permissions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/main.tf` around lines 139 - 187, The EC2Management
IAM statement grants broad mutating EC2 permissions on all resources (resources
= ["*"])—narrow this by adding tag-based conditions so mutations are limited to
environment-scoped resources: update the statement with a Condition using
aws:RequestTag/environment and aws:ResourceTag/environment (e.g., StringEquals
-> "environment": "dev" or a variable) and where possible replace resources="*"
with the appropriate ARNs for actions that support resource-level scoping;
target the statement identified by sid "EC2Management" when applying these
changes.
infrastructure/backend/main.tf (1)

146-155: Logs bucket uses S3-managed encryption (AES256) while state lock uses KMS — document the rationale.

The DynamoDB state lock table (line 99) uses a customer-managed KMS key, but the logs bucket (line 152) and state buckets (line 244) use AES256 (S3-managed SSE). The #trivy:ignore suppression indicates this is intentional. Consider adding a brief comment explaining the choice (e.g., cost or because logs/state don't contain sensitive data beyond what's in the state file), so future maintainers understand the divergence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/backend/main.tf` around lines 146 - 155, Add a short inline
comment above the aws_s3_bucket_server_side_encryption_configuration "logs"
resource explaining why SSE is set to AES256 (S3-managed) while the DynamoDB
state lock uses a customer-managed KMS key: note the intentional decision
(referencing aws_s3_bucket_server_side_encryption_configuration.logs and the
DynamoDB state lock KMS usage) and briefly state the rationale (cost, lack of
sensitive data in logs/state, or reduced key management burden) and reference
the `#trivy`:ignore tag so future maintainers understand this divergence.
.github/workflows/run-ci-cd.yaml (1)

703-706: scan-staging-images is a redundant direct dependency in plan-staging-nest.

bootstrap-staging-nest already declares scan-staging-images as its own dependency, so plan-staging-nest transitively waits for it. The explicit entry is harmless but adds noise to the DAG.

♻️ Suggested simplification
   needs:
     - bootstrap-staging-nest
-    - scan-staging-images
     - set-release-version
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/run-ci-cd.yaml around lines 703 - 706, Remove the
redundant direct dependency on scan-staging-images from the needs list of the
plan-staging-nest job; since bootstrap-staging-nest already declares
scan-staging-images as a dependency, keep needs: - bootstrap-staging-nest and -
set-release-version (or other required entries) but delete the explicit -
scan-staging-images entry to simplify the DAG while preserving correctness.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 821-824: The role-duration-seconds setting for the GitHub Actions
jobs (notably the deploy-staging-nest job’s role-duration-seconds currently set
to 1200) is too short and causes STS credentials to expire mid-run; update
role-duration-seconds for deploy-staging-nest (and mirror the change for
build-staging-images and plan-staging-nest) to at least 3600 (or to the maximum
allowed by the nest-staging-terraform role) so the assumed-role session spans
the full CI job runtime; ensure you change the role-duration-seconds key value
for those job blocks (deploy-staging-nest, build-staging-images,
plan-staging-nest).

In `@infrastructure/bootstrap/main.tf`:
- Around line 211-212: The IAM policy actions list contains a duplicated action
string "ecr:GetRepositoryPolicy"; remove the redundant entry so each action
appears only once (locate the actions array that includes
"ecr:GetRepositoryPolicy" and delete the duplicate occurrence).

---

Duplicate comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 689-691: The "Terraform Apply" step currently runs "terraform
apply -auto-approve" with no preceding plan; change the workflow to first run a
"Terraform Plan" step in the same working-directory ("infrastructure/bootstrap")
that runs "terraform init" then "terraform plan -out=tfplan" and saves/exports
the plan (e.g., "terraform show -json tfplan > plan.json"), upload that
plan.json as a job artifact and/or print a human-readable plan to the job
summary, and make the existing "Terraform Apply" step consume the saved plan
(e.g., "terraform apply tfplan") instead of applying directly from workspace to
ensure IAM and other diffs are reviewed and surfaced.

In `@infrastructure/bootstrap/main.tf`:
- Around line 258-274: The ECSOrchestration statement (sid "ECSOrchestration")
grants ARNs using "${var.project_name}-*" without environment scoping; update
the resource ARNs to include the environment/stage variable (e.g., incorporate
var.environment or var.stage into the pattern) so they target
"${var.project_name}-${var.environment}-*" (or equivalent) for task-definition,
service, cluster, and task ARNs; ensure you update all four resource entries in
that statement block so policies are environment-scoped and maintain the
existing region/account interpolation.

---

Nitpick comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 703-706: Remove the redundant direct dependency on
scan-staging-images from the needs list of the plan-staging-nest job; since
bootstrap-staging-nest already declares scan-staging-images as a dependency,
keep needs: - bootstrap-staging-nest and - set-release-version (or other
required entries) but delete the explicit - scan-staging-images entry to
simplify the DAG while preserving correctness.

In `@infrastructure/backend/main.tf`:
- Around line 146-155: Add a short inline comment above the
aws_s3_bucket_server_side_encryption_configuration "logs" resource explaining
why SSE is set to AES256 (S3-managed) while the DynamoDB state lock uses a
customer-managed KMS key: note the intentional decision (referencing
aws_s3_bucket_server_side_encryption_configuration.logs and the DynamoDB state
lock KMS usage) and briefly state the rationale (cost, lack of sensitive data in
logs/state, or reduced key management burden) and reference the `#trivy`:ignore
tag so future maintainers understand this divergence.

In `@infrastructure/bootstrap/main.tf`:
- Around line 302-326: The ELBManagement IAM statement currently uses resources
= ["*"] giving broad mutating rights; replace the wildcard with
environment-scoped ELB ARNs (instead of "*") so only load balancers, target
groups, listeners and listener-rules for the given environment can be
modified—use patterns built from var.aws_region,
data.aws_caller_identity.current.account_id, var.project_name and each.key
(e.g., loadbalancer/app/..., targetgroup/..., listener/app/...,
listener-rule/...) to mirror the scoping used by
ECSClusterManagement/ECRManagement; update the ELBManagement statement's
resources array accordingly so actions like CreateLoadBalancer,
DeleteLoadBalancer, DeleteTargetGroup, CreateListener, and related actions are
limited to those ARNs.
- Around line 139-187: The EC2Management IAM statement grants broad mutating EC2
permissions on all resources (resources = ["*"])—narrow this by adding tag-based
conditions so mutations are limited to environment-scoped resources: update the
statement with a Condition using aws:RequestTag/environment and
aws:ResourceTag/environment (e.g., StringEquals -> "environment": "dev" or a
variable) and where possible replace resources="*" with the appropriate ARNs for
actions that support resource-level scoping; target the statement identified by
sid "EC2Management" when applying these changes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
infrastructure/bootstrap/main.tf (1)

83-84: Checkov CKV_AWS_356 — resources = ["*"] for partially restrictable services.

Checkov flags four statements where at least some actions support resource-level ARN restrictions:

Statement Key actions that ARE restrictable
AppAutoscalingManagement (line 83) PutScalingPolicy, DeleteScalingPolicy can be scoped to scalable target ARNs
EC2Management (line 187) VPC, subnet, security group, route table, NAT gateway, VPC endpoint all have ARN formats
ELBManagement (line 324) Load balancer, listener, rule, target group each have elasticloadbalancing: ARN formats
KMSManagement (line 420) PutKeyPolicy, ScheduleKeyDeletion, GetKeyPolicy, etc. can be scoped to arn:aws:kms:...:key/* (only CreateKey genuinely requires *)

For the testing environment, this is acceptable. If/when this config is hardened, split kms:CreateKey into its own resources = ["*"] statement and scope the remaining KMS mutation actions to arn:aws:kms:${var.aws_region}:${data.aws_caller_identity.current.account_id}:key/*; similarly scope ELB management actions to the project's load balancer ARN patterns.

Also applies to: 187-188, 324-325, 420-421

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/main.tf` around lines 83 - 84, The policy statements
currently using resources = ["*"] (notably the AppAutoscalingManagement,
EC2Management, ELBManagement and KMSManagement statements) should be split so
only truly global actions keep "*" and all other actions are scoped to resource
ARNs; specifically: for KMSManagement move kms:CreateKey into its own statement
with resources = ["*"] and scope the remaining KMS actions to
arn:aws:kms:${var.aws_region}:${data.aws_caller_identity.current.account_id}:key/*,
scope ELBManagement actions to the project's ELB ARNs (elasticloadbalancing:…
target-group/listener/loadbalancer ARNs), scope EC2Management actions to
VPC/subnet/sg/route/nat/vpc-endpoint ARNs, and scope AppAutoscalingManagement
PutScalingPolicy/DeleteScalingPolicy to scalable target ARNs—update the
statements (by name) accordingly so only necessary actions remain wildcarded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 444-452: Restore the removed branch guards and test-job
dependencies so staging jobs only run on the canonical main branch and after
tests: re-add the exact if condition "github.repository == 'OWASP/Nest' &&
github.ref == 'refs/heads/main'" to the jobs build-staging-images,
bootstrap-staging-nest, plan-staging-nest, and deploy-staging-nest, and
re-enable the needs list for build-staging-images to include run-backend-tests,
run-frontend-a11y-tests, run-frontend-e2e-tests, run-frontend-unit-tests, and
run-infrastructure-tests so image builds and Terraform applies are gated by
upstream test jobs.
- Around line 503-505: The CI currently stopped pushing Docker Hub tags
(owasp/nest:backend-staging and owasp/nest:frontend-staging) in the
build-staging-images job but scan-staging-images still expects those Docker Hub
names (via BACKEND_IMAGE_NAME=owasp/nest:backend-staging), so either restore the
Docker Hub push tags in build-staging-images (re-add the
owasp/nest:backend-staging and owasp/nest:frontend-staging tags to the tags
block so Docker Hub images are available for scan-staging-images) or change
scan-staging-images to pull from ECR by replacing
BACKEND_IMAGE_NAME/FRONTEND_IMAGE_NAME values with the ECR names and adding AWS
credential setup (aws-actions/configure-aws-credentials) and login-to-ecr steps
in the scan-staging-images job; update references to build-staging-images,
scan-staging-images, BACKEND_IMAGE_NAME and FRONTEND_IMAGE_NAME accordingly.

In `@infrastructure/bootstrap/main.tf`:
- Around line 551-554: The S3 policy's resources array uses a broad wildcard
("${var.project_name}-*") allowing cross-environment access; replace the bucket
ARNs to scope them to the environment key (use
"${var.project_name}-${each.key}-*" and "${var.project_name}-${each.key}-*/*" in
the resources list) so S3Management follows the same per-environment scoping
pattern as other policies (references: resources, var.project_name, each.key);
if there are intentional shared buckets, add those specific ARNs as additional
entries rather than using a widened wildcard.

---

Nitpick comments:
In `@infrastructure/bootstrap/main.tf`:
- Around line 83-84: The policy statements currently using resources = ["*"]
(notably the AppAutoscalingManagement, EC2Management, ELBManagement and
KMSManagement statements) should be split so only truly global actions keep "*"
and all other actions are scoped to resource ARNs; specifically: for
KMSManagement move kms:CreateKey into its own statement with resources = ["*"]
and scope the remaining KMS actions to
arn:aws:kms:${var.aws_region}:${data.aws_caller_identity.current.account_id}:key/*,
scope ELBManagement actions to the project's ELB ARNs (elasticloadbalancing:…
target-group/listener/loadbalancer ARNs), scope EC2Management actions to
VPC/subnet/sg/route/nat/vpc-endpoint ARNs, and scope AppAutoscalingManagement
PutScalingPolicy/DeleteScalingPolicy to scalable target ARNs—update the
statements (by name) accordingly so only necessary actions remain wildcarded.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
infrastructure/bootstrap/main.tf (1)

244-255: Optional: scope ecs:TagResource instead of granting it on * in ECSGlobal.

ecs:RegisterTaskDefinition and ecs:DeregisterTaskDefinition genuinely require *, but ecs:TagResource supports resource-level restrictions. Keeping it in ECSGlobal also makes the scoped ecs:TagResource entry in ECSClusterManagement (line 237) redundant and allows tagging any ECS resource in the account.

Consider moving it to the per-environment scoped blocks instead:

♻️ Proposed refactor
  statement {
    sid    = "ECSGlobal"
    effect = "Allow"
    actions = [
      "ecs:DeregisterTaskDefinition",
      "ecs:ListClusters",
      "ecs:ListTaskDefinitions",
      "ecs:RegisterTaskDefinition",
-     "ecs:TagResource",
    ]
    resources = ["*"]
  }

Then add ecs:TagResource for task-definition ARNs in the ECSTaskDefinition block (line 287) and keep it in ECSClusterManagement/ECSServiceManagement as already scoped.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/main.tf` around lines 244 - 255, The ECSGlobal
policy block currently grants ecs:TagResource on "*" which is overly broad;
remove ecs:TagResource from the ECSGlobal statement and instead add scoped
ecs:TagResource permissions to the per-environment blocks: add ecs:TagResource
for task definition ARNs in the ECSTaskDefinition policy (so task-definition
tagging is limited to arn:aws:ecs:...:task-definition/*), and ensure
ECSClusterManagement and ECSServiceManagement keep their existing scoped
ecs:TagResource entries—this eliminates the redundant global tag permission
while preserving required broad permissions for ecs:RegisterTaskDefinition and
ecs:DeregisterTaskDefinition in ECSGlobal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 694-700: The variable declaration for aws_role_external_id in
variables.tf is missing sensitive = true; update the aws_role_external_id
variable block to include sensitive = true so Terraform redacts its value in
plan output (modify the aws_role_external_id variable declaration in
variables.tf to add the sensitive attribute and re-run the plan to confirm the
value is redacted).

---

Duplicate comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 444-452: Several staging CI jobs have their conditional `if:`
gates and upstream `needs:` dependencies commented out (e.g., the commented
`#if:` block and `needs:` list around the staging job definitions); restore the
guarded execution by uncommenting the `if:` condition and the `needs:` entries
so these jobs run only when the repository/ref matches and after upstream test
jobs complete—specifically remove the leading "#" from the `if:` line and the
required `needs:` lines in the same job blocks (the blocks shown plus the other
occurrences noted at the ranges referenced) so the workflow validates upstream
gates and dependencies.
- Around line 604-615: The workflow step is using old Docker Hub image names;
update the invocations of the Make targets (security-scan-backend-image,
security-scan-frontend-image, sbom-backend-image, sbom-frontend-image) to pass
the correct, currently published image names (or CI image variables) instead of
BACKEND_IMAGE_NAME=owasp/nest:backend-staging and
FRONTEND_IMAGE_NAME=owasp/nest:frontend-staging; change those arguments to the
new registry/image tags or to existing CI variables (e.g., use your build
artifact variables or GHCR/ECR names) so the Make targets reference images that
are actually pushed.

In `@infrastructure/bootstrap/main.tf`:
- Around line 257-273: The IAM policy statement with sid "ECSOrchestration" uses
generic resource ARNs referencing "${var.project_name}-*" which doesn't include
the per-item key; update each ARN in the resources block to interpolate the loop
key (e.g., include "${each.key}" alongside "${var.project_name}") so ARNs are
scoped per item when this module is iterated (adjust the task-definition,
service, cluster, and task ARNs to include ${each.key} where appropriate).
- Around line 551-554: The S3Management IAM policy still grants
cross-environment access by using "arn:aws:s3:::${var.project_name}-*" and
should be limited to the current environment; update the resources array in the
S3Management policy (the entries currently using ${var.project_name}-*) to
reference the environment-scoped bucket naming (e.g., include var.env or
var.deployment_stage so ARNs become ${var.project_name}-${var.env}-* and
${var.project_name}-${var.env}-*/*) or otherwise use the exact bucket
names/variables for this deployment, removing the broad wildcard that allows
cross-environment access.

---

Nitpick comments:
In `@infrastructure/bootstrap/main.tf`:
- Around line 244-255: The ECSGlobal policy block currently grants
ecs:TagResource on "*" which is overly broad; remove ecs:TagResource from the
ECSGlobal statement and instead add scoped ecs:TagResource permissions to the
per-environment blocks: add ecs:TagResource for task definition ARNs in the
ECSTaskDefinition policy (so task-definition tagging is limited to
arn:aws:ecs:...:task-definition/*), and ensure ECSClusterManagement and
ECSServiceManagement keep their existing scoped ecs:TagResource entries—this
eliminates the redundant global tag permission while preserving required broad
permissions for ecs:RegisterTaskDefinition and ecs:DeregisterTaskDefinition in
ECSGlobal.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 8 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="infrastructure/staging/README.md">

<violation number="1" location="infrastructure/staging/README.md:18">
P2: Use the specific Terraform state bucket placeholder instead of a wildcard prefix so the policy only grants access to the actual state bucket.</violation>

<violation number="2" location="infrastructure/staging/README.md:30">
P2: Keep the DynamoDB ARN parameterized for region and table name so the IAM policy matches the configured Terraform backend settings.</violation>
</file>

<file name="infrastructure/bootstrap/main.tf">

<violation number="1" location="infrastructure/bootstrap/main.tf:553">
P2: Object ARN pattern is broader than the bucket ARN and grants access to buckets outside the intended `${var.project_name}-${each.key}-*` scope. Align the object ARN with the bucket naming pattern to keep least-privilege.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
infrastructure/bootstrap/main.tf (3)

26-53: ec2:DescribeFlowLogs and ec2:DescribeNetworkAcls are redundant with ec2:Describe*.

Lines 34–35 explicitly list ec2:DescribeFlowLogs and ec2:DescribeNetworkAcls, but ec2:Describe* on line 33 already covers all EC2 Describe actions. These duplicates are harmless but add noise.

Proposed cleanup
     actions = [
       "acm:DescribeCertificate",
       "application-autoscaling:DescribeScalableTargets",
       "application-autoscaling:DescribeScalingPolicies",
       "ec2:Describe*",
-      "ec2:DescribeFlowLogs",
-      "ec2:DescribeNetworkAcls",
       "ecr:DescribeRepositories",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/main.tf` around lines 26 - 53, In the IAM policy
statement with sid "GlobalDiscovery" remove the redundant actions
"ec2:DescribeFlowLogs" and "ec2:DescribeNetworkAcls" from the actions list
because they are already covered by "ec2:Describe*"; update the actions array in
that statement to eliminate those two explicit entries so the policy is cleaner
while keeping "ec2:Describe*" intact.

86-101: CloudWatchLogsManagement grants write access to all log groups in the region.

The resource pattern arn:aws:logs:${region}:${account}:* allows creating/deleting/tagging any log group in the account, not just environment-scoped ones. Other statements in this policy (ECS, ECR, ElastiCache, etc.) use ${each.key} for environment scoping.

If your log groups follow a naming convention like ${project_name}-${environment}-*, you could tighten this:

resources = ["arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/*/${var.project_name}-${each.key}-*"]

However, CloudWatch Log group ARNs can vary based on the source service (Lambda, ECS, VPC Flow Logs all use different prefixes), so this may require multiple ARN patterns. Noting as a hardening opportunity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/main.tf` around lines 86 - 101, The
CloudWatchLogsManagement policy currently allows write access to all log groups
via the resources pattern
"arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*";
restrict this by scoping the resource ARNs to your project and environment (use
variables like var.project_name and each.key) instead of a global wildcard and
add additional ARN patterns if needed for service-specific prefixes (e.g.,
log-group:/aws/*/...). Update the statement with sid "CloudWatchLogsManagement"
to replace the broad resources entry with a tightened list of ARN patterns
matching your naming convention (and include multiple patterns to cover
Lambda/ECS/VPC Flow Logs where applicable).

301-325: ELBManagement uses resources = ["*"] — consider scoping if ALB naming is consistent.

ELBv2 supports resource-level permissions. If your ALBs and target groups follow ${project_name}-${environment}-*, you could scope these. However, given that this infrastructure code is for testing purposes, the current approach is pragmatic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/bootstrap/main.tf` around lines 301 - 325, The IAM statement
with sid "ELBManagement" currently grants actions to resources = ["*"]; replace
this with scoped ARNs for ALBs and target groups using your naming convention
(e.g.,
arn:aws:elasticloadbalancing:${region}:${account}:loadbalancer/${project_name}-${environment}-*
and
arn:aws:elasticloadbalancing:${region}:${account}:targetgroup/${project_name}-${environment}-*)
or build those ARNs from existing vars/locals (project_name, environment,
region, account_id) so permissions are resource-scoped; optionally add a boolean
var (e.g., allow_wide_elb_permission) to fall back to "*" for test runs and wrap
the resources value with a conditional so tests keep current behavior while
production is scoped.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 626-637: The bootstrap job ordering creates a first-run
dependency: update the workflow so the bootstrap-staging-nest job (which
creates/updates the nest-staging-terraform role) runs before or in parallel with
build-staging-images rather than after scan-staging-images; specifically, remove
or change the needs dependency chain that makes bootstrap-staging-nest depend on
scan-staging-images and ensure build-staging-images either depends on
bootstrap-staging-nest or both run in the correct order/parallel, or
alternatively add a README note documenting that the nest-staging-terraform role
must be pre-created manually before first pipeline run.

In `@infrastructure/bootstrap/main.tf`:
- Around line 551-554: The S3 object ARN in the resources list is missing the
hyphen after ${each.key}, making the pattern too broad; update the second ARN
string in the resources array from
"arn:aws:s3:::${var.project_name}-${each.key}*/*" to include the hyphen so it
reads "arn:aws:s3:::${var.project_name}-${each.key}-*/*" (refer to the resources
array, the ARN literals and variables var.project_name and each.key).

---

Duplicate comments:
In @.github/workflows/run-ci-cd.yaml:
- Around line 694-700: The CI step "Show plan in summary" exposes sensitive plan
contents; mark the Terraform variable aws_role_external_id as sensitive by
adding sensitive = true to the variable "aws_role_external_id" in
infrastructure/bootstrap/variables.tf and remove or redact the raw terraform
show -no-color tfplan output in the workflow (the step named "Show plan in
summary") — either stop appending the plan to $GITHUB_STEP_SUMMARY or replace
the show with a filtered/exported view that omits assume-role/external_id
details (e.g., produce a sanitized summary or use terraform show -json and strip
the external_id field before writing).

---

Nitpick comments:
In `@infrastructure/bootstrap/main.tf`:
- Around line 26-53: In the IAM policy statement with sid "GlobalDiscovery"
remove the redundant actions "ec2:DescribeFlowLogs" and
"ec2:DescribeNetworkAcls" from the actions list because they are already covered
by "ec2:Describe*"; update the actions array in that statement to eliminate
those two explicit entries so the policy is cleaner while keeping
"ec2:Describe*" intact.
- Around line 86-101: The CloudWatchLogsManagement policy currently allows write
access to all log groups via the resources pattern
"arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*";
restrict this by scoping the resource ARNs to your project and environment (use
variables like var.project_name and each.key) instead of a global wildcard and
add additional ARN patterns if needed for service-specific prefixes (e.g.,
log-group:/aws/*/...). Update the statement with sid "CloudWatchLogsManagement"
to replace the broad resources entry with a tightened list of ARN patterns
matching your naming convention (and include multiple patterns to cover
Lambda/ECS/VPC Flow Logs where applicable).
- Around line 301-325: The IAM statement with sid "ELBManagement" currently
grants actions to resources = ["*"]; replace this with scoped ARNs for ALBs and
target groups using your naming convention (e.g.,
arn:aws:elasticloadbalancing:${region}:${account}:loadbalancer/${project_name}-${environment}-*
and
arn:aws:elasticloadbalancing:${region}:${account}:targetgroup/${project_name}-${environment}-*)
or build those ARNs from existing vars/locals (project_name, environment,
region, account_id) so permissions are resource-scoped; optionally add a boolean
var (e.g., allow_wide_elb_permission) to fall back to "*" for test runs and wrap
the resources value with a conditional so tests keep current behavior while
production is scoped.

This reverts commit d43fc1c.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/run-ci-cd.yaml">

<violation number="1" location=".github/workflows/run-ci-cd.yaml:632">
P1: Commenting out the `if` guard on the staging bootstrap job allows infrastructure bootstrapping to run on any branch/repo trigger. This is risky for staging environments and can apply unreviewed changes from non-main branches. Restore the main-branch guard (and scan dependency if required) so bootstrap only runs in the intended context.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@sonarqubecloud
Copy link

@rudransh-shrivastava rudransh-shrivastava marked this pull request as ready for review February 18, 2026 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend ci docs Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Automate IAM User Role Policies

1 participant

Comments