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. 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 tag-protection rule that restricts both tag update and tag deletion.
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 rule’s ref/name scope: a rule scoped to v* protects v1.2.3 but leaves release-2024 mutable, so that tag is reported. A GitHub ruleset that only enforces a naming pattern or signatures — without restricting update and deletion — does not make tags immutable and will still be reported.
|
Platform support
The tag-protection primitive is platform-specific; the detector evaluates each platform against its native mechanism:
| Platform | Tag-protection primitive | Status |
|---|---|---|
GitHub / GitHub Enterprise |
Ruleset with |
Supported |
GitLab / GitLab Enterprise |
Protected tags whose name pattern matches the tag |
Supported |
Gitea |
Tag protection rules whose name pattern matches the tag |
Supported |
Azure DevOps |
No per-tag immutability rule — tag create/delete is governed by repository security-namespace permissions, not a queryable protected-tags primitive |
Not supported |
Bitbucket (Cloud / Data Center) |
Branch restrictions apply to branches, not tags; no protected-tags API |
Not supported |
Where a platform exposes no native tag-protection primitive, the detector reports nothing for that platform (it never guesses), to avoid false positives.
Impact context
On GitHub, the reported severity and message are modulated by how the repository’s tags are consumed:
-
Custom GitHub Action repository (an
action.yml/action.yamlat the root) — catastrophic, reported atcritical: consumers runuses: 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, oron: pushwith atags: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.
Mitigation / Fix
GitHub — create a 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.
GitLab — add a protected tag whose name (or wildcard, e.g. v*) covers your release tags, restricting who can create/overwrite them.
Gitea — add a protected tag rule whose name pattern covers your release tags.
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 tag-protection rule (GitHub ruleset target=tag / GitLab protected tags)
# that restricts both tag update and deletion.
requireTagImmutability: true