feat: casan chat optz
This commit is contained in:
@@ -199,7 +199,10 @@ ALERT_PATTERNS=(
|
||||
)
|
||||
|
||||
EMAIL_REGEX='[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
|
||||
PHONE_REGEX='(\+?[0-9][0-9 .-]{8,}[0-9])'
|
||||
# Keep the fallback aligned with pii-rules.yaml. The previous separator-heavy
|
||||
# expression treated ISO dates such as 2026-07-19 as phone numbers and corrupted
|
||||
# timestamps in certified evidence.
|
||||
PHONE_REGEX='([+]?[0-9]{9,15})'
|
||||
PERSONAL_ID_REGEX='\b[0-9]{9,12}\b'
|
||||
CREDIT_CARD_REGEX='\b([0-9]{4}[- ]?){3}[0-9]{4}\b'
|
||||
SECRET_REGEX='(API[_-]?KEY|ACCESS[_-]?TOKEN|REFRESH[_-]?TOKEN|PASSWORD|JWT[_-]?SECRET|SECRET)[[:space:]]*[:=][[:space:]]*[^[:space:]]+'
|
||||
@@ -313,15 +316,20 @@ if [[ "$MODE" == "input" ]]; then
|
||||
# genuinely novel paraphrase slips through as low-risk, so a still-allowed
|
||||
# input is routed to the model classifier. Semantic can only ADD a block,
|
||||
# never remove one. Two modes:
|
||||
# * CASAN_SECURITY_STRICT=1 — semantic is REQUIRED (V1). If the model is
|
||||
# unreachable or returns no usable verdict we FAIL CLOSED (block); never a
|
||||
# silent skip. Off by default so CI without a model stays non-strict.
|
||||
# * CASAN_SECURITY_STRICT=1 — semantic is REQUIRED (V1). An explicitly
|
||||
# requested strict scan still fails closed when the classifier is down.
|
||||
# Production's implicit strict mode uses a risk-based degraded verdict so
|
||||
# a classifier outage cannot take down every read-only Ask CASAN request.
|
||||
# * CASAN_SEMANTIC_CLASSIFY=1 (non-strict) — best-effort. On model outage we
|
||||
# keep the regex verdict but log SEMANTIC_SKIPPED loudly (no silent pass).
|
||||
# SEC-17 (ARCH-03): strict is ON when explicitly set, OR unset under prod profile
|
||||
# (secure-by-default). An explicit CASAN_SECURITY_STRICT=0 (internal scans) wins.
|
||||
STRICT_ON=0
|
||||
if [[ "${CASAN_SECURITY_STRICT:-0}" == "1" || ( -z "${CASAN_SECURITY_STRICT+x}" && "${CASAN_PROFILE:-}" == "prod" ) ]]; then
|
||||
STRICT_EXPLICIT=0
|
||||
if [[ "${CASAN_SECURITY_STRICT:-0}" == "1" ]]; then
|
||||
STRICT_ON=1
|
||||
STRICT_EXPLICIT=1
|
||||
elif [[ -z "${CASAN_SECURITY_STRICT+x}" && "${CASAN_PROFILE:-}" == "prod" ]]; then
|
||||
STRICT_ON=1
|
||||
fi
|
||||
SEMANTIC_REQUIRED=0
|
||||
@@ -342,13 +350,19 @@ if [[ "$MODE" == "input" ]]; then
|
||||
MATCHED_RULES+=("semantic-injection")
|
||||
elif [[ -z "$SEM_VERDICT" ]]; then
|
||||
# Model unreachable / no usable verdict.
|
||||
if [[ "$STRICT_ON" == "1" ]]; then
|
||||
if [[ "$STRICT_EXPLICIT" == "1" || "${CASAN_SECURITY_UNAVAILABLE_POLICY:-risk_based}" == "block" ]]; then
|
||||
STATUS="blocked"; ACTION="block"; RISK_LEVEL="high"
|
||||
MATCHED_RULES+=("semantic-strict-unavailable")
|
||||
casan_log error security "SEMANTIC_STRICT_FAIL_CLOSED trace_id=$TRACE_ID reason=model_unavailable action=block"
|
||||
else
|
||||
[[ "$RISK_LEVEL" == "low" ]] && RISK_LEVEL="medium"
|
||||
ACTION="alert"
|
||||
MATCHED_RULES+=("semantic-unavailable")
|
||||
casan_log warn security "SEMANTIC_SKIPPED trace_id=$TRACE_ID reason=model_unavailable action=keep_regex_verdict hint=set_CASAN_SECURITY_STRICT=1_to_fail_closed"
|
||||
if [[ "$STRICT_ON" == "1" ]]; then
|
||||
casan_log warn security "SEMANTIC_DEGRADED trace_id=$TRACE_ID reason=model_unavailable action=keep_deterministic_verdict hint=set_CASAN_SECURITY_UNAVAILABLE_POLICY=block_to_fail_closed"
|
||||
else
|
||||
casan_log warn security "SEMANTIC_SKIPPED trace_id=$TRACE_ID reason=model_unavailable action=keep_regex_verdict hint=set_CASAN_SECURITY_STRICT=1_to_fail_closed"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -389,6 +403,12 @@ fi
|
||||
INPUT_HASH="$(printf '%s' "$CONTENT" | hash_text)"
|
||||
OUTPUT_HASH="$(printf '%s' "$SAFE_CONTENT" | hash_text)"
|
||||
RULES_JSON="$(printf '%s\n' "${MATCHED_RULES[@]:-}" | "$PYTHON_BIN" -c 'import json,sys; print(json.dumps([x for x in sys.stdin.read().splitlines() if x]))')"
|
||||
CATEGORIES_CSV="$(printf '%s\n' "${MATCHED_RULES[@]:-}" | "$PYTHON_BIN" -c 'import sys; rules=sys.stdin.read().splitlines(); cats=[]
|
||||
for rule in rules:
|
||||
cat = (rule.split(":", 1)[0] if ":" in rule else rule).replace("semantic-strict-unavailable", "semantic-availability").replace("semantic-unavailable", "semantic-availability")
|
||||
if cat and cat not in cats: cats.append(cat)
|
||||
print(",".join(cats))')"
|
||||
CATEGORIES_JSON="$(printf '%s' "$CATEGORIES_CSV" | "$PYTHON_BIN" -c 'import json,sys; print(json.dumps([x for x in sys.stdin.read().split(",") if x]))')"
|
||||
|
||||
TRACE_FILE="$TRACE_DIR/security-$TRACE_ID.json"
|
||||
cat > "$TRACE_FILE" <<EOF
|
||||
@@ -401,13 +421,14 @@ cat > "$TRACE_FILE" <<EOF
|
||||
"action": "$ACTION",
|
||||
"risk_level": "$RISK_LEVEL",
|
||||
"matched_rules": $RULES_JSON,
|
||||
"matched_categories": $CATEGORIES_JSON,
|
||||
"input_hash": "$INPUT_HASH",
|
||||
"output_hash": "$OUTPUT_HASH"
|
||||
}
|
||||
EOF
|
||||
|
||||
printf '{"timestamp":"%s","trace_id":"%s","harness":"H4-security","mode":"%s","status":"%s","action":"%s","risk_level":"%s","input_hash":"%s","output_hash":"%s"}\n' \
|
||||
"$TIMESTAMP" "$TRACE_ID" "$MODE" "$STATUS" "$ACTION" "$RISK_LEVEL" "$INPUT_HASH" "$OUTPUT_HASH" >> "$AUDIT_DIR/security.jsonl"
|
||||
printf '{"timestamp":"%s","trace_id":"%s","harness":"H4-security","mode":"%s","status":"%s","action":"%s","risk_level":"%s","matched_categories":%s,"input_hash":"%s","output_hash":"%s"}\n' \
|
||||
"$TIMESTAMP" "$TRACE_ID" "$MODE" "$STATUS" "$ACTION" "$RISK_LEVEL" "$CATEGORIES_JSON" "$INPUT_HASH" "$OUTPUT_HASH" >> "$AUDIT_DIR/security.jsonl"
|
||||
|
||||
if [[ "$STATUS" == "blocked" ]]; then
|
||||
: > "$OUTPUT_FILE"
|
||||
@@ -417,4 +438,4 @@ fi
|
||||
|
||||
printf '%s\n' "$SAFE_CONTENT" > "$OUTPUT_FILE"
|
||||
STATUS_UPPER="$(printf '%s' "$STATUS" | tr '[:lower:]' '[:upper:]')"
|
||||
echo "SECURITY_${STATUS_UPPER} trace_id=$TRACE_ID risk=$RISK_LEVEL action=$ACTION output=$OUTPUT_FILE"
|
||||
echo "SECURITY_${STATUS_UPPER} trace_id=$TRACE_ID risk=$RISK_LEVEL action=$ACTION categories=$CATEGORIES_CSV output=$OUTPUT_FILE"
|
||||
|
||||
Reference in New Issue
Block a user