Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,29 @@ Trusted publishing/OIDC removes long-lived static npm tokens, but it does not ma

Provenance answers **which workflow built this artifact**, not **whether the workflow, source tree, cache, or build steps were clean**.<sup>[[1]](#references)[[2]](#references)[[13]](#references)[[15]](#references)</sup>

### Build-to-publish privilege crossing

A release job that installs dependencies and publishes in the same job gives install-time code the job's identity. In this abbreviated workflow, a malicious `postinstall` script reached by `npm ci` inherits the workflow-level `NODE_AUTH_TOKEN` and can request an OIDC token because `id-token: write` applies to the whole job, not just the publish step.<sup>[[19]](#references)[[21]](#references)</sup>

```yaml
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
jobs:
release:
permissions:
contents: read
id-token: write
steps:
- run: npm ci
- run: npm publish
```

`npm ci` requires an existing `package-lock.json` or `npm-shrinkwrap.json`; without one, this example fails rather than resolving a fresh malicious version. The poisoned version must be selected by the lockfile for this exact install path to run it.<sup>[[20]](#references)</sup>

Moving `NODE_AUTH_TOKEN` to the `npm publish` step removes it from the install step, but **does not protect the publish command** if the attacker controls the package's publish-time scripts.<sup>[[19]](#references)</sup> npm runs `prepublishOnly`, `prepack`, `prepare`, `postpack`, `publish`, and `postpublish` around `npm publish`, so a token-bearing publish step remains an execution surface.<sup>[[6]](#references)</sup> Step-scoped environment variables also do not narrow `id-token: write`, which GitHub supports at workflow or job scope, not step scope.<sup>[[21]](#references)</sup>

When assessing a release workflow, trace whether dependency installation, tests, artifact creation, and publication share a job with `id-token: write` or registry secrets.<sup>[[19]](#references)</sup> A separate build job with no publisher identity removes those credentials and OIDC request access from install-time code in that job; the privileged publish job can consume its output via an artifact.<sup>[[19]](#references)</sup> The artifact is still untrusted input: a malicious package or publish-time script in it can execute with the publisher identity. See [Artifact Poisoning](./#artifact-poisoning) for the cross-job trust boundary and [AWS - Federation Abuse](../../../pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md#oidc---github-actions-abuse) for role-ARN discovery when the trusted provider is AWS.<sup>[[14]](#references)[[19]](#references)</sup>

ChainDrop implemented a repository-gated path that injected a Git-pinned `@opensearch/setup` dependency imitating the legitimate `@opensearch-project` scope. Review dependency diffs even when lifecycle scripts do not change.<sup>[[13]](#references)</sup>

High-signal review points:
Expand Down Expand Up @@ -221,5 +244,8 @@ Useful behavior-level signals from the same chain include:<sup>[[18]](#reference
- [16] [Malicious key-derivation telemetry commit in InjectiveLabs/injective-ts](https://github.com/InjectiveLabs/injective-ts/commit/01219285b16ce85c70cdf47a71a551ff5e41f1ed)
- [17] [Not-So-Anonymous Telemetry: The @injectivelabs/sdk-ts Backdoor](https://securitylabs.datadoghq.com/articles/not-so-anonymous-telemetry-injectivelabs-sdk-ts-backdoor)
- [18] [NPM Worm Compromises Hundreds of Popular npm Packages](https://securitylabs.datadoghq.com/articles/npm-worm-compromises-popular-npm-packages)
- [19] [CI/CD Protected Build Jobs: Isolating Build and Publish Phases](https://labs.sra.io/posts/cicd_protected_build_jobs)
- [20] [npm-ci | npm Docs](https://docs.npmjs.com/cli/v11/commands/npm-ci/)
- [21] [Workflow syntax for GitHub Actions | GitHub Docs](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)

{{#include ../../../banners/hacktricks-training.md}}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ GitHub Actions can exchange a short-lived OIDC token for temporary AWS credentia
```
The `sub` values limit the role to the selected repository, pull-request runs, and the `main` branch ref. The role's permissions policy should independently enforce least privilege.<sup>[[1]](#references)[[4]](#references)[[6]](#references)</sup>
6. The role ARN is the identifier consumed by the action; the example stores it in the `READ_ROLE` secret. An ARN is not itself a credential, but an environment-scoped secret can be used when the workflow also uses protected environment rules.<sup>[[1]](#references)[[6]](#references)</sup>
- A compromised job with `id-token: write` still needs the target role ARN to exchange its GitHub OIDC token for AWS credentials.<sup>[[4]](#references)[[13]](#references)</sup> If the repository is checked out, a literal `role-to-assume` value may be visible in `.github/workflows/`; the ARN may also be in a job environment variable.<sup>[[4]](#references)[[13]](#references)</sup> A workflow reference to a secret reveals only the secret name, not its value.<sup>[[14]](#references)</sup> The ARN is not a security boundary: the IAM trust policy decides whether that workflow identity can assume the role.<sup>[[4]](#references)[[13]](#references)</sup> See [Build-to-publish privilege crossing](../../../pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md#build-to-publish-privilege-crossing) for the related npm job boundary.
7. Use `aws-actions/configure-aws-credentials` to exchange the GitHub OIDC token for temporary AWS credentials.<sup>[[4]](#references)[[7]](#references)</sup> The following workflow is an example:

```yaml
Expand Down Expand Up @@ -141,5 +142,7 @@ An explicit `sub` condition prevents other service accounts in the cluster or na
- [10] [IAM Roles for Service Accounts - eksctl](https://docs.aws.amazon.com/eks/latest/eksctl/iamserviceaccounts.html)
- [11] [Assign IAM roles to Kubernetes service accounts](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html)
- [12] [Get started with Amazon EKS - eksctl](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html)
- [13] [CI/CD Protected Build Jobs: Isolating Build and Publish Phases](https://labs.sra.io/posts/cicd_protected_build_jobs)
- [14] [Secrets - GitHub Docs](https://docs.github.com/en/actions/concepts/security/secrets)

{{#include ../../../banners/hacktricks-training.md}}
Loading