> ## 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-2 — Long-lived personal access token in workflow

> Workflows authenticating with a PAT secret instead of GITHUB_TOKEN or OIDC use static, long-lived identities.

| Field    | Value                                                                                                                                                                    |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Category | `CICD-SEC-2`                                                                                                                                                             |
| Severity | **MEDIUM**                                                                                                                                                               |
| OWASP    | [CICD-SEC-2: Inadequate Identity and Access Management](https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-02-Inadequate-Identity-and-Access-Management) |
| Auto-fix | ✗                                                                                                                                                                        |

## What the check does

Flags any workflow that authenticates with a secret whose name implies a long-lived personal access token. The check walks every `env:` block (workflow, job, step) and every step `with:` input, looking for `${{ secrets.NAME }}` references where `NAME` matches any of:

* `*_PAT` or `PAT_*`
* `*_PERSONAL_ACCESS_TOKEN` (any casing or separator)
* `*_GH_TOKEN` / `*_GH_PAT` / `*_GITHUB_PAT`

The literal `GITHUB_TOKEN` is **not** flagged — that's the short-lived per-run token GitHub mints automatically, which is the recommended replacement.

## Why it matters

A PAT is a static credential bound to a single human account. It:

* Survives employee turnover (until someone notices and revokes it manually).
* Carries the user's full set of repo permissions, not the workflow's least-privilege scope.
* Is difficult to rotate — every workflow that references it must be updated together.
* Bypasses GitHub App / organisation governance because it acts as the user.

A compromise of one PAT (phishing, leaked dotfile, third-party breach) gives an attacker the same access the user has — typically across many repos.

## Vulnerable example

```yaml theme={null}
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
        with:
          token: ${{ secrets.RELEASE_PERSONAL_ACCESS_TOKEN }}  # ← long-lived PAT
      - run: gh release create v1.0.0
        env:
          GH_TOKEN: ${{ secrets.MY_GH_PAT }}                  # ← long-lived PAT
```

## Safe alternatives

**Use the per-run GITHUB\_TOKEN with explicit permissions:**

```yaml theme={null}
permissions:
  contents: write
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
      - run: gh release create v1.0.0
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

**For cross-repo writes, install a GitHub App and mint an installation token at job start.** App tokens are short-lived (1 hour) and scoped to the installation, not to any human account.

**For cloud-provider auth, federate via OIDC.** `aws-actions/configure-aws-credentials`, `google-github-actions/auth`, and `azure/login` all accept the workflow's OIDC token in place of a long-lived access key.
