feat: plan 16 P1 (SEC-08/09/19/20/21) — fail-open/DoS/authz hardening
- SEC-08 pii-mask: fail-closed on missing rules / broken regex (no unmasked leak) - SEC-09: input-size cap + fail-closed reads (security-check/drift-detect/context-compress); non-UTF8 no longer crashes - SEC-19: control-plane store POSIX flock + atomic tmp+rename write - SEC-20: new toolchain-verify.sh (missing/PATH-shadowed/in-workspace binary -> refuse); wired into harness-preflight - SEC-21: model-call timeout 180->60s configurable + per-run call budget - SEC-11 realized by SEC-17 prod profile (no code) - 5 fail-able test suites wired into ci-harness-gate.sh; test-integrity manifest regenerated Verify: SEC+integrity gate 16/0, run-casan4 0-FAIL, adversarial 44/44, no regressions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8c3c5e8bff
commit
e70f0815ab
@@ -55,11 +55,37 @@ def estimate_tokens(text: str) -> int:
|
||||
return len(text.split())
|
||||
|
||||
|
||||
# SEC-09 (M-10): bound input size (DoS) and read fail-closed. Non-UTF8 degrades via
|
||||
# errors="replace" instead of crashing; oversize/unreadable input exits non-zero and
|
||||
# emits nothing (never a crash traceback, never silent truncation).
|
||||
MAX_BYTES = int(os.environ.get("CASAN_MAX_INPUT_BYTES", str(2 * 1024 * 1024)))
|
||||
|
||||
|
||||
def read_capped(src: str) -> str:
|
||||
try:
|
||||
if src == "-":
|
||||
data = sys.stdin.buffer.read(MAX_BYTES + 1)
|
||||
else:
|
||||
with open(src, "rb") as fh:
|
||||
data = fh.read(MAX_BYTES + 1)
|
||||
except OSError as exc:
|
||||
print(f"COMPRESS_FAIL unreadable_input: {exc}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
if len(data) > MAX_BYTES:
|
||||
print(f"COMPRESS_FAIL input_exceeds_cap({MAX_BYTES}B) fail-closed", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
return data.decode("utf-8", errors="replace")
|
||||
|
||||
|
||||
def load_patterns(path: str):
|
||||
if not path:
|
||||
return []
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
return [line.strip() for line in fh if line.strip()]
|
||||
try:
|
||||
with open(path, encoding="utf-8", errors="replace") as fh:
|
||||
return [line.strip() for line in fh if line.strip()]
|
||||
except OSError as exc:
|
||||
print(f"COMPRESS_FAIL must_keep_file_unreadable: {exc}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def is_must_keep(line: str, patterns) -> bool:
|
||||
@@ -114,7 +140,7 @@ def main() -> int:
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
raw = sys.stdin.read() if args.input == "-" else open(args.input, encoding="utf-8").read()
|
||||
raw = read_capped(args.input)
|
||||
must = load_patterns(args.must_keep_file)
|
||||
|
||||
if args.failed:
|
||||
|
||||
Reference in New Issue
Block a user