fix(limits): 4 audit fixes — atomic cache, jq guard, key argv leak, tonumber
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
Claude critic audit of v0.43 kei-limits.sh found 4 real issues. All fixed.
[HIGH] Non-atomic cache write
Was: jq > $CACHE truncated before jq ran — transient failure wiped cache.
Now: stage in mktemp, validate non-empty, atomic mv. Preserves last-known-good.
[HIGH] tonumber threw on non-numeric balance → emptied --argjson → killed assembler
Was: jq tonumber on $avail aborted on any non-numeric. Probe returned empty.
Now: tonumber? // 0 swallows parse errors. Plus _safe_json wrapper validates
each probe's output before --argjson — any single probe failure can no
longer poison the whole cache.
[MEDIUM] MOONSHOT_API_KEY leaked to ps / /proc/<pid>/cmdline via curl argv
Was: curl -H 'Authorization: Bearer $TOKEN' — token visible to local users.
Now: token fed via curl --config - (stdin) — never on argv.
[MEDIUM] No jq runtime guard (40+ sibling scripts have it)
Was: jq used unconditionally; on missing-jq host the script spewed parse
errors and wiped the cache.
Now: command -v jq check at top, clear error + early exit.
Verified: 'kei limits' still produces honest report; cache atomicity holds
under simulated failure; install lands all v0.40+v0.42+v0.43 components.
This commit is contained in:
parent
633ee4aeeb
commit
a9e01a6b17
1 changed files with 69 additions and 18 deletions
|
|
@ -22,6 +22,12 @@
|
|||
|
||||
set -u
|
||||
|
||||
# v0.43-fix #4: jq runtime guard (convention with 40+ sibling scripts).
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
echo "kei-limits: jq required (brew install jq / apt install jq)" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
CACHE="${KEI_LIMITS_CACHE:-$HOME/.claude/pet/limits-cache.json}"
|
||||
mkdir -p "$(dirname "$CACHE")"
|
||||
|
||||
|
|
@ -60,43 +66,88 @@ probe_kimi() {
|
|||
printf '%s' '{"status":"need-key","note":"set MOONSHOT_API_KEY in env to fetch live balance","dashboard":"https://platform.kimi.ai"}'
|
||||
return
|
||||
fi
|
||||
# Real probe: Moonshot balance API. Honest about what we get back.
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
printf '%s' '{"status":"no-curl","note":"curl required for live probe"}'
|
||||
return
|
||||
fi
|
||||
# v0.43-fix #3: feed the bearer token via stdin (--config -), NOT as
|
||||
# a curl argv. argv is visible to `ps`/`/proc/<pid>/cmdline` for any
|
||||
# local user. Audit found this on critic@claude.
|
||||
local resp
|
||||
resp=$(curl -sS --max-time 5 \
|
||||
-H "Authorization: Bearer $MOONSHOT_API_KEY" \
|
||||
"https://api.moonshot.ai/v1/users/me/balance" 2>/dev/null || echo '')
|
||||
resp=$(printf 'header = "Authorization: Bearer %s"\n' "$MOONSHOT_API_KEY" \
|
||||
| curl -sS --max-time 5 --config - \
|
||||
"https://api.moonshot.ai/v1/users/me/balance" 2>/dev/null \
|
||||
|| echo '')
|
||||
if [ -z "$resp" ]; then
|
||||
printf '%s' '{"status":"probe-failed","note":"no response (network / wrong key)"}'
|
||||
return
|
||||
fi
|
||||
# Validate JSON shape.
|
||||
local avail cash voucher
|
||||
# v0.43-fix #2: tonumber? swallows parse errors (was: tonumber threw on
|
||||
# any non-numeric balance, emitted empty JSON, poisoned the assembler
|
||||
# --argjson → whole cache wiped).
|
||||
local avail
|
||||
avail=$(printf '%s' "$resp" | jq -r '.data.available_balance // empty' 2>/dev/null)
|
||||
if [ -z "$avail" ]; then
|
||||
printf '%s' '{"status":"probe-failed","note":"API returned non-balance response"}'
|
||||
return
|
||||
fi
|
||||
local cash voucher
|
||||
cash=$(printf '%s' "$resp" | jq -r '.data.cash_balance // 0' 2>/dev/null)
|
||||
voucher=$(printf '%s' "$resp" | jq -r '.data.voucher_balance // 0' 2>/dev/null)
|
||||
jq -n --arg s "live" --arg a "$avail" --arg c "$cash" --arg v "$voucher" \
|
||||
'{status:$s, available_balance_usd:($a|tonumber), cash_balance_usd:($c|tonumber), voucher_balance_usd:($v|tonumber), dashboard:"https://platform.kimi.ai"}'
|
||||
'{status:$s, available_balance_usd:($a|tonumber? // 0), cash_balance_usd:($c|tonumber? // 0), voucher_balance_usd:($v|tonumber? // 0), dashboard:"https://platform.kimi.ai"}'
|
||||
}
|
||||
|
||||
# --- assemble cache JSON ---------------------------------------------------
|
||||
# v0.43-fix #1: atomic stage-and-rename. Was: `jq > "$CACHE"` truncated the
|
||||
# cache BEFORE jq ran — a transient failure permanently wiped the cache.
|
||||
# Now: build in tmpfile, validate non-empty, then atomic mv. Preserves
|
||||
# last-known-good across probe failures.
|
||||
# v0.43-fix #2 (defense-in-depth): if any individual probe returns empty
|
||||
# string, substitute a status marker so --argjson never sees invalid JSON.
|
||||
|
||||
_safe_json() {
|
||||
local payload="$1"
|
||||
if [ -z "$payload" ]; then
|
||||
printf '%s' '{"status":"probe-empty","note":"probe returned empty result"}'
|
||||
return
|
||||
fi
|
||||
# Validate parses.
|
||||
if ! printf '%s' "$payload" | jq empty 2>/dev/null; then
|
||||
printf '%s' '{"status":"probe-invalid","note":"probe returned non-JSON"}'
|
||||
return
|
||||
fi
|
||||
printf '%s' "$payload"
|
||||
}
|
||||
|
||||
P_CLAUDE=$(_safe_json "$(probe_claude)")
|
||||
P_GROK=$(_safe_json "$(probe_grok)")
|
||||
P_AGY=$(_safe_json "$(probe_agy)")
|
||||
P_COPILOT=$(_safe_json "$(probe_copilot)")
|
||||
P_KIMI=$(_safe_json "$(probe_kimi)")
|
||||
|
||||
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
jq -n \
|
||||
TMP=$(mktemp "${CACHE}.XXXXXX")
|
||||
if jq -n \
|
||||
--arg ts "$NOW" \
|
||||
--argjson claude "$(probe_claude)" \
|
||||
--argjson grok "$(probe_grok)" \
|
||||
--argjson agy "$(probe_agy)" \
|
||||
--argjson copilot "$(probe_copilot)" \
|
||||
--argjson kimi "$(probe_kimi)" \
|
||||
--argjson claude "$P_CLAUDE" \
|
||||
--argjson grok "$P_GROK" \
|
||||
--argjson agy "$P_AGY" \
|
||||
--argjson copilot "$P_COPILOT" \
|
||||
--argjson kimi "$P_KIMI" \
|
||||
'{ts:$ts, claude:$claude, grok:$grok, agy:$agy, copilot:$copilot, kimi:$kimi}' \
|
||||
> "$CACHE"
|
||||
> "$TMP" 2>/dev/null \
|
||||
&& [ -s "$TMP" ]; then
|
||||
mv -f "$TMP" "$CACHE"
|
||||
else
|
||||
rm -f "$TMP" 2>/dev/null
|
||||
echo "kei-limits: cache refresh failed — keeping previous cache" >&2
|
||||
if [ ! -f "$CACHE" ]; then
|
||||
# No prior cache + assembly failed: write a minimal marker so consumers
|
||||
# don't see a missing file as their failure mode.
|
||||
printf '%s\n' '{"ts":"","status":"assembly-failed"}' > "$CACHE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- output ----------------------------------------------------------------
|
||||
if [ "$JSON_OUT" = "1" ]; then
|
||||
|
|
|
|||
Loading…
Reference in a new issue