Skip to content

Commit 25484be

Browse files
authored
chore: add support for Git tag aliases in vuln GHA check (#1065)
This PR adds a utility function to identify the highest semantic version tag from a set of Git tags, specifically for cases where multiple tags point to the same commit SHA in a third-party GitHub Action. Signed-off-by: behnazh-w <[email protected]>
1 parent 365054e commit 25484be

File tree

3 files changed

+88
-6
lines changed

3 files changed

+88
-6
lines changed

src/macaron/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class CloneError(MacaronError):
3232
"""Happens when cannot clone a git repository."""
3333

3434

35+
class GitTagError(MacaronError):
36+
"""Happens when there is a Git tag related error."""
37+
38+
3539
class RepoCheckOutError(MacaronError):
3640
"""Happens when there is an error when checking out the correct revision of a git repository."""
3741

src/macaron/slsa_analyzer/git_url.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
from git import GitCommandError
1717
from git.objects import Commit
1818
from git.repo import Repo
19+
from packaging import version
1920
from pydriller.git import Git
2021

2122
from macaron.config.defaults import defaults
2223
from macaron.config.global_config import global_config
2324
from macaron.environment_variables import get_patched_env
24-
from macaron.errors import CloneError
25+
from macaron.errors import CloneError, GitTagError
2526

2627
logger: logging.Logger = logging.getLogger(__name__)
2728

@@ -984,3 +985,73 @@ def get_tags_via_git_remote(repo: str) -> dict[str, str] | None:
984985
logger.debug("Found %s tags via ls-remote of %s", len(tags), repo)
985986

986987
return tags
988+
989+
990+
def find_highest_git_tag(tags: set[str]) -> str:
991+
"""
992+
Find and return the highest (most recent) semantic version tag from a set of Git tags.
993+
994+
Parameters
995+
----------
996+
tags : set[str]
997+
A set of version strings (e.g., {"v1.0.0", "v2.3.4", "v2.1.0"}). Tags must follow PEP 440 or
998+
semver format, otherwise they will be skipped.
999+
1000+
Returns
1001+
-------
1002+
str
1003+
The tag string corresponding to the highest version.
1004+
1005+
Raises
1006+
------
1007+
GitTagError
1008+
If no valid tag is found or if a tag is not a valid version.
1009+
1010+
Example
1011+
-------
1012+
>>> find_highest_git_tag({"v2.0.0"})
1013+
'v2.0.0'
1014+
1015+
>>> find_highest_git_tag({"v4", "v4.2.1"})
1016+
'v4.2.1'
1017+
1018+
>>> find_highest_git_tag({"1.2.3", "2.0.0", "1.10.1"})
1019+
'2.0.0'
1020+
1021+
>>> find_highest_git_tag({"0.1", "0.1.1", "0.0.9"})
1022+
'0.1.1'
1023+
1024+
>>> find_highest_git_tag({"invalid", "1.0.0"})
1025+
'1.0.0'
1026+
1027+
>>> find_highest_git_tag(set())
1028+
Traceback (most recent call last):
1029+
...
1030+
GitTagError: No tags provided.
1031+
1032+
>>> find_highest_git_tag({"invalid"})
1033+
Traceback (most recent call last):
1034+
...
1035+
GitTagError: No valid version tag found.
1036+
"""
1037+
if not tags:
1038+
raise GitTagError("No tags provided.")
1039+
1040+
highest_tag = None
1041+
highest_parsed_tag = version.Version("0")
1042+
1043+
for tag in tags:
1044+
try:
1045+
parsed_tag = version.Version(tag)
1046+
except version.InvalidVersion:
1047+
logger.debug("Invalid version tag encountered while finding the highest tag: %s", tag)
1048+
continue
1049+
1050+
if parsed_tag > highest_parsed_tag:
1051+
highest_parsed_tag = parsed_tag
1052+
highest_tag = tag
1053+
1054+
if highest_tag is None:
1055+
raise GitTagError("No valid version tag found.")
1056+
1057+
return highest_tag

src/macaron/slsa_analyzer/package_registry/osv_dev.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
from packaging import version
1111

1212
from macaron.config.defaults import defaults
13-
from macaron.errors import APIAccessError
13+
from macaron.errors import APIAccessError, GitTagError
1414
from macaron.json_tools import json_extract
15-
from macaron.slsa_analyzer.git_url import get_tags_via_git_remote, is_commit_hash
15+
from macaron.slsa_analyzer.git_url import find_highest_git_tag, get_tags_via_git_remote, is_commit_hash
1616
from macaron.util import send_post_http_raw
1717

1818
logger: logging.Logger = logging.getLogger(__name__)
@@ -328,15 +328,22 @@ def is_version_affected(
328328
# and try to match the commit hash with the tag. If a match is found, update `pkg_version` to the tag.
329329
if source_repo and is_commit_hash(pkg_version):
330330
tags: dict = get_tags_via_git_remote(source_repo) or {}
331+
# There might be tag aliases for a release, e.g., v4 and v4.2.1.
332+
commit_tags = set()
331333
for tag, commit in tags.items():
332334
if commit.startswith(pkg_version):
333-
pkg_version = tag
334-
break
335+
commit_tags.add(tag)
335336

336337
# If we were not able to find a tag for the commit hash, raise an exception.
337-
if is_commit_hash(pkg_version):
338+
if not commit_tags:
338339
raise APIAccessError(f"Failed to find a tag for {pkg_name}@{pkg_version}.")
339340

341+
# Pick the most recent (highest) tag if there are aliases.
342+
try:
343+
pkg_version = find_highest_git_tag(commit_tags)
344+
except GitTagError as error:
345+
raise APIAccessError from error
346+
340347
affected = json_extract(vuln, ["affected"], list)
341348
if not affected:
342349
raise APIAccessError(f"Received invalid response for {pkg_name}@{pkg_version}.")

0 commit comments

Comments
 (0)