From a97e8501d529ba699b7ab019b235070757689d56 Mon Sep 17 00:00:00 2001 From: Erik Osterman Date: Mon, 13 Apr 2026 10:57:49 -0500 Subject: [PATCH 1/5] Add CI security blog post and remove legacy pull_request_target workflow Adds a changelog blog post about hardening PR preview workflows following a responsible disclosure, removes the last remaining pull_request_target-based workflow, and updates package-lock.json. Co-Authored-By: Claude Opus 4.6 --- ...26-04-13-pr-preview-workflow-hardening.mdx | 51 ++++++++++++++++++ .../workflows/atmos-terraform-plan.yaml | 53 ------------------- package-lock.json | 41 +++++++++++++- 3 files changed, 90 insertions(+), 55 deletions(-) create mode 100644 blog/2026-04-13-pr-preview-workflow-hardening.mdx delete mode 100644 examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml diff --git a/blog/2026-04-13-pr-preview-workflow-hardening.mdx b/blog/2026-04-13-pr-preview-workflow-hardening.mdx new file mode 100644 index 000000000..b1f8221fd --- /dev/null +++ b/blog/2026-04-13-pr-preview-workflow-hardening.mdx @@ -0,0 +1,51 @@ +--- +title: "Security Update: Hardening Pull Request Preview Workflows" +slug: pr-preview-workflow-hardening +description: "We removed a pull_request_target-based preview workflow from our documentation repository after a responsible disclosure highlighting the risk of executing code influenced by an untrusted pull request in the base repository context." +authors: [cloudposse] +tags: [security, github-actions, ci-cd, docs] +date: 2026-04-13 +--- +import Intro from '@site/src/components/Intro'; + + +We removed a pull_request_target-based preview workflow from our documentation repository after a responsible disclosure from security researcher Aviv Donenfeld. This was the last remaining instance of this pattern in our GitHub organization. The issue was limited to pull request preview environments for this repository, there is no indication it was ever exploited, and the overall impact was minimal. + + + + +One of our long-standing best practices is requiring maintainer approval before running workflows triggered by pull requests. That eliminates an entire class of CI/CD attack vectors involving untrusted code execution. + +It is easy to assume that protection applies universally, including to workflows triggered by `pull_request_target`. That assumption breaks down when those workflows execute code, scripts, or artifacts influenced by an untrusted pull request. + +Workflows triggered by `pull_request_target` run in the context of the base repository. That is not inherently unsafe if all executed code comes strictly from a trusted and protected branch. The problem is when such workflows execute code influenced by an untrusted pull request, or publish pull request content into a trusted location or environment. In that case, they can bypass the assumptions many teams make about pull request approval gates and create an avoidable CI/CD exposure. + +## What we changed + +Following Aviv's report, we removed the affected pattern from this repository's pull request preview workflow. + +This was the last remaining instance of this pattern in our GitHub organization. We had already phased it out elsewhere some time ago, and it has now been fully removed here as well. + +## Scope and impact + +The issue was limited to preview environments for this documentation repository. + +- There is no indication the issue was ever exploited +- We do not believe this had broader production impact +- The overall severity was low, but the pattern itself was still worth eliminating + +## Why this matters + +This is one of those edge cases that is easy to miss because the workflow may still look "approved" or "maintainer-controlled" at first glance. The security boundary is different for `pull_request_target`, and that difference matters when the workflow executes anything influenced by untrusted pull request content. + +The practical takeaway is simple: `pull_request_target` can be a convenient choice for labeling pull requests. Beyond that, it should be used only when all executed code is strictly from a trusted and protected branch. Do not use it to execute code influenced by an untrusted pull request, or to publish pull request content into a trusted location or environment. + +## Guidance for customers and the community + +Because this repository is our documentation site, we are also using this as an opportunity to point customers and the community to the upstream guidance we recommend reviewing: + +- [GitHub Actions: `pull_request_target` event](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) +- [GitHub Actions security hardening](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions) +- [GitHub Security Lab: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) + +We appreciate researchers like Aviv who take the time to surface issues like this clearly and responsibly. In this case, the report was thorough, accurate, and directly actionable. diff --git a/examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml b/examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml deleted file mode 100644 index a74ff6e8d..000000000 --- a/examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: 👽 Atmos Terraform Plan -run-name: 👽 Atmos Terraform Plan - -on: - pull_request_target: - types: - - opened - - synchronize - - reopened - branches: - - main - -permissions: - id-token: write - contents: read - -jobs: - atmos-affected: - if: ${{ !contains( github.event.pull_request.labels.*.name, 'no-plan') }} - name: Determine Affected Stacks - runs-on: - - runs-on=${{ github.run_id }} - - runner=terraform - - extras=s3-cache - - private=false - steps: - - id: affected - uses: cloudposse/github-action-atmos-affected-stacks@v4 - with: - atmos-version: ${{ vars.ATMOS_VERSION }} - atmos-config-path: ${{ vars.ATMOS_CONFIG_PATH }} - base-ref: ${{ github.event.pull_request.base.sha }} - head-ref: ${{ github.event.pull_request.head.sha }} - outputs: - stacks: ${{ steps.affected.outputs.matrix }} - has-affected-stacks: ${{ steps.affected.outputs.has-affected-stacks }} - - atmos-plan: - needs: ["atmos-affected"] - if: ${{ needs.atmos-affected.outputs.has-affected-stacks == 'true' }} - name: Plan (${{ matrix.name }}) - uses: ./.github/workflows/atmos-terraform-plan-matrix.yaml - strategy: - matrix: ${{ fromJson(needs.atmos-affected.outputs.stacks) }} - max-parallel: 1 # This is important to avoid ddos GHA API - fail-fast: false # Don't fail fast to avoid locking TF State - with: - stacks: ${{ matrix.items }} - atmos-version: ${{ vars.ATMOS_VERSION }} - atmos-config-path: ${{ vars.ATMOS_CONFIG_PATH }} - sha: ${{ github.event.pull_request.head.sha }} - secrets: inherit - diff --git a/package-lock.json b/package-lock.json index 78445fdae..8a2b882a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -284,6 +284,7 @@ "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.46.2.tgz", "integrity": "sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/client-common": "5.46.2", "@algolia/requester-browser-xhr": "5.46.2", @@ -525,6 +526,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2380,6 +2382,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2402,6 +2405,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2511,6 +2515,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2932,6 +2937,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -3748,6 +3754,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz", "integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==", "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/babel": "3.9.2", "@docusaurus/bundler": "3.9.2", @@ -3983,6 +3990,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -4265,6 +4273,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz", "integrity": "sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA==", "license": "MIT", + "peer": true, "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/logger": "3.9.2", @@ -4510,6 +4519,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" }, @@ -4897,6 +4907,7 @@ "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==", "license": "MIT", + "peer": true, "dependencies": { "@types/mdx": "^2.0.0" }, @@ -5224,6 +5235,7 @@ "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -6101,6 +6113,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -6465,6 +6478,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6550,6 +6564,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6595,6 +6610,7 @@ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.46.2.tgz", "integrity": "sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q==", "license": "MIT", + "peer": true, "dependencies": { "@algolia/abtesting": "1.12.2", "@algolia/client-abtesting": "5.46.2", @@ -7231,6 +7247,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8293,6 +8310,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8616,6 +8634,7 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -9025,6 +9044,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -10320,6 +10340,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -15404,6 +15425,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -15994,6 +16016,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -16897,6 +16920,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -17801,6 +17825,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -17874,6 +17899,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -17886,6 +17912,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -18001,7 +18028,8 @@ "version": "3.5.1", "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.1.tgz", "integrity": "sha512-yM4PyeHuwhIOUHNJxi1/Mbq8kVLv4AkyE7IYLP/LK0lIFcr3tRa2H1iZlBYKIxOlf+/jruBTe8DdKSyQX9w4OA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-hubspot-form/node_modules/stylis-rule-sheet": { "version": "0.0.10", @@ -18045,6 +18073,7 @@ "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" }, @@ -18089,6 +18118,7 @@ "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.13", "history": "^4.9.0", @@ -18836,6 +18866,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", "license": "MIT", + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -18890,6 +18921,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -19108,6 +19140,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.1.0.tgz", "integrity": "sha512-74Wpe+hhPx4V8NFe00I2Fu9gTJopKoH5vE7nCqFzVgKOXV8AnN23T58K79QLYQotzGpH93UZ+UN2Y11j9huZJg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -20262,7 +20295,8 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tunnel-agent": { "version": "0.6.0", @@ -20693,6 +20727,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -20982,6 +21017,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -21604,6 +21640,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From 138888d51a190e3d3aac3f48e7fc7e8f0ecdd83b Mon Sep 17 00:00:00 2001 From: Erik Osterman Date: Mon, 13 Apr 2026 16:13:08 -0500 Subject: [PATCH 2/5] Remove legacy atmos terraform plan references --- .../migrate-from-github-actions-gitops.mdx | 1 - docs/layers/gitops/example-workflows.mdx | 17 ----------------- .../layers/gitops/example-workflows.mdx | 17 ----------------- 3 files changed, 35 deletions(-) diff --git a/docs/layers/atmos-pro/tutorials/migrate-from-github-actions-gitops.mdx b/docs/layers/atmos-pro/tutorials/migrate-from-github-actions-gitops.mdx index f1a26429f..3147896a7 100644 --- a/docs/layers/atmos-pro/tutorials/migrate-from-github-actions-gitops.mdx +++ b/docs/layers/atmos-pro/tutorials/migrate-from-github-actions-gitops.mdx @@ -80,7 +80,6 @@ Before removing the legacy workflows: Once Atmos Pro is working correctly, remove the legacy workflow files: -1. `.github/workflows/atmos-terraform-plan.yaml` 1. `.github/workflows/atmos-terraform-apply.yaml` 1. `.github/workflows/atmos-terraform-drift-detection.yaml` 1. `.github/workflows/atmos-terraform-drift-remediation.yaml` diff --git a/docs/layers/gitops/example-workflows.mdx b/docs/layers/gitops/example-workflows.mdx index 7ace41405..ba4819cdf 100644 --- a/docs/layers/gitops/example-workflows.mdx +++ b/docs/layers/gitops/example-workflows.mdx @@ -6,7 +6,6 @@ sidebar_position: 2 import Intro from '@site/src/components/Intro'; import CodeBlock from '@theme/CodeBlock'; import CollapsibleText from '@site/src/components/CollapsibleText'; -import PartialAtmosTerraformPlan from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml'; import PartialAtmosTerraformApply from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-apply.yaml'; import PartialAtmosTerraformDispatch from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-dispatch.yaml'; import PartialAtmosTerraformDriftDetection from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-drift-detection.yaml'; @@ -28,22 +27,6 @@ This content is preserved for users with existing GitHub Actions GitOps deployme The following GitHub Workflows should be used as examples. These are created in a given Infrastructure repository and can be modified however best suites your needs. For example, the labels we've chosen for triggering or skipping workflows are noted here as "Conventions" but can be changed however you would prefer. -### Atmos Terraform Plan - -:::info Conventions - -Use the `no-plan` label on a Pull Request to skip this workflow. - -::: - -The Atmos Terraform Plan workflow is triggered for every affected component from the Atmos Describe Affected workflow. This workflow takes a matrix of components and stacks and creates a plan for each, using the [Atmos Terraform Plan composite action](https://github.com/cloudposse/github-action-atmos-terraform-plan). For more on the Atmos Terraform Plan composite action, see [the official atmos.tools documentation](https://atmos.tools/integrations/github-actions/atmos-terraform-plan). - -If an affected component is disabled with `terraform.settings.github.actions_enabled`, the component will show up as affected but all Terraform steps will be skipped. See [Enabling or disabling components](#enabling-or-disabling-components). - - - {PartialAtmosTerraformPlan} - - ### Atmos Terraform Apply :::info Conventions diff --git a/versioned_docs/version-v1/layers/gitops/example-workflows.mdx b/versioned_docs/version-v1/layers/gitops/example-workflows.mdx index b9a386fac..97ea0f616 100644 --- a/versioned_docs/version-v1/layers/gitops/example-workflows.mdx +++ b/versioned_docs/version-v1/layers/gitops/example-workflows.mdx @@ -6,7 +6,6 @@ sidebar_position: 2 import Intro from '@site/src/components/Intro'; import CodeBlock from '@theme/CodeBlock'; import CollapsibleText from '@site/src/components/CollapsibleText'; -import PartialAtmosTerraformPlan from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml'; import PartialAtmosTerraformApply from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-apply.yaml'; import PartialAtmosTerraformDispatch from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-dispatch.yaml'; import PartialAtmosTerraformDriftDetection from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-drift-detection.yaml'; @@ -20,22 +19,6 @@ import PartialAtmosTerraformApplyMatrix from '@site/examples/legacy/snippets/.gi The following GitHub Workflows should be used as examples. These are created in a given Infrastructure repository and can be modified however best suites your needs. For example, the labels we've chosen for triggering or skipping workflows are noted here as "Conventions" but can be changed however you would prefer. -### Atmos Terraform Plan - -:::info Conventions - -Use the `no-plan` label on a Pull Request to skip this workflow. - -::: - -The Atmos Terraform Plan workflow is triggered for every affected component from the Atmos Describe Affected workflow. This workflow takes a matrix of components and stacks and creates a plan for each, using the [Atmos Terraform Plan composite action](https://github.com/cloudposse/github-action-atmos-terraform-plan). For more on the Atmos Terraform Plan composite action, see [the official atmos.tools documentation](https://atmos.tools/integrations/github-actions/atmos-terraform-plan). - -If an affected component is disabled with `terraform.settings.github.actions_enabled`, the component will show up as affected but all Terraform steps will be skipped. See [Enabling or disabling components](#enabling-or-disabling-components). - - - {PartialAtmosTerraformPlan} - - ### Atmos Terraform Apply :::info Conventions From 085a1842657894a0b597c10c46ad4112c6a2d196 Mon Sep 17 00:00:00 2001 From: Erik Osterman Date: Mon, 13 Apr 2026 16:39:05 -0500 Subject: [PATCH 3/5] Point legacy GitOps docs to Atmos Native CI --- docs/layers/gitops/example-workflows.mdx | 2 +- docs/layers/gitops/faq.mdx | 4 ++-- docs/layers/gitops/gitops.mdx | 8 +++----- docs/layers/gitops/setup.mdx | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/layers/gitops/example-workflows.mdx b/docs/layers/gitops/example-workflows.mdx index ba4819cdf..4e28ee0fc 100644 --- a/docs/layers/gitops/example-workflows.mdx +++ b/docs/layers/gitops/example-workflows.mdx @@ -16,7 +16,7 @@ import PartialAtmosTerraformApplyMatrix from '@site/examples/legacy/snippets/.gi :::warning Deprecated These example workflows are for the legacy GitHub Actions GitOps approach. -**The recommended approach now uses [Atmos Pro](/layers/atmos-pro/)**, which provides these workflows out-of-the-box with no custom configuration required. +For current examples using Atmos Native CI, see [`cloudposse-examples/atmos-native-ci`](https://github.com/cloudposse-examples/atmos-native-ci) and the [Atmos CI documentation](https://atmos.tools/ci). This content is preserved for users with existing GitHub Actions GitOps deployments. ::: diff --git a/docs/layers/gitops/faq.mdx b/docs/layers/gitops/faq.mdx index 5a8aa4958..62782b15a 100644 --- a/docs/layers/gitops/faq.mdx +++ b/docs/layers/gitops/faq.mdx @@ -7,7 +7,7 @@ sidebar_position: 10 :::warning Deprecated This FAQ is for the legacy GitHub Actions GitOps approach. -**The recommended approach now uses [Atmos Pro](/layers/atmos-pro/)**. +For current examples using Atmos Native CI, see [`cloudposse-examples/atmos-native-ci`](https://github.com/cloudposse-examples/atmos-native-ci) and the [Atmos CI documentation](https://atmos.tools/ci). This content is preserved for users with existing GitHub Actions GitOps deployments. ::: @@ -75,4 +75,4 @@ To resolve this error, thoroughly read through each of the [Authentication Prere ### How does GitHub OIDC work with AWS? -Please see [How to use GitHub OIDC with AWS](/layers/github-actions/github-oidc-with-aws) \ No newline at end of file +Please see [How to use GitHub OIDC with AWS](/layers/github-actions/github-oidc-with-aws) diff --git a/docs/layers/gitops/gitops.mdx b/docs/layers/gitops/gitops.mdx index 6982ea7a9..9de8acf98 100644 --- a/docs/layers/gitops/gitops.mdx +++ b/docs/layers/gitops/gitops.mdx @@ -13,10 +13,7 @@ import CodeBlock from '@theme/CodeBlock'; :::warning Deprecated This documentation describes the legacy GitHub Actions GitOps approach for Terraform automation. -**The recommended approach now uses [Atmos Pro](/layers/atmos-pro/)**, which provides: -- Integrated plan/apply workflows with no custom configuration -- Built-in drift detection and remediation -- Seamless integration with AWS SSO via `iam-role` component +For current examples using Atmos Native CI, see [`cloudposse-examples/atmos-native-ci`](https://github.com/cloudposse-examples/atmos-native-ci) and the [Atmos CI documentation](https://atmos.tools/ci). This content is preserved for users with existing GitHub Actions GitOps deployments. ::: @@ -65,6 +62,8 @@ Once the required S3 Bucket, DynamoDB table, and two separate roles to access Te ## References - [Setup Documentation](/layers/gitops/setup) +- [Atmos CI documentation](https://atmos.tools/ci) +- [`cloudposse-examples/atmos-native-ci`](https://github.com/cloudposse-examples/atmos-native-ci) - [Atmos integration documentation](https://atmos.tools/category/integrations/github-actions). - [GitHub OIDC Integration with AWS](/layers/github-actions/github-oidc-with-aws) - [`cloudposse/github-action-atmos-terraform-plan`](https://github.com/cloudposse/github-action-atmos-terraform-plan) @@ -74,4 +73,3 @@ Once the required S3 Bucket, DynamoDB table, and two separate roles to access Te - [`gitops/s3-bucket`](/components/library/aws/s3-bucket/): Deploy a S3 Bucket using the `s3-bucket` component. This bucket holds Terraform planfiles. - [`gitops/dynamodb`](/components/library/aws/dynamodb/): Deploy a DynamoDB table using the `dynamodb` component. This table is used to hold metadata for Terraform Plans - [`github-oidc-role`](/components/library/aws/github-oidc-role/): Deploys an IAM Role that GitHub is able to assume via GitHub OIDC. This role has access to the bucket and table for planfiles. - diff --git a/docs/layers/gitops/setup.mdx b/docs/layers/gitops/setup.mdx index 36b1ed8ce..e5e2355e4 100644 --- a/docs/layers/gitops/setup.mdx +++ b/docs/layers/gitops/setup.mdx @@ -16,7 +16,7 @@ import CodeBlock from '@theme/CodeBlock'; :::warning Deprecated This documentation describes the legacy GitHub Actions GitOps setup. -**The recommended approach now uses [Atmos Pro](/layers/atmos-pro/)**, which provides integrated Terraform automation with no custom workflow configuration required. +For current examples using Atmos Native CI, see [`cloudposse-examples/atmos-native-ci`](https://github.com/cloudposse-examples/atmos-native-ci) and the [Atmos CI documentation](https://atmos.tools/ci). This content is preserved for users with existing GitHub Actions GitOps deployments. ::: From c3679a4564d6169a20e2df858dfdcb96d65ec8c3 Mon Sep 17 00:00:00 2001 From: Erik Osterman Date: Mon, 13 Apr 2026 19:30:07 -0500 Subject: [PATCH 4/5] Rewrite legacy plan workflow to use pull_request trigger and environment Instead of deleting the legacy workflow, rewrite it to demonstrate the secure pattern: use `pull_request` instead of `pull_request_target`, add a `plan` environment for approval gating, and use `github.sha` instead of the PR head ref. Co-Authored-By: Claude Opus 4.6 --- .../workflows/atmos-terraform-plan.yaml | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml diff --git a/examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml b/examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml new file mode 100644 index 000000000..fac019c16 --- /dev/null +++ b/examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml @@ -0,0 +1,53 @@ +name: 👽 Atmos Terraform Plan +run-name: 👽 Atmos Terraform Plan + +on: + pull_request: + types: + - opened + - synchronize + - reopened + branches: + - main + +permissions: + id-token: write + contents: read + +jobs: + atmos-affected: + if: ${{ !contains( github.event.pull_request.labels.*.name, 'no-plan') }} + name: Determine Affected Stacks + environment: plan + runs-on: + - runs-on=${{ github.run_id }} + - runner=terraform + - extras=s3-cache + - private=false + steps: + - id: affected + uses: cloudposse/github-action-atmos-affected-stacks@v4 + with: + atmos-version: ${{ vars.ATMOS_VERSION }} + atmos-config-path: ${{ vars.ATMOS_CONFIG_PATH }} + base-ref: ${{ github.event.pull_request.base.sha }} + head-ref: ${{ github.sha }} + outputs: + stacks: ${{ steps.affected.outputs.matrix }} + has-affected-stacks: ${{ steps.affected.outputs.has-affected-stacks }} + + atmos-plan: + needs: ["atmos-affected"] + if: ${{ needs.atmos-affected.outputs.has-affected-stacks == 'true' }} + name: Plan (${{ matrix.name }}) + uses: ./.github/workflows/atmos-terraform-plan-matrix.yaml + strategy: + matrix: ${{ fromJson(needs.atmos-affected.outputs.stacks) }} + max-parallel: 1 # This is important to avoid ddos GHA API + fail-fast: false # Don't fail fast to avoid locking TF State + with: + stacks: ${{ matrix.items }} + atmos-version: ${{ vars.ATMOS_VERSION }} + atmos-config-path: ${{ vars.ATMOS_CONFIG_PATH }} + sha: ${{ github.sha }} + secrets: inherit From 043a627ae2866e93a9ef13835b72e215ed4ed95c Mon Sep 17 00:00:00 2001 From: Erik Osterman Date: Mon, 13 Apr 2026 20:49:01 -0500 Subject: [PATCH 5/5] Point legacy GitOps docs to Atmos Native CI Restore the Atmos Terraform Plan section in example-workflows.mdx now that the workflow has been rewritten to use pull_request + environment instead of pull_request_target. Co-Authored-By: Claude Opus 4.6 --- docs/layers/gitops/example-workflows.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/layers/gitops/example-workflows.mdx b/docs/layers/gitops/example-workflows.mdx index 4e28ee0fc..e3d0f4056 100644 --- a/docs/layers/gitops/example-workflows.mdx +++ b/docs/layers/gitops/example-workflows.mdx @@ -6,6 +6,7 @@ sidebar_position: 2 import Intro from '@site/src/components/Intro'; import CodeBlock from '@theme/CodeBlock'; import CollapsibleText from '@site/src/components/CollapsibleText'; +import PartialAtmosTerraformPlan from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml'; import PartialAtmosTerraformApply from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-apply.yaml'; import PartialAtmosTerraformDispatch from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-dispatch.yaml'; import PartialAtmosTerraformDriftDetection from '@site/examples/legacy/snippets/.github/workflows/atmos-terraform-drift-detection.yaml'; @@ -27,6 +28,22 @@ This content is preserved for users with existing GitHub Actions GitOps deployme The following GitHub Workflows should be used as examples. These are created in a given Infrastructure repository and can be modified however best suites your needs. For example, the labels we've chosen for triggering or skipping workflows are noted here as "Conventions" but can be changed however you would prefer. +### Atmos Terraform Plan + +:::info Conventions + +Use the `no-plan` label on a Pull Request to skip this workflow. + +::: + +The Atmos Terraform Plan workflow is triggered for every affected component from the Atmos Describe Affected workflow. This workflow takes a matrix of components and stacks and creates a plan for each, using the [Atmos Terraform Plan composite action](https://github.com/cloudposse/github-action-atmos-terraform-plan). For more on the Atmos Terraform Plan composite action, see [the official atmos.tools documentation](https://atmos.tools/integrations/github-actions/atmos-terraform-plan). + +If an affected component is disabled with `terraform.settings.github.actions_enabled`, the component will show up as affected but all Terraform steps will be skipped. See [Enabling or disabling components](#enabling-or-disabling-components). + + + {PartialAtmosTerraformPlan} + + ### Atmos Terraform Apply :::info Conventions