Files
evandrodevbr c17192c9fe feat: vendorizar as 10 skills do pilar de seguranca em skills/
Copia local (nao fork, nao submodule) de safedeps, sca-audit,
secrets-scan, code-review-security, api-security-review,
web-security-review, cve-triage, patch-prioritization,
dependency-scanning e hermaguard -- commit exato de origem e licenca
de cada uma em skills/NOTICE.md. Elimina o passo de clone externo
pro pilar de seguranca do wf-security-review: git clone deste repo
ja traz tudo funcional.

Corrige tambem o comando de instalacao pro Claude Code nos dois
READMEs e no docs/SKILLS.md: Claude Code exige um diretorio por
skill (skills/<nome>/SKILL.md), nao aceita os .md soltos que o
comando antigo copiava -- confirmado testando a instalacao real.
2026-08-24 00:32:20 -03:00

206 lines
8.8 KiB
Python

"""
Hermaguard Verify — Test Suite
Runs REAL PoCs in the REAL sandbox. No mocks of the verdict logic:
every test executes actual subprocesses and asserts on actual evidence.
"""
import importlib.util
import json
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parent
def _load_module(name, relpath):
spec = importlib.util.spec_from_file_location(name, ROOT / relpath)
if spec is None or spec.loader is None:
raise ImportError(f"Could not load {relpath}")
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod
spec.loader.exec_module(mod)
return mod
V = _load_module("hermaguard_verify", "tools/hermaguard-verify/hermaguard-verify.py")
def _write(tmpdir, name, content):
p = Path(tmpdir) / name
p.write_text(content)
return str(p)
class TestRealPoCRuns(unittest.TestCase):
"""Each test writes a genuine PoC + target and runs the real sandbox."""
def test_verified_on_demonstrated_crash(self):
# Target has the classic null-deref bug (bug 003 from the corpus)
with tempfile.TemporaryDirectory() as d:
target = _write(d, "app.py",
'def get_user_email(cur, user_id):\n row = cur.fetchone()\n return row[0]\n')
# Real PoC: a stub cursor that returns None, like a missing row
poc = _write(d, "poc.py",
"import app\n"
"class Cur:\n"
" def fetchone(self):\n"
" return None\n"
"app.get_user_email(Cur(), 123)\n")
ev = V.run_poc(poc, target, expect_fail=True, expect_output="TypeError")
self.assertEqual(ev["verdict"], "VERIFIED", ev)
self.assertIn("TypeError", ev["stderr"] + ev["stdout"])
def test_refuted_when_no_bug(self):
# Fixed target: no bug, PoC runs clean -> REFUTED (likely false positive)
with tempfile.TemporaryDirectory() as d:
target = _write(d, "app.py",
'def get_user_email(cur, user_id):\n row = cur.fetchone()\n'
' return None if row is None else row[0]\n')
poc = _write(d, "poc.py",
"import app\n"
"class Cur:\n"
" def fetchone(self):\n"
" return None\n"
"assert app.get_user_email(Cur(), 123) is None\n"
"print('no crash — code handles the null case')\n")
ev = V.run_poc(poc, target, expect_fail=True)
self.assertEqual(ev["verdict"], "REFUTED")
self.assertEqual(ev["exit_code"], 0)
def test_unverified_on_timeout(self):
with tempfile.TemporaryDirectory() as d:
poc = _write(d, "poc.py", "while True: pass\n")
ev = V.run_poc(poc, None, expect_fail=True, timeout=5)
self.assertEqual(ev["verdict"], "UNVERIFIED")
self.assertTrue(ev["timed_out"])
def test_unverified_when_clean_run_no_expectation(self):
with tempfile.TemporaryDirectory() as d:
poc = _write(d, "poc.py", "print('hello')\n")
ev = V.run_poc(poc, None, expect_fail=False, expect_output=None)
self.assertEqual(ev["verdict"], "UNVERIFIED") # proves nothing
def test_non_posix_fallback_no_crash(self):
# Windows has no resource module and no preexec_fn support — the
# sandbox must degrade to timeout-only, not crash.
saved_posix, saved_res = V._POSIX, V._HAS_RESOURCE
V._POSIX, V._HAS_RESOURCE = False, False
try:
with tempfile.TemporaryDirectory() as d:
poc = _write(d, "poc.py", "print('hello')\n")
ev = V.run_poc(poc, None, expect_fail=False, expect_output=None)
self.assertEqual(ev["verdict"], "UNVERIFIED")
self.assertFalse(ev["sandbox"]["rlimits_applied"])
with tempfile.TemporaryDirectory() as d:
poc = _write(d, "poc.py", "while True: pass\n")
ev = V.run_poc(poc, None, expect_fail=True, timeout=3)
self.assertEqual(ev["verdict"], "UNVERIFIED")
self.assertTrue(ev["timed_out"])
finally:
V._POSIX, V._HAS_RESOURCE = saved_posix, saved_res
def test_race_condition_verified_by_real_concurrency(self):
# The hard class CWE-362: double-spend only manifests under actual
# concurrency. The PoC spawns threads against the buggy code.
with tempfile.TemporaryDirectory() as d:
target = _write(d, "app.py",
"class Account:\n"
" def __init__(self, balance):\n"
" self.balance = balance\n"
"def withdraw(acct, amount):\n"
" if acct.balance >= amount: # check\n"
" acct.balance -= amount # act (non-atomic)\n"
" return True\n"
" return False\n")
poc = _write(d, "poc.py",
"import app, threading\n"
"acct = app.Account(100)\n"
"results = []\n"
"def try_take():\n"
" results.append(app.withdraw(acct, 100))\n"
"ts = [threading.Thread(target=try_take) for _ in range(8)]\n"
"[t.start() for t in ts]\n"
"[t.join() for t in ts]\n"
"if sum(results) > 1:\n"
" print('DOUBLE-SPEND: %d withdrawals succeeded from balance 100' % sum(results))\n"
" import sys; sys.exit(3)\n"
"print('no race observed this run')\n")
ev = V.run_poc(poc, target, expect_fail=True, expect_output="DOUBLE-SPEND")
# GIL makes the race probabilistic; with 8 threads on a
# check-then-act it fires reliably, but we accept either a
# VERIFIED race or an honest UNVERIFIED/REFUTED — never a crash.
self.assertIn(ev["verdict"], ("VERIFIED", "REFUTED", "UNVERIFIED"))
self.assertIsNotNone(ev["exit_code"])
def test_sandbox_no_env_leakage(self):
# The PoC must NOT see any parent environment variables.
with tempfile.TemporaryDirectory() as d:
poc = _write(d, "poc.py",
"import os\n"
"leaked = [k for k in os.environ if not k.startswith(('PATH', 'LC_', 'LANG'))]\n"
"print('LEAKED:', leaked)\n")
ev = V.run_poc(poc, None, expect_fail=False, expect_output=None)
self.assertNotIn("HERMES", ev["stdout"])
self.assertNotIn("SECRET", ev["stdout"])
# env={} means essentially nothing is present:
self.assertNotIn("HOME", ev["stdout"])
def test_output_truncated(self):
with tempfile.TemporaryDirectory() as d:
poc = _write(d, "poc.py", "print('x' * 100_000)\n")
ev = V.run_poc(poc, None, expect_fail=False, expect_output=None)
self.assertLessEqual(len(ev["stdout"]), V.MAX_OUTPUT_BYTES + 100)
class TestPolicyEnforcement(unittest.TestCase):
def test_hard_class_without_verification_violates(self):
report = {"findings": [
{"id": "HG-1", "classes": ["CWE-362"], "severity": "HIGH"},
]}
r = V.check_report_policy(report)
self.assertFalse(r["ok"])
self.assertEqual(r["violations"][0]["verification_verdict"], "MISSING")
def test_hard_class_with_verified_passes(self):
report = {"findings": [
{"id": "HG-1", "classes": ["CWE-362"], "severity": "HIGH",
"verification": {"verdict": "VERIFIED", "signal": "DOUBLE-SPEND observed"}},
]}
r = V.check_report_policy(report)
self.assertTrue(r["ok"])
def test_soft_class_exempt(self):
report = {"findings": [
{"id": "HG-2", "classes": ["CWE-89"], "severity": "CRITICAL"}, # sqli: not forced
]}
r = V.check_report_policy(report)
self.assertTrue(r["ok"])
self.assertEqual(r["hard_class_findings"], 0)
def test_unverified_still_violates(self):
report = {"findings": [
{"id": "HG-1", "classes": ["HG-ASYNC"], "verification": {"verdict": "UNVERIFIED"}},
]}
r = V.check_report_policy(report)
self.assertFalse(r["ok"])
def test_cli_check_report(self):
with tempfile.TemporaryDirectory() as d:
rp = Path(d) / "report.json"
rp.write_text(json.dumps({"findings": [
{"id": "HG-1", "classes": ["HG-PARTIAL-WRITE"]}]}))
tool = str(ROOT / "tools/hermaguard-verify/hermaguard-verify.py")
r = subprocess.run([sys.executable, tool, "--check-report", str(rp)],
capture_output=True, text=True)
self.assertEqual(r.returncode, 1)
self.assertIn("HG-PARTIAL-WRITE", r.stdout)
self.assertIn("HG-1", r.stdout)
if __name__ == "__main__":
unittest.main()