feat: plan 16 P2 batch 2 (SEC-12 drift, SEC-29 audit fail-closed, SEC-30 replay, SEC-15 low)
- SEC-12: drift-detect adds semantic invariants — negation-flip detection (a dropped "not" now FAILS despite high char-similarity) + env must-keep patterns. - SEC-29 (X-05): governance-check audit write fails CLOSED — an unwritable audit log denies the action and empties the output (no unaudited output). - SEC-30 (X-06): approval-verify records a one-time-use nonce (sha of token/sig) and rejects replays (enforced mode / when a nonce ledger is set); dev unchanged. - SEC-15 (low): typosquat distance<=2 with the levenshtein length-sentinel bug fixed (no false positives); tool-exec fails closed with no timeout backend in enforced mode; validate-tool-input now validates nested objects/arrays recursively. Verify: new SEC suites all green via gate, run-casan4 0-FAIL, adversarial 44/44, track-c 29/0, h5-approval 12/0, no regressions. Plan-16 P2 remaining: infra-gated only (SEC-14/22/23/24/25/26). 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
8c06a55aed
commit
d695a598ee
@@ -38,6 +38,25 @@ fi
|
||||
|
||||
deny() { echo "APPROVAL_DENIED reason=$1 approver=$APPROVER action=$ACTION" >&2; exit 3; }
|
||||
|
||||
# SEC-30 (X-06): one-time-use. A verified approval token (JWT or offline signature)
|
||||
# is valid for its whole exp window, so it could be REPLAYED. Record a per-token
|
||||
# nonce (sha256 of the token/signature) and reject any repeat. Active in enforced
|
||||
# mode or when an explicit nonce ledger is configured (dev default: off).
|
||||
sha_stdin() {
|
||||
if command -v sha256sum >/dev/null 2>&1; then sha256sum | awk '{print $1}'
|
||||
else shasum -a 256 | awk '{print $1}'; fi
|
||||
}
|
||||
record_nonce_or_deny() {
|
||||
local nonce="$1"
|
||||
[[ "${CASAN_PROFILE:-}" == "prod" || -n "${CASAN_APPROVAL_NONCE_FILE:-}" ]] || return 0
|
||||
local ledger="${CASAN_APPROVAL_NONCE_FILE:-$PROJECT_ROOT/.specify/logs/level5/approval-nonces.txt}"
|
||||
mkdir -p "$(dirname "$ledger")" 2>/dev/null || true
|
||||
if [[ -f "$ledger" ]] && grep -qxF "$nonce" "$ledger" 2>/dev/null; then
|
||||
deny "approval_replayed(nonce=${nonce:0:12}…)"
|
||||
fi
|
||||
printf '%s\n' "$nonce" >> "$ledger" || deny "nonce_ledger_unwritable"
|
||||
}
|
||||
|
||||
[[ -f "$INPUT_FILE" ]] || deny "input_file_missing"
|
||||
[[ -f "$REVIEWERS_FILE" ]] || deny "reviewer_registry_missing"
|
||||
command -v openssl >/dev/null 2>&1 || deny "openssl_unavailable"
|
||||
@@ -164,6 +183,7 @@ print(f"OK role={role}")
|
||||
PY
|
||||
)" || deny "oidc_jwt_invalid(${JWT_OUT:-see_stderr})"
|
||||
ROLE="${JWT_OUT#OK role=}"
|
||||
record_nonce_or_deny "$(printf '%s' "$CASAN_APPROVAL_JWT" | sha_stdin)"
|
||||
echo "APPROVAL_OK mechanism=oidc role=$ROLE approver=$APPROVER action=$ACTION"
|
||||
exit 0
|
||||
fi
|
||||
@@ -192,5 +212,6 @@ printf '%s' "$MSG" > "$TMP"
|
||||
openssl dgst -sha256 -verify "$PUB" -signature "$SIG_FILE" "$TMP" >/dev/null 2>&1 \
|
||||
|| deny "approval_signature_invalid"
|
||||
|
||||
record_nonce_or_deny "$(sha_stdin < "$SIG_FILE")"
|
||||
echo "APPROVAL_OK role=$ROLE approver=$APPROVER action=$ACTION"
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user