Repository navigation
Drop effective_cve from oauthlib GHSA labels added in #212 - #213
Conversation
… filtering The matching grype results have no resolvable CVE in the schema v5 DB, so yardstick keeps the matches (year=None) but filters out the labels (year=2026 via effective_cve), leaving the matches unlabeled. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Arvind Somya <arvind.somya@anchore.com>
Verified: v5 gate passes with this changeI ran yardstick 0.16.2's real gate code locally ( Setup
Results
Labels for the image: 348 unfiltered, 346 after the year filter with #212 (only the two new oauthlib labels dropped), 348 with this PR. The CI CVE-lookup caveat doesn't apply on v5. Yardstick's lookup ( Failing run: https://github.com/anchore/enterprise-data-service/actions/runs/36905166845 Test harness (run_gate.py)"""Run yardstick 0.16.2's real v5 quality gate for the python test image against a given labels dir.
usage: run_gate.py <labels_dir> <db_dir> [--related-cve]
"""
import datetime
import json
import logging
import os
import shutil
import sys
import yardstick
from yardstick import artifact
from yardstick.store import config as store_config
from yardstick.store import labels as label_store
from yardstick.store import scan_result
from yardstick.utils import grype_db
from yardstick.validate import GateConfig, validate_image
labels_dir, db_dir = sys.argv[1], os.path.abspath(sys.argv[2])
related_cve = "--related-cve" in sys.argv
REPO = "docker.io/anchore/test_images"
DIGEST = "sha256:b2b58a55c0b03c1626d2aaae2add9832208b02124dda7b7b41811e14f0fb272c"
IMAGE = f"{REPO}@{DIGEST}"
store_root = os.path.abspath("store")
shutil.rmtree(store_root, ignore_errors=True)
os.makedirs(store_root)
os.symlink(os.path.abspath(labels_dir), os.path.join(store_root, "labels"))
store_config.set_values(store_root=store_root)
# same production settings as config/grype-db-manager/include.d/validate.yaml (first gate)
gate_config = GateConfig(
max_f1_regression=0.15,
max_unlabeled_percent=50,
max_new_false_negatives=10,
max_year=2021,
candidate_tool_label="custom-db",
)
def match(pkg, ver, vuln_id, cve=None):
return {
"artifact": {"name": pkg, "version": ver, "type": "python", "purl": f"pkg:pypi/{pkg}@{ver}"},
"vulnerability": {"id": vuln_id, "namespace": "github:language:python"},
"relatedVulnerabilities": [{"id": cve, "namespace": "nvd:cpe"}] if cve else [],
"matchDetails": [],
}
common = match("aiohttp", "3.8.1", "GHSA-pjjw-qhg8-p2p9")
oauth = [
match("oauthlib", "3.2.0", "GHSA-hj66-6f7g-4r5v", "CVE-2026-49264" if related_cve else None),
match("oauthlib", "3.2.0", "GHSA-xpv3-w29h-x7cv", "CVE-2026-49265" if related_cve else None),
]
ids = []
for tool_name, label, matches in [
("grype[custom-db]", "custom-db", [common, *oauth]),
("grype", None, [common]),
]:
cfg = artifact.ScanConfiguration(
image_repo=REPO,
image_digest=DIGEST,
tool_name=tool_name,
tool_version="v0.87.0",
tool_label=label,
image_tag="grype-quality-python-d89207b",
timestamp=datetime.datetime.now(tz=datetime.timezone.utc),
)
raw = json.dumps({"matches": matches, "descriptor": {"db": {"location": db_dir}}})
scan_result.save(raw, artifact.ScanResult(config=cfg, matches=[]))
ids.append(cfg.ID)
# diagnostics: what the year filter sees, using the same DB the gate will use
grype_db.use(db_dir)
all_labels = label_store.load_for_image(IMAGE)
kept = label_store.load_for_image(IMAGE, year_max_limit=2021)
new = {"GHSA-hj66-6f7g-4r5v", "GHSA-xpv3-w29h-x7cv"}
for le in all_labels:
if le.vulnerability_id in new:
print(f"label {le.vulnerability_id}: effective_cve={le.effective_cve} year={le.effective_year()}"
f" kept_by_filter={le.vulnerability_id in {k.vulnerability_id for k in kept}}")
print(f"labels for image: {len(all_labels)} unfiltered, {len(kept)} after max_year=2021")
for v in ("GHSA-hj66-6f7g-4r5v", "GHSA-xpv3-w29h-x7cv"):
print(f"db normalize_to_cve({v}) -> {grype_db.normalize_to_cve(v)}")
logging.disable(logging.WARNING)
# capture the candidate summary the Gate computes its verdict from (it's an InitVar, not stored)
from yardstick.validate import gate as gate_mod
_captured = {}
_orig_post_init = gate_mod.Gate.__post_init__
def _post_init(self, reference_comparison, candidate_comparison):
_captured["candidate"] = candidate_comparison
return _orig_post_init(self, reference_comparison, candidate_comparison)
gate_mod.Gate.__post_init__ = _post_init
gate = validate_image(
IMAGE,
gate_config=gate_config,
descriptions=ids,
always_run_label_comparison=False,
verbosity=0,
)
c = _captured.get("candidate")
if c is None:
print("candidate: no label comparison needed (candidate and reference matches identical after year filter)")
else:
print(f"candidate: TP={c.true_positives} FP={c.false_positives} indeterminate={c.indeterminate}"
f" ({c.indeterminate_percent:.2f}%)")
print("GATE:", "PASS" if gate.passed() else "FAIL")
for r in gate.reasons:
print(" reason:", r)🤖 Generated with Claude Code |
Follow-up to #212. Removes
effective_cvefrom the two oauthlib labels on thegrype-quality-python-d89207bimage.Why: in yardstick 0.16.2 the
max_yearfilter derives years asymmetrically:tool/grype.py,store/scan_result.py): CVE comes from the match ID /relatedVulnerabilities/ DB lookup. For these GHSAs in the schema v5 DB none resolve, so year isNoneand the match is kept.artifact.py,store/labels.py):effective_cve: CVE-2026-*gives year 2026 >max_year2021, so the labels are filtered out.Result: the matches stay unlabeled and the enterprise-data-service v5 quality gate still fails (2/3 indeterminate). Without
effective_cve, labels fall back to the same DB lookup as the matches, so they should getyear=Noneand survive the filter too.Verified: yardstick 0.16.2's gate passes on a real v5 DB with these labels (and fails exactly like production with #212 as merged). Details: #213 (comment)
🤖 Generated with Claude Code