From 2f4f40b31238dd81b844aa99e07b0e0f585d6f6e Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Mon, 19 Oct 2026 01:23:24 +0000 Subject: [PATCH 1/2] Add content from: CI/CD Protected Build Jobs: Isolating Build and Publish Phas... --- .../gh-actions-npm-supply-chain-abuse.md | 26 +++++++++++++++++++ .../aws-federation-abuse.md | 2 ++ 2 files changed, 28 insertions(+) diff --git a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md index 89a0f54c91..75acaaf0d0 100644 --- a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md +++ b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md @@ -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**.[[1]](#references)[[2]](#references)[[13]](#references)[[15]](#references) +### 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.[[19]](#references)[[21]](#references) + +```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.[[20]](#references) + +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.[[19]](#references) npm runs `prepublishOnly`, `prepack`, `prepare`, `postpack`, `publish`, and `postpublish` around `npm publish`, so a token-bearing publish step remains an execution surface.[[6]](#references) Step-scoped environment variables also do not narrow `id-token: write`, which GitHub supports at workflow or job scope, not step scope.[[21]](#references) + +When assessing a release workflow, trace whether dependency installation, tests, artifact creation, and publication share a job with `id-token: write` or registry secrets.[[19]](#references) 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.[[19]](#references) 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.[[14]](#references)[[19]](#references) + 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.[[13]](#references) High-signal review points: @@ -221,5 +244,8 @@ Useful behavior-level signals from the same chain include:[[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}} diff --git a/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md b/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md index 76e23c7759..dc4e8a840c 100644 --- a/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md +++ b/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md @@ -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.[[1]](#references)[[4]](#references)[[6]](#references) 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.[[1]](#references)[[6]](#references) + - For a compromised job with `id-token: write`, the role ARN is an additional input needed to exchange its GitHub OIDC token for AWS credentials. If the job checks out its repository, inspect `.github/workflows/` for `role-to-assume`; an ARN may also be passed through job environment variables. The ARN is not a security boundary: the IAM trust policy, not secrecy of the role name, decides whether that workflow identity may assume it. For the matching npm build/publish trust boundary, 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).[[4]](#references)[[13]](#references) 7. Use `aws-actions/configure-aws-credentials` to exchange the GitHub OIDC token for temporary AWS credentials.[[4]](#references)[[7]](#references) The following workflow is an example: ```yaml @@ -141,5 +142,6 @@ 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) {{#include ../../../banners/hacktricks-training.md}} From 6d066f32aa6558ab1ecb3a928c7c449ad624415e Mon Sep 17 00:00:00 2001 From: SirBroccoli Date: Mon, 19 Oct 2026 03:41:44 +0200 Subject: [PATCH 2/2] docs: repair PR placement and formatting after editorial review --- .../aws-security/aws-basic-information/aws-federation-abuse.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md b/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md index dc4e8a840c..5cd6edefc3 100644 --- a/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md +++ b/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md @@ -42,7 +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.[[1]](#references)[[4]](#references)[[6]](#references) 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.[[1]](#references)[[6]](#references) - - For a compromised job with `id-token: write`, the role ARN is an additional input needed to exchange its GitHub OIDC token for AWS credentials. If the job checks out its repository, inspect `.github/workflows/` for `role-to-assume`; an ARN may also be passed through job environment variables. The ARN is not a security boundary: the IAM trust policy, not secrecy of the role name, decides whether that workflow identity may assume it. For the matching npm build/publish trust boundary, 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).[[4]](#references)[[13]](#references) + - A compromised job with `id-token: write` still needs the target role ARN to exchange its GitHub OIDC token for AWS credentials.[[4]](#references)[[13]](#references) 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.[[4]](#references)[[13]](#references) A workflow reference to a secret reveals only the secret name, not its value.[[14]](#references) The ARN is not a security boundary: the IAM trust policy decides whether that workflow identity can assume the role.[[4]](#references)[[13]](#references) 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.[[4]](#references)[[7]](#references) The following workflow is an example: ```yaml @@ -143,5 +143,6 @@ An explicit `sub` condition prevents other service accounts in the cluster or na - [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}}