feat(kvb-check-security): accept takes every rule of a line; acceptance only after kvb-think-criticize
MEDUSA often fires several rules on one line (14 INF-SCAN rules on one tempfile line), so security.py accept now takes --rule repeatedly and appends one row per rule, same id formula, in one locked write; a single --rule works as before. The skill now allows an acceptance only once kvb-think-criticize judged the finding a false positive or by design, per the operator's order of 2026-11-05. 🎯 Fix: 96 % 💥 Break: 2 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 94 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
12ed861a9a
commit
95d166ef78
3 files changed
+33
-17
No files matched your search
@@ -47,12 +47,12 @@ value. Pass `<n>` as `--security` and the critical count as `--critical`
|
|||||||
`--security '~<n>'` (quoted: zsh expands a bare `~100` as a user's home) and the critical findings you found as `--critical`.
|
`--security '~<n>'` (quoted: zsh expands a bare `~100` as a user's home) and the critical findings you found as `--critical`.
|
||||||
- Exit 2: usage error.
|
- Exit 2: usage error.
|
||||||
- **Accepted false positives**: `security.py accept --repo <root> --rule
|
- **Accepted false positives**: `security.py accept --repo <root> --rule
|
||||||
<id> --path <path> --sha1 <sha1> --reason "<one line>"`, rule, path and
|
<id> [--rule <id> ...] --path <path> --sha1 <sha1> --reason "<one line>"`
|
||||||
sha1 from `--json`, appends a row outside the repository: to the council
|
(from `--json`) appends a row per rule to the council root, which the
|
||||||
root, which the `kvb-org-organize` watch hook keeps in
|
`kvb-org-organize` watch hook mirrors to `~/.local/state/agents-council/`.
|
||||||
`~/.local/state/agents-council/`. A match leaves the score and shows as
|
A match leaves the score as `<a> accepted` until the line changes. Accept
|
||||||
`<a> accepted`; editing the line brings it back. Accept only after reading
|
only after `kvb-think-criticize` judged it a false positive or by design;
|
||||||
the rule's pattern, and name each acceptance in the commit message.
|
name each acceptance in the commit message.
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ Args:
|
|||||||
--base: git ref the change starts from.
|
--base: git ref the change starts from.
|
||||||
--json: print the figures as JSON instead of the line.
|
--json: print the figures as JSON instead of the line.
|
||||||
|
|
||||||
accept --repo <root> --rule <rule_id> --path <repo path> --sha1 <40 hex> --reason <one line>:
|
accept --repo <root> --rule <rule_id> [--rule ...] --path <repo path> --sha1 <40 hex> --reason <one line>:
|
||||||
record a reviewed false positive instead; --json of a scan prints rule, path
|
record reviewed false positives of one line instead, a row per rule; --json
|
||||||
and sha1 of every finding.
|
of a scan prints rule, path and sha1 of every finding.
|
||||||
|
|
||||||
Scan:
|
Scan:
|
||||||
The regular files `git diff <ref>` names plus untracked ones, symlinks left
|
The regular files `git diff <ref>` names plus untracked ones, symlinks left
|
||||||
@@ -151,25 +151,29 @@ def accepted(listings: tuple[Path, ...]) -> set[tuple[str, str, str]]:
|
|||||||
|
|
||||||
def accept(argv: list[str]) -> int:
|
def accept(argv: list[str]) -> int:
|
||||||
parser = argparse.ArgumentParser(prog="security.py accept", description="Record a reviewed false positive.")
|
parser = argparse.ArgumentParser(prog="security.py accept", description="Record a reviewed false positive.")
|
||||||
for flag in ("--repo", "--rule", "--path", "--sha1", "--reason"):
|
for flag in ("--repo", "--path", "--sha1", "--reason"):
|
||||||
parser.add_argument(flag, required=True)
|
parser.add_argument(flag, required=True)
|
||||||
|
parser.add_argument("--rule", action="append", required=True)
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
if not SHA1.fullmatch(args.sha1):
|
if not SHA1.fullmatch(args.sha1):
|
||||||
parser.error("--sha1 must be 40 lowercase hex digits")
|
parser.error("--sha1 must be 40 lowercase hex digits")
|
||||||
if not args.reason.strip() or "\n" in args.reason:
|
if not args.reason.strip() or "\n" in args.reason:
|
||||||
parser.error("--reason must be one non-empty line")
|
parser.error("--reason must be one non-empty line")
|
||||||
root = git(Path(args.repo), "rev-parse", "--show-toplevel").strip()
|
root = git(Path(args.repo), "rev-parse", "--show-toplevel").strip()
|
||||||
key = f"{args.rule}\t{args.path}\t{args.sha1}"
|
date = datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds")
|
||||||
row = {
|
rows = [
|
||||||
"id": hashlib.sha1(key.encode()).hexdigest(), "rule": args.rule, "path": args.path, "sha1": args.sha1,
|
{
|
||||||
"reason": args.reason, "repo": root, "date": datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds"),
|
"id": hashlib.sha1(f"{rule}\t{args.path}\t{args.sha1}".encode()).hexdigest(), "rule": rule,
|
||||||
}
|
"path": args.path, "sha1": args.sha1, "reason": args.reason, "repo": root, "date": date,
|
||||||
|
}
|
||||||
|
for rule in args.rule
|
||||||
|
]
|
||||||
target = watch().ACCEPTED
|
target = watch().ACCEPTED
|
||||||
target.parent.mkdir(parents=True, exist_ok=True)
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with target.open("a", encoding="utf-8") as listing:
|
with target.open("a", encoding="utf-8") as listing:
|
||||||
fcntl.flock(listing, fcntl.LOCK_EX)
|
fcntl.flock(listing, fcntl.LOCK_EX)
|
||||||
listing.write(json.dumps(row) + "\n")
|
listing.write("".join(json.dumps(row) + "\n" for row in rows))
|
||||||
print(f"accepted {args.rule} {args.path} ({args.sha1[:12]}) in {target}")
|
print(f"accepted {', '.join(args.rule)} {args.path} ({args.sha1[:12]}) in {target}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,18 @@ class TestSecurity(unittest.TestCase):
|
|||||||
self.assertEqual(result.returncode, 2)
|
self.assertEqual(result.returncode, 2)
|
||||||
self.assertIn("--sha1 must be 40 lowercase hex digits", result.stderr)
|
self.assertIn("--sha1 must be 40 lowercase hex digits", result.stderr)
|
||||||
|
|
||||||
|
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")
|
||||||
|
sha1 = hashlib.sha1(line.encode()).hexdigest()
|
||||||
|
result = self.accept(
|
||||||
|
"--rule", "FAKE-HIGH", "--rule", "FAKE-LOW", "--path", "app.py", "--sha1", sha1, "--reason", "fixture"
|
||||||
|
)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertIn("accepted FAKE-HIGH, FAKE-LOW app.py", result.stdout)
|
||||||
|
result = self.scan(env=self.env)
|
||||||
|
self.assertEqual(result.stdout, "🛡️ security 100 % (medusa: 0 findings, 2 accepted in 1 file)\n")
|
||||||
|
|
||||||
def test_critical_exits_1_and_floors_at_0(self):
|
def test_critical_exits_1_and_floors_at_0(self):
|
||||||
self.write("app.py", "a # MARK-CRITICAL\nb # MARK-CRITICAL\nc # MARK-CRITICAL\n")
|
self.write("app.py", "a # MARK-CRITICAL\nb # MARK-CRITICAL\nc # MARK-CRITICAL\n")
|
||||||
result = self.scan(env=self.env)
|
result = self.scan(env=self.env)
|
||||||
|
|||||||
Reference in new issue
Block a user