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
13 changes: 10 additions & 3 deletions .archgate/adrs/CI-001-pin-github-actions-by-hash.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ uses: owner/action@<40-char-sha> # <version>
- Local workflow references (e.g., `uses: ./.github/workflows/smoke-test.yml`) — these reference the same repository and do not carry supply chain risk
- Local composite actions (e.g., `uses: ./.github/actions/my-action`) — same repository, same trust boundary
- Docker container references (e.g., `uses: docker://image:tag`) — governed by separate container image policies
- The SLSA reusable workflow `slsa-framework/slsa-github-generator/.github/workflows/*` — see "Carved-out exceptions" below

**Carved-out exceptions:**

- **`slsa-framework/slsa-github-generator/.github/workflows/*`** — The SLSA generator's bootstrap script (`generate-builder.sh`) extracts the version from the workflow ref to download the prebuilt builder binary from a GitHub release. It explicitly rejects non-tag refs with `Invalid ref: ... Expected ref of the form refs/tags/vX.Y.Z`. This is documented upstream as [slsa-framework/slsa-github-generator#150](https://github.com/slsa-framework/slsa-github-generator/issues/150). The reusable workflow MUST therefore be referenced by tag (e.g., `@v2.1.0`). Trust is anchored in the SLSA project's own signing/verification chain rather than in SHA pinning at the call site. Confirmed empirically: pinning by SHA broke the `v0.31.0` release pipeline (run [25107195589](https://github.com/archgate/cli/actions/runs/25107195589)).

**Version comment format:** The comment after the SHA MUST contain the human-readable version that the SHA corresponds to (e.g., `# v6`, `# v2.4.3`, `# v2.1.0`). This enables:

Expand Down Expand Up @@ -110,8 +115,8 @@ uses: owner/action@<40-char-sha> # <version>
- **Mitigation**: Renovate is configured in the repository and understands SHA-pinned GitHub Action references. The `renovate.json` configuration includes GitHub Actions as an update target. Regular Renovate PRs ensure pins stay current.
- **Incorrect SHA resolution**: A contributor might resolve the SHA for the wrong tag, or the tag might be an annotated tag whose SHA differs from the commit SHA.
- **Mitigation**: The automated rule checks that all third-party `uses:` references match the `@<40-char-hex>` pattern. Code review MUST verify that the SHA matches the intended version by cross-referencing the action's releases page. For annotated tags, resolve the underlying commit via `gh api repos/<owner>/<repo>/git/ref/tags/<tag>` and follow the `object` if `type` is `"tag"`.
- **Reusable workflow compatibility**: Some reusable workflow providers (e.g., SLSA framework) have historically recommended tag references for verifier compatibility. SHA pinning may not be forward-compatible with all verification tools.
- **Mitigation**: The SLSA GitHub Generator v2.x explicitly supports SHA-based builder identity verification. Test SLSA provenance verification after pinning by running `slsa-verifier verify-artifact` against a release built with the SHA-pinned workflow.
- **Reusable workflow compatibility**: Some reusable workflow providers cannot be referenced by SHA. The SLSA GitHub Generator (`slsa-framework/slsa-github-generator/.github/workflows/*`) is the known case for this project — its bootstrap script reads the workflow ref to fetch the prebuilt builder from a GitHub release and rejects non-tag refs (upstream issue [#150](https://github.com/slsa-framework/slsa-github-generator/issues/150)). This was confirmed empirically when the `v0.31.0` release failed after SHA pinning.
- **Mitigation**: The SLSA reusable workflow is carved out as a documented exception (see "Scope" above). It is referenced by tag (`@v2.1.0`). The automated `no-unpinned-actions` rule allowlists this specific path so the exception is enforced rather than being a silent gap. Any other provider claiming SHA pinning is unsupported MUST be evaluated on a case-by-case basis and added to the allowlist with an explicit justification before merging.

## Compliance and Enforcement

Expand All @@ -131,7 +136,9 @@ Code reviewers MUST verify:

Local workflow and action references (`uses: ./.github/workflows/...` or `uses: ./.github/actions/...`) are exempt — they reference code in the same repository and are governed by the repository's own access controls. Docker container references (`uses: docker://...`) are also exempt from this ADR.

No exceptions for third-party references. If an upstream provider claims SHA pinning is unsupported, escalate to the project maintainer for evaluation before merging.
The SLSA reusable workflow (`slsa-framework/slsa-github-generator/.github/workflows/*`) is exempt because its bootstrap script requires a tag-format ref to fetch the builder binary; see "Carved-out exceptions" under Decision. The `no-unpinned-actions` rule explicitly allowlists this path.

For any other third-party reference where an upstream provider claims SHA pinning is unsupported: escalate to the project maintainer, document the upstream limitation in this ADR's "Carved-out exceptions" list, and update the rule allowlist before merging. Silent exceptions are not permitted.

## References

Expand Down
20 changes: 19 additions & 1 deletion .archgate/adrs/CI-001-pin-github-actions-by-hash.rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,28 @@ const USES_PATTERN = /uses:\s+(?!\.\/|docker:\/\/)(\S+@\S+)/g;
*/
const PINNED_SHA_PATTERN = /^.+@[0-9a-f]{40}\b/;

/**
* Carved-out exceptions where the upstream provider does not support SHA pinning.
* Each entry is matched against the part of the `uses:` value before the `@`.
* See CI-001 "Carved-out exceptions" for the rationale per entry.
*/
const SHA_PIN_EXEMPT_PREFIXES = [
// SLSA generator reads the workflow ref to download the prebuilt builder from
// a GitHub release; rejects non-tag refs. Upstream issue:
// https://github.com/slsa-framework/slsa-github-generator/issues/150
"slsa-framework/slsa-github-generator/.github/workflows/",
];

function isShaPinExempt(ref: string): boolean {
const beforeAt = ref.split("@")[0];
return SHA_PIN_EXEMPT_PREFIXES.some((prefix) => beforeAt.startsWith(prefix));
}

export default {
rules: {
"no-unpinned-actions": {
description:
"Third-party GitHub Actions and reusable workflows must be pinned by full commit SHA",
"Third-party GitHub Actions and reusable workflows must be pinned by full commit SHA (with documented exceptions)",
async check(ctx) {
const matches = await ctx.grepFiles(
USES_PATTERN,
Expand All @@ -32,6 +49,7 @@ export default {
if (!usesMatch) continue;

const ref = usesMatch[1];
if (isShaPinExempt(ref)) continue;
if (!PINNED_SHA_PATTERN.test(ref)) {
ctx.report.violation({
message: `Unpinned action reference: "${ref}". Pin by full 40-character commit SHA with a version comment (e.g., \`actions/checkout@<sha> # v6\`).`,
Expand Down
1 change: 1 addition & 0 deletions .claude/agent-memory/archgate-developer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv
- **Module-level `{ ...Bun.env }` captures env at import time** — Spreading `Bun.env` into a module-level constant freezes the env snapshot. Tests that override `Bun.env.HOME` after import won't affect the constant. Fix: use a function that returns `{ ...Bun.env, ... }` on each call so it picks up test-time overrides. Applied in `src/helpers/credential-store.ts`.
- **`Bun.Glob.scan({ dot: false })` silently drops dot-prefixed segments — even on explicit paths** — `dot: false` (the default) skips matches whose path contains a `.`-prefixed segment, including patterns that explicitly name the dir (e.g. `.github/workflows/release.yml`). Behavior also varies across platforms — Windows reliably drops the match while Linux can match the same pattern, so a rule appears to "work in CI" but no-ops locally. For code repos where `.github/`, `.husky/`, `.vscode/` are first-class source dirs, ALWAYS pass `dot: true` to `Bun.Glob.scan()`. Applied in `src/engine/runner.ts` (`ctx.glob`, `ctx.grepFiles`) and `src/engine/git-files.ts` (`resolveScopedFiles`). See archgate/cli#222.
- **PowerShell 5.1 reads BOM-less `.ps1` files as ANSI — never use non-ASCII chars in `install.ps1`** — `install.ps1` has no UTF-8 BOM (first bytes are `# A...`). On Windows PowerShell 5.1, this means the file is decoded as the system codepage (Windows-1252), so multi-byte UTF-8 characters like em-dash (`—`, `\xE2\x80\x94`) get split into garbage bytes that break later string parsing — the parser reports cryptic errors like "string is missing the terminator" on lines that look fine. ALWAYS stick to ASCII (`-`, `--`, straight quotes) in comments and string literals in `install.ps1`. To verify after editing: `[System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path .\install.ps1).Path, [ref]$null, [ref]$errs)`. Same caveat applies to any unsigned `.ps1` file the project distributes.
- **SLSA reusable workflow MUST be tag-pinned, not SHA-pinned** — `slsa-framework/slsa-github-generator/.github/workflows/*` looks like it should follow CI-001 (SHA pin), but the SLSA generator's `generate-builder.sh` reads the workflow ref to download the prebuilt builder from a GitHub release and rejects non-tag refs (`Invalid ref: ... Expected ref of the form refs/tags/vX.Y.Z`, exit 2). Pinning by SHA broke the v0.31.0 release ([run 25107195589](https://github.com/archgate/cli/actions/runs/25107195589)). The CI-001 rule allowlists this path so it does NOT block a SHA repin — meaning a future agent could "fix" the tag pin and the rule would be silent until the next release fails. ALWAYS keep `@v2.x.y` for `release-binaries.yml:165` and read the inline comment + CI-001 "Carved-out exceptions" before changing. Upstream issue: [slsa-framework/slsa-github-generator#150](https://github.com/slsa-framework/slsa-github-generator/issues/150).

## Validation Pipeline

Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/release-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ jobs:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0
# SLSA reusable workflow MUST be referenced by tag, not SHA — the generator's
# generate-builder.sh extracts the version from the ref to download the builder
# binary from a GitHub release and rejects non-tag refs. See CI-001 exception
# and slsa-framework/slsa-github-generator#150.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
with:
base64-subjects: "${{ needs.combine-hashes.outputs.digests }}"
upload-assets: true
Expand Down
Loading