Standard production layout: the OKR app (was nested under AINative_OKR_CASAN5/) is now
the repository root. No more wrapper directory.
- Promote AINative_OKR_CASAN5/* -> repo root (backend/ frontend/ packages/ apps/
.specify/ docs/ infra/ nginx/ scripts/ + configs). Merge tool dirs: .gitea (kept the
active deploy ci.yml, added harness-ci.yml + runbooks), .claude (agents/commands +
launch.json), .github moved up.
- Remove redundant: 00_SUBMISSION_PACKAGE, scattered root notes (FPT_CASAN_Full.md,
tu-tuong-casan.md, casan-tu-sinh..., casan_harness_assessment.md, source-review...,
README_CASAN5_REFINED.md), casan-next-plans/ and optimize-docs/ (competition/planning
artifacts — roadmap + design history preserved in git log / commit messages).
- Update all references to the old layout:
- .gitea/workflows/{ci,harness-ci}.yml, .github/workflows/{ci,deploy}.yml:
working-directory .; drop AINative_OKR_CASAN5/ prefix; .specify/{tests,scripts}
-> packages/casan-harness/... (.specify/logs state kept)
- .claude/launch.json, .gitea/*-runbook.md: path prefixes
- CLAUDE.md, README.md: docs/input -> apps/okr/domain/input
- policy-bundle.yaml: 8 policy paths -> packages/casan-harness/...; manifest re-signed
- secrets-scan.sh: fixture excludes -> new package/domain paths.
Full gate from the new root: PASS=64 FAIL=0 SKIP=3.
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))
|