Merge bugfix/audit-bugs into develop
This commit is contained in:
@@ -213,11 +213,20 @@ print(len(d.get('permissions',{}).get('deny',[])))
|
||||
if [ "$DENY_COUNT" = "?" ]; then
|
||||
warn "Could not parse deny count (python3 unavailable or JSON parse error)"
|
||||
else
|
||||
EXPECTED_DENY=100
|
||||
if [ "$DENY_COUNT" -eq "$EXPECTED_DENY" ] 2>/dev/null; then
|
||||
pass "Deny rules: $DENY_COUNT"
|
||||
# Expected = deny count in the last COMMITTED settings.json. A hardcoded
|
||||
# number drifts on every legit deny-list edit (false-warned for weeks at
|
||||
# 100 vs 99 — LRN-047 class); deriving from HEAD auto-tracks legit edits
|
||||
# and still flags live-vs-committed divergence.
|
||||
EXPECTED_DENY=$(git -C "$REPO" show HEAD:settings.json 2>/dev/null | python3 -c "
|
||||
import json,sys
|
||||
print(len(json.load(sys.stdin).get('permissions',{}).get('deny',[])))
|
||||
" 2>/dev/null || echo "?")
|
||||
if [ "$EXPECTED_DENY" = "?" ]; then
|
||||
warn "Could not derive expected deny count from committed settings.json"
|
||||
elif [ "$DENY_COUNT" -eq "$EXPECTED_DENY" ] 2>/dev/null; then
|
||||
pass "Deny rules: $DENY_COUNT (matches committed settings.json)"
|
||||
else
|
||||
warn "Deny rules: $DENY_COUNT (expected $EXPECTED_DENY) — settings may have been manually modified"
|
||||
warn "Deny rules: $DENY_COUNT (committed: $EXPECTED_DENY) — live settings diverge from last commit"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
@@ -310,8 +319,11 @@ else
|
||||
warn "gstack/browse/dist/ symlink missing — run: bash link.sh"
|
||||
fi
|
||||
|
||||
# Check owned skills have disable-model-invocation (skip external/symlinked skills)
|
||||
MISSING_DMI=()
|
||||
# BDR-019 (2026-06-09) stripped disable-model-invocation repo-wide so the
|
||||
# model/orchestrators can self-route. The old check required the key on
|
||||
# every owned skill — permanent false-warn since. Inverted: warn if any
|
||||
# owned skill REintroduces the key (regression watch on BDR-019).
|
||||
PRESENT_DMI=()
|
||||
for f in "$HOME/.claude/skills/"*/SKILL.md; do
|
||||
[ -f "$f" ] || continue
|
||||
dir=$(dirname "$f")
|
||||
@@ -319,19 +331,22 @@ for f in "$HOME/.claude/skills/"*/SKILL.md; do
|
||||
[ -L "$dir" ] && continue
|
||||
[ -L "$f" ] && continue
|
||||
name=$(basename "$dir")
|
||||
if ! grep -q "disable-model-invocation" "$f" 2>/dev/null; then
|
||||
MISSING_DMI+=("$name")
|
||||
if grep -q "disable-model-invocation" "$f" 2>/dev/null; then
|
||||
PRESENT_DMI+=("$name")
|
||||
fi
|
||||
done
|
||||
if [ ${#MISSING_DMI[@]} -eq 0 ]; then
|
||||
pass "All owned skills have disable-model-invocation"
|
||||
if [ ${#PRESENT_DMI[@]} -eq 0 ]; then
|
||||
pass "No owned skill carries disable-model-invocation (BDR-019)"
|
||||
else
|
||||
warn "Owned skills missing disable-model-invocation: ${MISSING_DMI[*]}"
|
||||
warn "Owned skills reintroduce disable-model-invocation (BDR-019 regression): ${PRESENT_DMI[*]}"
|
||||
fi
|
||||
|
||||
# Check expected skills are present
|
||||
# Check expected skills are present. Repo-owned skills only: gstack skills
|
||||
# (health, status, …) are OFF by default and toggled per profile — requiring
|
||||
# them here false-warns on a default install, and "run link.sh" cannot
|
||||
# restore them (they are profile-managed, not link.sh-managed).
|
||||
EXPECTED_SKILLS=(
|
||||
"analyze" "doc" "health" "init-project" "onboard" "plugin-check"
|
||||
"analyze" "doc" "init-project" "onboard" "plugin-check"
|
||||
"refactor" "ship-feature" "status"
|
||||
)
|
||||
MISSING_SKILLS=()
|
||||
@@ -341,7 +356,7 @@ for skill in "${EXPECTED_SKILLS[@]}"; do
|
||||
fi
|
||||
done
|
||||
if [ ${#MISSING_SKILLS[@]} -eq 0 ]; then
|
||||
pass "All ${#EXPECTED_SKILLS[@]} expected skills present (analyze, doc, health, init-project, onboard, plugin-check, refactor, ship-feature, status)"
|
||||
pass "All ${#EXPECTED_SKILLS[@]} expected skills present (${EXPECTED_SKILLS[*]})"
|
||||
else
|
||||
warn "Missing skills: ${MISSING_SKILLS[*]} — run: bash link.sh"
|
||||
fi
|
||||
|
||||
@@ -1 +1 @@
|
||||
ef0d630994fd7ef5f2b84fb66cd6249c493bb8736bcacd4734d7c798125018fb rtk-rewrite.sh
|
||||
871efa28daf7c06a9c9039a2875407e2536646f5d82f7e7a9c6a80dd3742929c rtk-rewrite.sh
|
||||
|
||||
+55
-29
@@ -7,8 +7,17 @@
|
||||
# which is the single source of truth (src/discover/registry.rs).
|
||||
# To add or change rewrite rules, edit the Rust registry — not this file.
|
||||
#
|
||||
# INTEGRITY PIN: the rtk binary verifies this file against
|
||||
# hooks/.rtk-hook.sha256 at execution time and refuses to run on mismatch.
|
||||
# ANY edit here must re-pin: (cd hooks && sha256sum rtk-rewrite.sh > .rtk-hook.sha256)
|
||||
#
|
||||
# Exit code protocol for `rtk rewrite`:
|
||||
# 0 + stdout Rewrite found, no deny/ask rule matched → auto-allow
|
||||
# 0 + stdout Rewrite found, no rtk deny/ask rule matched → rewrite. NO
|
||||
# permissionDecision is emitted (auto-allow dropped 2026-07-02:
|
||||
# it made rtk's registry a parallel permission authority that
|
||||
# bypassed settings.json deny/ask). The REWRITTEN command goes
|
||||
# through native evaluation; explicit `rtk <tool>` allow rules
|
||||
# in settings.json keep read-only forms frictionless.
|
||||
# 1 No RTK equivalent → pass through unchanged
|
||||
# 2 Deny rule matched → pass through (Claude Code native deny handles it)
|
||||
# 3 + stdout Ask rule matched → rewrite but let Claude Code prompt the user
|
||||
@@ -18,14 +27,27 @@ if ! command -v jq &>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! command -v rtk &>/dev/null; then
|
||||
# PATH heal: hook/tool-shell PATH may lack the cargo bin dir (hand-managed
|
||||
# ~/.bashrc can lose the cargo line — LRN-036 class). Resolve the ABSOLUTE
|
||||
# binary path: the rewritten command executes in the tool shell, whose PATH
|
||||
# the hook cannot fix — a bare `rtk …` rewrite would exit 127 there.
|
||||
RTK_BIN="$(command -v rtk 2>/dev/null || true)"
|
||||
RTK_ON_PATH=1
|
||||
if [ -z "$RTK_BIN" ]; then
|
||||
RTK_ON_PATH=0
|
||||
for _d in "$HOME/.cargo/bin" "$HOME/.local/bin"; do
|
||||
if [ -x "$_d/rtk" ]; then RTK_BIN="$_d/rtk"; break; fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$RTK_BIN" ]; then
|
||||
echo "[rtk] WARNING: rtk is not installed or not in PATH. Hook cannot rewrite commands. Install: https://github.com/rtk-ai/rtk#installation" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Version guard: rtk rewrite was added in 0.23.0.
|
||||
# Older binaries: warn once and exit cleanly (no silent failure).
|
||||
RTK_VERSION=$(rtk --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
RTK_VERSION=$("$RTK_BIN" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
if [ -n "$RTK_VERSION" ]; then
|
||||
MAJOR=$(echo "$RTK_VERSION" | cut -d. -f1)
|
||||
MINOR=$(echo "$RTK_VERSION" | cut -d. -f2)
|
||||
@@ -44,13 +66,13 @@ if [ -z "$CMD" ]; then
|
||||
fi
|
||||
|
||||
# Delegate all rewrite + permission logic to the Rust binary.
|
||||
REWRITTEN=$(rtk rewrite "$CMD" 2>/dev/null)
|
||||
REWRITTEN=$("$RTK_BIN" rewrite "$CMD" 2>/dev/null)
|
||||
EXIT_CODE=$?
|
||||
|
||||
case $EXIT_CODE in
|
||||
0)
|
||||
# Rewrite found, no permission rules matched — safe to auto-allow.
|
||||
# If the output is identical, the command was already using RTK.
|
||||
# Rewrite found. If the output is identical, the command was
|
||||
# already using RTK — nothing to do.
|
||||
[ "$CMD" = "$REWRITTEN" ] && exit 0
|
||||
;;
|
||||
1)
|
||||
@@ -70,29 +92,33 @@ case $EXIT_CODE in
|
||||
;;
|
||||
esac
|
||||
|
||||
# When rtk is NOT on PATH, a bare `rtk …` rewrite exits 127 in the tool
|
||||
# shell (whose PATH the hook cannot fix). Substitute the absolute path at
|
||||
# the string head — the only position safe to rewrite. Compound commands
|
||||
# (`a && b`) can carry further bare rtk segments we canNOT substitute
|
||||
# safely (quoted text, e.g. commit messages, may contain the same
|
||||
# pattern): if any remain at a command position, pass through unrewritten
|
||||
# — lose the compression, never emit a command that 127s.
|
||||
if [ "$RTK_ON_PATH" -eq 0 ]; then
|
||||
case "$REWRITTEN" in
|
||||
rtk\ *) REWRITTEN="$RTK_BIN ${REWRITTEN#rtk }" ;;
|
||||
esac
|
||||
if printf '%s' "$REWRITTEN" | grep -Eq '(^|[;&|][[:space:]]*)rtk[[:space:]]'; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input')
|
||||
UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
|
||||
|
||||
if [ "$EXIT_CODE" -eq 3 ]; then
|
||||
# Ask: rewrite the command, omit permissionDecision so Claude Code prompts.
|
||||
jq -n \
|
||||
--argjson updated "$UPDATED_INPUT" \
|
||||
'{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"updatedInput": $updated
|
||||
}
|
||||
}'
|
||||
else
|
||||
# Allow: rewrite the command and auto-allow.
|
||||
jq -n \
|
||||
--argjson updated "$UPDATED_INPUT" \
|
||||
'{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow",
|
||||
"permissionDecisionReason": "RTK auto-rewrite",
|
||||
"updatedInput": $updated
|
||||
}
|
||||
}'
|
||||
fi
|
||||
# Rewrite WITHOUT a permissionDecision (exit 0 and exit 3 alike): the
|
||||
# rewritten command goes through Claude Code's native allow/deny/ask
|
||||
# evaluation. Permission control lives in settings.json, not in rtk.
|
||||
jq -n \
|
||||
--argjson updated "$UPDATED_INPUT" \
|
||||
'{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"updatedInput": $updated
|
||||
}
|
||||
}'
|
||||
|
||||
@@ -182,7 +182,7 @@ printf "│ 📦 v%-45s│\n" "$CONFIG_VERSION"
|
||||
# Version check: compare local vs remote (non-blocking)
|
||||
_remote_ver=""
|
||||
if [ -n "$REPO_DIR" ] && [ -d "$REPO_DIR/.git" ]; then
|
||||
_remote_ver=$(cd "$REPO_DIR" 2>/dev/null && git fetch origin --quiet 2>/dev/null && git show origin/master:version.txt 2>/dev/null) || _remote_ver=""
|
||||
_remote_ver=$(cd "$REPO_DIR" 2>/dev/null && git fetch origin --quiet 2>/dev/null && git show origin/main:version.txt 2>/dev/null) || _remote_ver=""
|
||||
fi
|
||||
if [ -n "$_remote_ver" ] && [ "$_remote_ver" != "$CONFIG_VERSION" ]; then
|
||||
printf "│ 🔄 update available: v%-27s│\n" "$_remote_ver"
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
# --- Always-on plugins ---
|
||||
|
||||
detect_rtk() {
|
||||
command -v rtk &>/dev/null
|
||||
command -v rtk &>/dev/null && return 0
|
||||
# PATH heal: hook/session PATH may lack the cargo bin dir (LRN-036 class)
|
||||
[ -x "$HOME/.cargo/bin/rtk" ] || [ -x "$HOME/.local/bin/rtk" ]
|
||||
}
|
||||
|
||||
detect_superpowers() {
|
||||
|
||||
@@ -65,7 +65,10 @@ if has "$cand" "--help"; then ok "T5 surfaced --help candidate (BDR-001 ⇄ --he
|
||||
echo; echo "=== T6 live oracle smoke — oracles QUERY real git/fs (not a name) ==="
|
||||
if reconcile_oracle_merge_done "$REPO" "prune-memory"; then ok "T6a merge_done(prune-memory) via git log"; else no "T6a merge not found in git"; fi
|
||||
if reconcile_oracle_sha_exists "$REPO" "be1dcef"; then ok "T6b sha_exists(be1dcef) via cat-file"; else no "T6b sha missing"; fi
|
||||
dk="$MEM/../skills/darwin-skill"
|
||||
# $REPO here = lib/ (see line 12) → lib/../skills = the real skills/ dir.
|
||||
# Was "$MEM/../skills" = .claude/skills/ — the LRN-042 parasite dir, removed
|
||||
# 2026-06-30 by make plugin Step 8.5: green-for-wrong-reason (LRN-077 class).
|
||||
dk="$REPO/../skills/darwin-skill"
|
||||
if reconcile_oracle_path_present "$dk"; then ok "T6c path_present(darwin-skill) via fs"; else no "T6c path absent"; fi
|
||||
|
||||
echo; echo "================ $pass GREEN / $fail RED ================"
|
||||
|
||||
Reference in New Issue
Block a user