Tags are not immutable

ID

tag_protection

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Family

SCM

Tags

ASVS50:v15.1.1, cicd-sec-09, cicd-security, security, source-code, spvs10-v3.3.18, supply-chain, tag-protection

Description

Are the repository’s tags protected against being moved or deleted?

A release tag is a trust anchor: consumers pin a GitHub Action or a download to a tag (for example uses: org/action@v1) and assume the tag keeps pointing at the reviewed commit. GitHub tags are mutable by default — a tag can be re-pointed to a different commit or deleted and recreated. The check fetches the repository’s actual tags and reports the ones not covered by an active GitHub ruleset that restricts both tag update and tag deletion.

Tag rulesets are a GitHub-native primitive, so this detector applies to GitHub and GitHub Enterprise repositories. The check is only raised for repositories that actually publish tags — a repository with no tags has nothing to protect.

Coverage is evaluated per tag against the ruleset’s ref-name scope: a ruleset restricting update and deletion but scoped to refs/tags/v* protects v1.2.3 but leaves release-2024 mutable, so that tag is reported. A ruleset that only enforces a naming pattern or signatures — without restricting update and deletion — does not make tags immutable and will still be reported.

Impact context

The reported severity and message are modulated by how the repository’s tags are consumed:

  • Custom GitHub Action repository (an action.yml / action.yaml at the root) — catastrophic, reported at critical: consumers run uses: owner/repo@<tag>, so a moved tag executes attacker-controlled code in every consumer’s pipeline (the xygeni-action class of incident).

  • Tag-triggered CI/CD (on: release, or on: push with a tags: filter) — a re-pointed tag runs the pipeline against tainted code.

  • Published releases — release artifacts are resolved by tag, so consumers downloading by tag receive tampered content.

Security

Tag immutability is the control that stops tag-poisoning supply-chain attacks. If an attacker who has gained write access (or a compromised token/bot) can move a widely-consumed release tag to attacker-controlled code, every downstream pipeline that pins to that tag will silently execute the malicious version on its next run.

This is the class of weakness behind the xygeni-action tag-poisoning incident and, more broadly, the reason SHA-pinning of third-party actions is recommended (see pipeline_external_dependencies). Tag immutability protects the producer side (your published tags can’t be moved), while SHA-pinning protects the consumer side (you don’t trust someone else’s mutable tag). Both are needed.

Mitigation / Fix

Create a GitHub ruleset that targets tags and, at minimum:

  • Restrict updates — prevents an existing tag from being moved to a different commit.

  • Restrict deletions — prevents a tag from being deleted (and recreated elsewhere).

Set the ruleset enforcement to Active, and scope its target tag pattern to your release tags (for example v*). See Managing rulesets for a repository.

Configuration

To change these options you can modify SCANNER_DIR/conf/misconfigurations/tag_protection.yml. The following are the default configuration properties:

# Configuration for the tag immutability check.
properties:
  # If true, require an active GitHub ruleset (target: tag) that restricts both tag update and deletion.
  requireTagImmutability: true