Skip to content

Commit 9197a7f

Browse files
ostermanclaude
andauthored
Add CI security blog post and remove legacy pull_request_target workflow (#900)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 03dff3c commit 9197a7f

9 files changed

Lines changed: 102 additions & 34 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
title: "Security Update: Hardening Pull Request Preview Workflows"
3+
slug: pr-preview-workflow-hardening
4+
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."
5+
authors: [cloudposse]
6+
tags: [security, github-actions, ci-cd, docs]
7+
date: 2026-04-13
8+
---
9+
import Intro from '@site/src/components/Intro';
10+
11+
<Intro>
12+
We removed a <code>pull_request_target</code>-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.
13+
</Intro>
14+
15+
<!--truncate-->
16+
17+
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.
18+
19+
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.
20+
21+
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.
22+
23+
## What we changed
24+
25+
Following Aviv's report, we removed the affected pattern from this repository's pull request preview workflow.
26+
27+
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.
28+
29+
## Scope and impact
30+
31+
The issue was limited to preview environments for this documentation repository.
32+
33+
- There is no indication the issue was ever exploited
34+
- We do not believe this had broader production impact
35+
- The overall severity was low, but the pattern itself was still worth eliminating
36+
37+
## Why this matters
38+
39+
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.
40+
41+
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.
42+
43+
## Guidance for customers and the community
44+
45+
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:
46+
47+
- [GitHub Actions: `pull_request_target` event](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target)
48+
- [GitHub Actions security hardening](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)
49+
- [GitHub Security Lab: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
50+
51+
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.

docs/layers/atmos-pro/tutorials/migrate-from-github-actions-gitops.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ Before removing the legacy workflows:
8080
Once Atmos Pro is working correctly, remove the legacy workflow files:
8181

8282
<Steps>
83-
1. `.github/workflows/atmos-terraform-plan.yaml`
8483
1. `.github/workflows/atmos-terraform-apply.yaml`
8584
1. `.github/workflows/atmos-terraform-drift-detection.yaml`
8685
1. `.github/workflows/atmos-terraform-drift-remediation.yaml`

docs/layers/gitops/example-workflows.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import PartialAtmosTerraformApplyMatrix from '@site/examples/legacy/snippets/.gi
1717
:::warning Deprecated
1818
These example workflows are for the legacy GitHub Actions GitOps approach.
1919

20-
**The recommended approach now uses [Atmos Pro](/layers/atmos-pro/)**, which provides these workflows out-of-the-box with no custom configuration required.
20+
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).
2121

2222
This content is preserved for users with existing GitHub Actions GitOps deployments.
2323
:::

docs/layers/gitops/faq.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ sidebar_position: 10
77
:::warning Deprecated
88
This FAQ is for the legacy GitHub Actions GitOps approach.
99

10-
**The recommended approach now uses [Atmos Pro](/layers/atmos-pro/)**.
10+
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).
1111

1212
This content is preserved for users with existing GitHub Actions GitOps deployments.
1313
:::
@@ -75,4 +75,4 @@ To resolve this error, thoroughly read through each of the [Authentication Prere
7575

7676
### How does GitHub OIDC work with AWS?
7777

78-
Please see [How to use GitHub OIDC with AWS](/layers/github-actions/github-oidc-with-aws)
78+
Please see [How to use GitHub OIDC with AWS](/layers/github-actions/github-oidc-with-aws)

docs/layers/gitops/gitops.mdx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ import CodeBlock from '@theme/CodeBlock';
1313
:::warning Deprecated
1414
This documentation describes the legacy GitHub Actions GitOps approach for Terraform automation.
1515

16-
**The recommended approach now uses [Atmos Pro](/layers/atmos-pro/)**, which provides:
17-
- Integrated plan/apply workflows with no custom configuration
18-
- Built-in drift detection and remediation
19-
- Seamless integration with AWS SSO via `iam-role` component
16+
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).
2017

2118
This content is preserved for users with existing GitHub Actions GitOps deployments.
2219
:::
@@ -65,6 +62,8 @@ Once the required S3 Bucket, DynamoDB table, and two separate roles to access Te
6562
## References
6663

6764
- [Setup Documentation](/layers/gitops/setup)
65+
- [Atmos CI documentation](https://atmos.tools/ci)
66+
- [`cloudposse-examples/atmos-native-ci`](https://github.com/cloudposse-examples/atmos-native-ci)
6867
- [Atmos integration documentation](https://atmos.tools/category/integrations/github-actions).
6968
- [GitHub OIDC Integration with AWS](/layers/github-actions/github-oidc-with-aws)
7069
- [`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
7473
- [`gitops/s3-bucket`](/components/library/aws/s3-bucket/): Deploy a S3 Bucket using the `s3-bucket` component. This bucket holds Terraform planfiles.
7574
- [`gitops/dynamodb`](/components/library/aws/dynamodb/): Deploy a DynamoDB table using the `dynamodb` component. This table is used to hold metadata for Terraform Plans
7675
- [`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.
77-

docs/layers/gitops/setup.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import CodeBlock from '@theme/CodeBlock';
1616
:::warning Deprecated
1717
This documentation describes the legacy GitHub Actions GitOps setup.
1818

19-
**The recommended approach now uses [Atmos Pro](/layers/atmos-pro/)**, which provides integrated Terraform automation with no custom workflow configuration required.
19+
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).
2020

2121
This content is preserved for users with existing GitHub Actions GitOps deployments.
2222
:::

examples/legacy/snippets/.github/workflows/atmos-terraform-plan.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: 👽 Atmos Terraform Plan
22
run-name: 👽 Atmos Terraform Plan
33

44
on:
5-
pull_request_target:
5+
pull_request:
66
types:
77
- opened
88
- synchronize
@@ -18,7 +18,8 @@ jobs:
1818
atmos-affected:
1919
if: ${{ !contains( github.event.pull_request.labels.*.name, 'no-plan') }}
2020
name: Determine Affected Stacks
21-
runs-on:
21+
environment: plan
22+
runs-on:
2223
- runs-on=${{ github.run_id }}
2324
- runner=terraform
2425
- extras=s3-cache
@@ -30,7 +31,7 @@ jobs:
3031
atmos-version: ${{ vars.ATMOS_VERSION }}
3132
atmos-config-path: ${{ vars.ATMOS_CONFIG_PATH }}
3233
base-ref: ${{ github.event.pull_request.base.sha }}
33-
head-ref: ${{ github.event.pull_request.head.sha }}
34+
head-ref: ${{ github.sha }}
3435
outputs:
3536
stacks: ${{ steps.affected.outputs.matrix }}
3637
has-affected-stacks: ${{ steps.affected.outputs.has-affected-stacks }}
@@ -48,6 +49,5 @@ jobs:
4849
stacks: ${{ matrix.items }}
4950
atmos-version: ${{ vars.ATMOS_VERSION }}
5051
atmos-config-path: ${{ vars.ATMOS_CONFIG_PATH }}
51-
sha: ${{ github.event.pull_request.head.sha }}
52+
sha: ${{ github.sha }}
5253
secrets: inherit
53-

0 commit comments

Comments
 (0)