From 87426c0046143e9019e8d7290456024eb6366737 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Wed, 11 Nov 2026 15:48:11 +0000 Subject: [PATCH] fix(kvb-check-security): accept a finding without a line by the sha1 of its file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A MEDUSA finding without a line number (a Dockerfile-level rule, for example) had no fingerprint, so security.py accept could never waive it and the score stayed below 100 after review. Such a finding is now keyed by the sha1 of the whole file; any edit of the file brings it back. 🎯 Fix: 95 % 💥 Break: 2 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 93 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 --- skills/kvb-check-security/scripts/security.py | 16 ++++++++++------ tests/test_security.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/skills/kvb-check-security/scripts/security.py b/skills/kvb-check-security/scripts/security.py index 42580a6..a0dfe1f 100755 --- a/skills/kvb-check-security/scripts/security.py +++ b/skills/kvb-check-security/scripts/security.py @@ -20,12 +20,13 @@ Scan: Accepted: One JSON row per reviewed false positive (id, rule, path, sha1 of the stripped - source line, reason, repo, date), appended by accept to + source line, or of the whole file for a finding without a line, reason, repo, + date), appended by accept to /security-accepted.jsonl of the kvb-org-organize watch.py, whose hook keeps the rows in ~/.local/state/agents-council/security-accepted.jsonl; - a scan reads both. A finding with that rule, path and line text is accepted and - leaves the score; an edit of the line brings it back. A row without rule, path - and sha1 is a usage error. + a scan reads both. A finding with that rule, path and text is accepted and + leaves the score; an edit of the line, or of the file, brings it back. A row + without rule, path and sha1 is a usage error. Output: `🛡️ security % (medusa: findings, accepted in files: :, ...)`, @@ -116,10 +117,13 @@ def located(name: str, tree: Path) -> str: def fingerprint(tree: Path, path: str, line: int | None) -> str | None: try: - rows = (tree / path).read_text(encoding="utf-8", errors="replace").splitlines() + text = (tree / path).read_text(encoding="utf-8", errors="replace") except OSError: return None - if line is None or not 1 <= line <= len(rows): + if line is None: + return hashlib.sha1(text.encode()).hexdigest() + rows = text.splitlines() + if not 1 <= line <= len(rows): return None return hashlib.sha1(rows[line - 1].strip().encode()).hexdigest() diff --git a/tests/test_security.py b/tests/test_security.py index 1035370..83aeb50 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -33,6 +33,8 @@ for path in sorted(p for p in target.rglob("*") if p.is_file()): "severity": severity, "rule_id": "FAKE-" + severity, "file": str(path), "line": number, "code": text, }}) + if "MARK-FILE" in text: + findings.append({{"severity": "MEDIUM", "rule_id": "FAKE-FILE", "file": str(path), "line": None}}) report = {{"issues": findings}} if os.environ.get("FAKE_MEDUSA_SHAPE") == "other" else {{"findings": findings}} (out / "medusa-scan-20261104-120000.json").write_text(json.dumps(report)) """ @@ -163,6 +165,18 @@ class TestSecurity(unittest.TestCase): self.assertEqual(result.returncode, 2) self.assertIn("--sha1 must be 40 lowercase hex digits", result.stderr) + def test_a_file_level_finding_is_accepted_by_the_sha1_of_its_file_until_the_file_changes(self): + text = "FROM debian # MARK-FILE\n" + self.write("Dockerfile", text) + sha1 = hashlib.sha1(text.encode()).hexdigest() + self.assertEqual(json.loads(self.scan("--json", env=self.env).stdout)["findings"][0]["sha1"], sha1) + result = self.accept("--rule", "FAKE-FILE", "--path", "Dockerfile", "--sha1", sha1, "--reason", "fixture") + self.assertEqual(result.returncode, 0, result.stderr) + result = self.scan(env=self.env) + self.assertEqual(result.stdout, "🛡️ security 100 % (medusa: 0 findings, 1 accepted in 1 file)\n") + self.write("Dockerfile", text + "USER app\n") + self.assertEqual(self.scan(env=self.env).returncode, 3) + def test_one_accept_takes_every_rule_of_a_line(self): line = "z = both() # MARK-HIGH MARK-LOW" self.write("app.py", f"{line}\n")