fix(guard): review-block curl -s without status capture so receipts are decisive

This commit is contained in:
Violin
2026-08-11 19:43:26 +01:00
parent f593b359ae
commit 5355ff1b68
2 changed files with 113 additions and 0 deletions
+59
View File
@@ -332,6 +332,60 @@ def check_local_artifact_paths(command: str) -> CheckResult:
return result
_HTTP_CLIENT_RE = re.compile(r"\b(?:curl|wget)\b", re.I)
_HTTP_URL_RE = re.compile(r"https?://\S+", re.I)
_HTTP_LONG_FLAG_RE = re.compile(
r"-(?:include|verbose|head|dump-header|write-out|output|remote-name|output-document)\b",
re.I,
)
# Offline captures (`-o file`, `-O`, `-e/--output`, `> file`) are not
# interactive HTTP evidence; status probes via `-w` are separately exempted.
_HTTP_OFFLINE_CAPTURE_RE = re.compile(
r"-(?:o|O|output|remote-name|output-document)\b|\s>\s*[^\s|]+", re.I
)
def _has_short_flag(command: str, *flags: str) -> bool:
"""True if any single-dash short-flag cluster contains one of `flags`.
Handles combined clusters (`-si`, `-sv`, `-Dk`) and separate tokens
(`-s -i`). Case-sensitive on purpose: `-D` (dump-header) counts while
`-d` (POST data) does not. Guarded to letters-only tokens to avoid
matching data payloads or stray dashes.
"""
wanted = set(flags)
return any(
any(ch in wanted for ch in token)
for token in re.findall(r"(?<!\S)-[A-Za-z]+", command)
)
def check_http_proof_flags(command: str) -> CheckResult:
"""Review-level guard: HTTP probes must capture the response status/headers.
SKILL.md §4 mandates `-i` or `-sv` when testing HTTP endpoints so evidence
files carry empirical status lines. Receipts from plain `curl -s` (no `-i`)
make `has_decisive_proof` fail and burn real confirmations at scoring time
(benchmark-run-20260811_175611: only 6/194 receipts carried HTTP/1.1).
Exempted: status probes (`-w %{http_code}`), HEAD (`-I`), header dumps
(`-D`), and offline captures (`-o`/`-O`/`> file`) that are not interactive
HTTP evidence.
"""
result = CheckResult()
if not _HTTP_CLIENT_RE.search(command) or not _HTTP_URL_RE.search(command):
return result
if _HTTP_LONG_FLAG_RE.search(command) or _HTTP_OFFLINE_CAPTURE_RE.search(command):
return result
if _has_short_flag(command, "i", "v", "I", "D", "w", "o", "O"):
return result
result.add_warning(
"HTTP probe without status/headers capture: add `-i` (or `-sv`) to curl "
"so the evidence file records the response status line — plain `-s` "
"produces no HTTP/1.1 line and fails decisive-proof scoring"
)
return result
def check_cross_engagement_paths(command: str, active_eng_dir: Path) -> CheckResult:
"""Block commands that reference a foreign engagement directory under engagements/."""
result = CheckResult()
@@ -626,6 +680,11 @@ def check_command(args: CheckCommandArgs) -> CheckResult:
destructive_result = check_destructive_patterns(args.command)
result.errors.extend(destructive_result.errors)
# 2c2. HTTP proof flags (review): `-i`/`-sv` so receipts are decisive
proof_result = check_http_proof_flags(args.command)
result.warnings.extend(proof_result.warnings)
result.infos.extend(proof_result.infos)
artifact_result = check_local_artifact_paths(args.command)
result.infos.extend(artifact_result.infos)
@@ -0,0 +1,54 @@
"""HTTP proof-flag guard tests — decisiveness of curl receipts at scoring time.
Regression: benchmark-run-20260811_175611 wrote 194 receipts but only 6
carried HTTP/1.1 status lines (plain `curl -s` without `-i`), so the
decisive-proof scorer rejected most of them. SKILL.md §4 mandates `-i`/`-sv`;
this guard makes the requirement a review gate.
"""
from __future__ import annotations
from plugins.violin_guard.command import check_http_proof_flags
def test_plain_silent_curl_warns_review() -> None:
result = check_http_proof_flags(
"curl -s https://duck-store.escape.tech/api/v1/products"
)
assert result.errors == []
assert len(result.warnings) == 1
assert "-i" in result.warnings[0]
assert result.exit_code() == 2 # review, not hard block
def test_curl_with_status_capture_is_clean() -> None:
for command in (
"curl -si https://duck-store.escape.tech/api/v1/products",
"curl -i -X POST https://duck-store.escape.tech/api/v1/auth/login -d '{}'",
"curl -sv https://duck-store.escape.tech/api/v1/orders/1",
"curl -I https://duck-store.escape.tech/",
"curl -D headers.txt https://duck-store.escape.tech/api/v1/products",
):
result = check_http_proof_flags(command)
assert result.errors == [] and result.warnings == [], command
assert result.exit_code() == 0, command
def test_status_probe_and_offline_capture_exempt() -> None:
for command in (
"curl -s -o /dev/null -w '%{http_code}' https://duck-store.escape.tech/api/v1/auth/login",
"curl -sO https://duck-store.escape.tech/bundle.js",
"curl -s https://duck-store.escape.tech/api/v1/products > evidence/recon/products.json",
):
result = check_http_proof_flags(command)
assert result.errors == [] and result.warnings == [], command
def test_non_http_or_non_curl_commands_untouched() -> None:
for command in (
"nmap -sV 10.10.10.10",
"grep -i admin evidence/recon/products.json",
"python3 tools/burst.py --target https://duck-store.escape.tech",
):
result = check_http_proof_flags(command)
assert result.errors == [] and result.warnings == [], command