Physically move the pure-code subtrees out of .specify into the package, leaving compat symlinks at the old .specify/<dir> paths so every existing reference (internal CASAN_HARNESS_ROOT + external CI/docker/mjs) keeps resolving. Runtime state stays put. Moved (git mv): scripts/ tests/ security/ templates/ config/ governance/ memory/ .specify/<dir> -> packages/casan-harness/<dir> (+ .specify/<dir> symlink) Stays in .specify (state/governance/domain, handled later): logs/ agentops/ level5/ init-options.json traceability-map.json Python `.resolve()` self-location followed the compat symlink into packages and lost the app root; generate-casan-demo-context.py, generate-agentops-dashboard.py and dashboard-server.py now walk UP for the `.specify` state marker instead of a fixed parent depth (fixes "missing trace files" in run-casan4). Full gate: PASS=64 FAIL=0 SKIP=3 (CASAN_CI_STEP_TIMEOUT_SEC=1200 — track-a ~450s runs close to the 600s default and can tip over under load; this is timing variance, not a regression — it passed cleanly with headroom). Runtime log/audit artifacts kept unstaged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
65 lines
2.0 KiB
Python
Executable File
65 lines
2.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""CASAN H4 — Suspicious base64/hex decoder (Track A, V4 encoding smuggling).
|
|
|
|
Reads text on stdin. Finds embedded base64 and hex blobs, decodes them, and
|
|
prints the decoded plaintext (one blob per line) on stdout so the caller can
|
|
re-run its injection/secret pattern scan on the *decoded* content.
|
|
|
|
Safety / no-false-positive design:
|
|
* Only blobs >= MIN_LEN characters are considered (short words are ignored).
|
|
* A decoded blob is emitted ONLY if it is mostly printable text. Random
|
|
base64-looking words (e.g. "objectives", DER key bytes) decode to
|
|
non-printable garbage and are dropped, so they can never trigger a match.
|
|
* Output is advisory: the caller decides a decoded blob is malicious only if
|
|
the decoded text itself matches a block/secret pattern.
|
|
|
|
Deterministic: same input always yields the same output.
|
|
"""
|
|
import base64
|
|
import binascii
|
|
import re
|
|
import sys
|
|
|
|
MIN_LEN = 16
|
|
PRINTABLE_RATIO = 0.8
|
|
|
|
B64_RE = re.compile(r"[A-Za-z0-9+/]{%d,}={0,2}" % MIN_LEN)
|
|
HEX_RE = re.compile(r"\b[0-9a-fA-F]{%d,}\b" % MIN_LEN)
|
|
|
|
|
|
def _mostly_printable(text: str) -> bool:
|
|
if not text:
|
|
return False
|
|
ok = sum(1 for c in text if c.isprintable() or c.isspace())
|
|
return ok >= PRINTABLE_RATIO * len(text)
|
|
|
|
|
|
def decode_blobs(data: str):
|
|
out = []
|
|
for m in B64_RE.findall(data):
|
|
pad = m + "=" * ((4 - len(m) % 4) % 4)
|
|
try:
|
|
dec = base64.b64decode(pad, validate=True)
|
|
except (binascii.Error, ValueError):
|
|
continue
|
|
txt = dec.decode("utf-8", "ignore")
|
|
if _mostly_printable(txt):
|
|
out.append(txt)
|
|
for m in HEX_RE.findall(data):
|
|
if len(m) % 2 != 0:
|
|
continue
|
|
try:
|
|
dec = bytes.fromhex(m)
|
|
except ValueError:
|
|
continue
|
|
txt = dec.decode("utf-8", "ignore")
|
|
if _mostly_printable(txt):
|
|
out.append(txt)
|
|
return out
|
|
|
|
|
|
if __name__ == "__main__":
|
|
blobs = decode_blobs(sys.stdin.read())
|
|
if blobs:
|
|
sys.stdout.write("\n".join(blobs))
|