diff --git a/scripts/orca-aggregate.sh b/scripts/orca-aggregate.sh index d1d86ee..098cad4 100755 --- a/scripts/orca-aggregate.sh +++ b/scripts/orca-aggregate.sh @@ -69,7 +69,25 @@ while IFS= read -r peer; do orca_log_warn "aggregate" "$peer" "skipped" "failed to read state snapshot" continue fi - printf '{"peer":"%s","state":%s}\n' "$peer" "$snapshot" >>"$merge_tmp" + # REQ-131 / F11: use jq to safely construct JSON (prevents JSON + # injection from malicious peer output). $peer is sanitized; $snapshot + # is parsed as raw JSON by jq, so control chars can't break out. + if command -v jq >/dev/null 2>&1; then + snapshot_json="$(printf '%s' "$snapshot" | jq -c '.' 2>/dev/null)" || { + orca_log_warn "aggregate" "$peer" "skipped" "peer returned invalid JSON" + continue + } + peer_escaped="${peer//\"/\\\"}" + printf '{"peer":"%s","state":%s}\n' "$peer_escaped" "$snapshot_json" >>"$merge_tmp" + else + # Fallback: validate $snapshot looks like JSON before interpolation. + if ! printf '%s' "$snapshot" | grep -qE '^\s*\{.*\}\s*$'; then + orca_log_warn "aggregate" "$peer" "skipped" "peer returned non-JSON" + continue + fi + peer_escaped="${peer//\"/\\\"}" + printf '{"peer":"%s","state":%s}\n' "$peer_escaped" "$snapshot" >>"$merge_tmp" + fi orca_log_info "aggregate" "$peer" "ok" "snapshot=$latest_json" done < <(read_peers) diff --git a/scripts/orca-pull.sh b/scripts/orca-pull.sh index a7334db..d2e2426 100755 --- a/scripts/orca-pull.sh +++ b/scripts/orca-pull.sh @@ -115,10 +115,21 @@ if [ "$FORCE" != "true" ]; then if [ -f "$DRIFT_AGG_JSON" ]; then NS_FILTER="${NAMESPACE:-}" NS_REGEX="${NS_FILTER//\//.}" - if [ -n "$NS_FILTER" ]; then - DRIFT_HITS="$(grep -o '"path"[[:space:]]*:[[:space:]]*"[^"]*"' "$DRIFT_AGG_JSON" 2>/dev/null | sed 's/.*: *"//;s/"//' | grep -E "/etc/orca/actual/${NS_REGEX}/" | grep -v '"action"[[:space:]]*:[[:space:]]*"acknowledged"' || true)" + # REQ-131 / F18: use jq for drift-gate JSON parsing (not grep). + if command -v jq >/dev/null 2>&1; then + if [ -n "$NS_FILTER" ]; then + DRIFT_HITS="$(jq -r --arg ns "$NS_FILTER" '[.events[]? | select((.path|test("/etc/orca/actual/\($ns)/")) and (.action != "acknowledged"))] | length' "$DRIFT_AGG_JSON" 2>/dev/null || true)" + else + DRIFT_HITS="$(jq -r '[.events[]? | select(.drift_confirmed == true and .action != "acknowledged")] | length' "$DRIFT_AGG_JSON" 2>/dev/null || true)" + fi else - DRIFT_HITS="$(grep -o '"drift_confirmed"[[:space:]]*:[[:space:]]*true' "$DRIFT_AGG_JSON" 2>/dev/null || true)" + # Fallback: grep (less accurate; the ack filter may not + # match the same line as the path filter). + if [ -n "$NS_FILTER" ]; then + DRIFT_HITS="$(grep -o '"path"[[:space:]]*:[[:space:]]*"[^"]*"' "$DRIFT_AGG_JSON" 2>/dev/null | sed 's/.*: *"//;s/"//' | grep -E "/etc/orca/actual/${NS_REGEX}/" || true)" + else + DRIFT_HITS="$(grep -o '"drift_confirmed"[[:space:]]*:[[:space:]]*true' "$DRIFT_AGG_JSON" 2>/dev/null || true)" + fi fi if [ -n "$DRIFT_HITS" ]; then orca_log_error "orca-pull" "$TXN_DIR" "drift-detected" "namespace=${NAMESPACE:-cluster-wide}"