> ## 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-7 — Build artifact published without a retention cap

> An uploaded artifact is a zip anyone with read access can download, for as long as it is retained.

| Field      | Value                                                                                                                                            |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Rule ID    | `cicd-sec-7-artifact-exposure`                                                                                                                   |
| Category   | `CICD-SEC-7`                                                                                                                                     |
| Severity   | **MEDIUM**                                                                                                                                       |
| Confidence | MEDIUM                                                                                                                                           |
| Platforms  | GitHub Actions                                                                                                                                   |
| OWASP      | [CICD-SEC-7: Insecure System Configuration](https://owasp.org/www-project-top-10-ci-cd-security-risks/CICD-SEC-07-Insecure-System-Configuration) |
| Auto-fix   | partial                                                                                                                                          |

## What the check does

Flags an `actions/upload-artifact` step with **no `retention-days:`** (or one
above 90) in a workflow where the artifact is likely to carry something.

## Why it matters

An uploaded artifact is a zip anyone with read access to the repository can
download, for as long as it is retained — on a **public repository, everyone**.

GitHub's default retention is 90 days and an organisation can raise it to 400.
So *"we didn't set it"* and *"we set it to the maximum"* produce the same file
sitting there for over a year. Build trees routinely contain more than the
binary: a `.env` the build wrote, a signing key, a token in a config file, a
coverage report naming internal hosts.

## Why it does not fire on every upload

Most artifacts are a compiled binary nobody cares about. The rule fires where
the artifact is likely to carry something:

| Signal                                              | Detail                                                                                                                                               |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| The workflow handles secrets or publishes a release | The build tree may hold a token, a signing key, or a `.env` the build wrote                                                                          |
| The uploaded `path:` names a credential             | `.env`, `.npmrc`, `.pypirc`, `.netrc`, `id_rsa`, `id_ed25519`, `*.pem`, `*.key`, `*.p12`, `*.jks` — sensitive whatever the rest of the workflow does |

```yaml theme={null}
# flagged — a release workflow, no cap
- run: npm publish
- uses: actions/upload-artifact@v4
  with:
    name: dist
    path: dist/
```

## Safe alternative

Set the shortest window that is actually useful. For an artifact a later job
consumes minutes later, that is often 1:

```yaml theme={null}
- uses: actions/upload-artifact@v4
  with:
    name: dist
    path: dist/
    retention-days: 1
```

And if the artifact may contain a token, a signing key, or a `.env` the build
wrote — **do not upload it at all.** A short retention window on a leaked
credential is still a leaked credential.

## What does not fire

| Stays quiet                               | Why                                                                                                        |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `actions/download-artifact`               | Consuming an artifact is not publishing one                                                                |
| `retention-days: 7` (or anything ≤ 90)    | At or under the cap                                                                                        |
| `retention-days: ${{ inputs.retention }}` | The scanner cannot evaluate it, and guessing would flag a workflow that is already parameterised correctly |
| An ordinary build uploading `dist/`       | Neither the workflow nor the path is sensitive                                                             |

## A limitation worth knowing

The risk is worst on a **public repository**, where "anyone with read access"
means anyone at all. Repository visibility is **not derivable from a workflow
file** — nothing in the YAML says whether the repo is public. Rather than guess,
the rule gates on what the file does show. If your repository is public, treat
every one of these findings as more urgent than its MEDIUM severity suggests.

## Auto-fix

**Partial.** Adds `retention-days: 90`, creating the `with:` block if the step
has none, guarded by a comment:

```yaml theme={null}
- uses: actions/upload-artifact@v4
  with:
    name: dist
    path: dist/
    # pipefort: artifact retention — 90 days is a ceiling, not a recommendation.
    # Shorten it to the window this artifact is actually useful for, or stop
    # uploading it if it may contain a credential.
    retention-days: 90
```

90 caps the artifact against an organisation default that may be 400. It is
**not** the right answer for most artifacts, which is why the comment is part of
the fix. Running it twice does not stack a second comment.

## Related rules

* [CICD-SEC-7 — Actions debug logging enabled](/rules/cicd-sec-7)
* [CICD-SEC-1 — `workflow_run` downloads artifacts from the triggering run](/rules/cicd-sec-1-workflow-run-artifact-poisoning)
* [CICD-SEC-6 — Run step dumps the environment or echoes a CI token](/rules/cicd-sec-6-env-exfil)
