From 5679bef35a46c43448d028ced5a8b02306a862a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=99=BD?= <31078449+Nowhitestar@users.noreply.github.com> Date: Thu, 30 Apr 2026 19:08:16 +0800 Subject: [PATCH 1/3] fix(security): harden config reader and document security posture - check-mcp.sh: replace shell-interpolated $HOME inside python3 -c with os.path.expanduser('~/.claude.json') to eliminate a latent shell-injection vector when $HOME contains quote characters. Also use typed except. - SECURITY.md: add a Security Posture section documenting what the skill reads/writes, network egress, credential handling, and supply chain. Add a Scanner false-positive notes section explaining why VirusTotal and ClawScan may flag check-update.sh (self-update) and check-mcp.sh (credential read) as Suspicious, and why both patterns are intentional. Co-Authored-By: Claude Opus 4.7 (1M context) --- SECURITY.md | 47 ++++++++++++++++++++++++++++ skills/agentkey/scripts/check-mcp.sh | 7 +++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index a711429..5a4d255 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -25,3 +25,50 @@ Pre-1.0 releases are no longer maintained. Please upgrade to the latest 1.x rele ## Disclosure We follow coordinated disclosure. Once a fix is available, we publish a security advisory via GitHub Security Advisories and credit the reporter (with permission). + +## Security Posture + +### What this skill does on your machine + +The skill ships two helper scripts that the agent invokes: + +- **`skills/agentkey/scripts/check-update.sh`** — at most every 24 hours, calls `https://api.github.com/repos/chainbase-labs/agentkey/releases/latest` to learn the latest version. When the local version differs and the skill lives inside a git working tree (`.git/` present), the script shallow-fetches the new release tag and checks it out in place; otherwise it prints `UPDATE_FAILED: …` and the user updates manually. The release tag is only ever moved by [release-please](https://github.com/googleapis/release-please) from `main`, so the auto-applied artifact is always traceable to a merged PR. +- **`skills/agentkey/scripts/check-mcp.sh`** — reads `~/.claude.json` and `~/.env.local` to verify the AgentKey MCP server is registered and the API key is present. **Read-only**; no network egress; output is a single status code. + +### Files the skill reads or writes + +| Path | Mode | Purpose | +|---|---|---| +| `~/.claude.json` | read | Detect MCP registration; read `AGENTKEY_API_KEY` env value | +| `~/.env.local` | read | Fallback location for `AGENTKEY_API_KEY` | +| `${TMPDIR}/agentkey-update-check` | read/write | Cache for the update check | +| `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) / `%APPDATA%/Claude/...` (Windows) | written by the separate `npx -y @agentkey/mcp --auth-login` command, **not** by the skill | MCP registration | +| `~/.cursor/mcp.json` | written by `--auth-login`, **not** by the skill | MCP registration | + +### Network egress from the skill + +| Destination | When | Why | +|---|---|---| +| `api.github.com` | At most every 24 hours | Look up the latest release tag | +| npm registry | When the user first runs `npx -y @agentkey/mcp` | Resolve and run the MCP server | + +### Credential handling + +- `AGENTKEY_API_KEY` is stored only in user-local config files (paths above). +- The key leaves the user's machine only as the `Authorization` header to AgentKey's own API endpoints. +- The skill collects no telemetry. + +### Supply chain + +- Releases are cut by [release-please](https://github.com/googleapis/release-please) from merged Conventional-Commit PRs on `main` — no manual artifact uploads, no manual tag pushes. +- The companion `@agentkey/mcp` npm package is published from the same organization. Users invoke it via `npx -y @agentkey/mcp`, which resolves to the latest published version at runtime — this is the same threat model as any other `npx`-launched CLI. +- Future work: SLSA provenance attestation via GitHub OIDC + sigstore; signed npm provenance. + +## Scanner false-positive notes + +Automated scanners (VirusTotal, ClawScan) may flag this skill as `Suspicious` due to two intentional patterns. We document them here so reviewers can verify intent: + +1. **`check-update.sh` contacts GitHub and self-applies a release tag.** Pattern matches "remote-controlled binary update" heuristics. **Why this is intentional:** the script only fast-forwards to a tag on `chainbase-labs/agentkey`, and that tag is only ever set by release-please from a merged PR on `main`. The auto-update preserves UX parity with `npx`-resolved CLIs (which also re-resolve at runtime), and the script source — including the exact `git fetch` + `git checkout` invocation — is auditable in this repository. +2. **`check-mcp.sh` reads `*API_KEY*` env values.** Pattern matches "credential harvesting" heuristics. **Why this is intentional:** the read is local-only, never transmitted, and exists purely to confirm `AGENTKEY_API_KEY` is configured before the agent attempts an MCP call. The script's only output is a one-word status code (`MCP_OK` / `MCP_NO_KEY` / `MCP_NOT_CONFIGURED`); the key value itself is discarded. + +If you operate a scanner and need additional context to triage, please email `support@chainbase.com`. diff --git a/skills/agentkey/scripts/check-mcp.sh b/skills/agentkey/scripts/check-mcp.sh index ba0dc87..765871e 100755 --- a/skills/agentkey/scripts/check-mcp.sh +++ b/skills/agentkey/scripts/check-mcp.sh @@ -14,11 +14,12 @@ check_key_exists() { if [ -f "$HOME/.claude.json" ]; then local key_val key_val=$(python3 -c " -import json, sys +import json, os try: - d = json.load(open('$HOME/.claude.json')) + with open(os.path.expanduser('~/.claude.json')) as f: + d = json.load(f) print(d.get('mcpServers', {}).get('agentkey', {}).get('env', {}).get('AGENTKEY_API_KEY', '')) -except: pass +except Exception: pass " 2>/dev/null | tr -d '[:space:]') [ -n "$key_val" ] && return 0 fi From fc418fd16a8098cb42f468a24a365f9e6f0e6d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=99=BD?= <31078449+Nowhitestar@users.noreply.github.com> Date: Thu, 30 Apr 2026 19:38:12 +0800 Subject: [PATCH 2/3] fix(security): make check-update.sh notify-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace self-applying git fetch+checkout with a notify-only flow modeled after gstack's update-check (https://github.com/garrytan/gstack): - check-update.sh now only emits UP_TO_DATE | UPGRADE_AVAILABLE | (silent). Never invokes git. Never modifies the install. The user runs `npx skills update chainbase-labs/agentkey` themselves. - Two-tier cache TTL (60 min for UP_TO_DATE, 12 h for UPGRADE_AVAILABLE) so we detect new releases quickly but don't hammer the GitHub API once an upgrade is known. - Validate remote response with a regex so HTML error pages or rate-limit JSON don't get cached as a fake version. - Cache invalidates automatically when local version moves past the cached "old" — handles the manually-updated user case. - SKILL.md Step 0 routing now handles UPGRADE_AVAILABLE and tells the user the manual update command. UPDATED / UPDATE_FAILED handlers removed (script no longer emits those). - SECURITY.md updated to reflect the new posture: no self-modification, no git, single GET to api.github.com, exit. Scanner false-positive note rewritten accordingly — the "remote-controlled binary update" framing no longer fits. Co-Authored-By: Claude Opus 4.7 (1M context) --- SECURITY.md | 4 +- skills/agentkey/SKILL.md | 5 +- skills/agentkey/scripts/check-update.sh | 70 +++++++++++++++---------- 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 5a4d255..6fd8558 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -32,7 +32,7 @@ We follow coordinated disclosure. Once a fix is available, we publish a security The skill ships two helper scripts that the agent invokes: -- **`skills/agentkey/scripts/check-update.sh`** — at most every 24 hours, calls `https://api.github.com/repos/chainbase-labs/agentkey/releases/latest` to learn the latest version. When the local version differs and the skill lives inside a git working tree (`.git/` present), the script shallow-fetches the new release tag and checks it out in place; otherwise it prints `UPDATE_FAILED: …` and the user updates manually. The release tag is only ever moved by [release-please](https://github.com/googleapis/release-please) from `main`, so the auto-applied artifact is always traceable to a merged PR. +- **`skills/agentkey/scripts/check-update.sh`** — **notify-only**. At most every 60 minutes (12 hours once an upgrade is known), it calls `https://api.github.com/repos/chainbase-labs/agentkey/releases/latest`, compares the tag against the local `version.txt`, and prints `UPGRADE_AVAILABLE ` if they differ. It never runs `git fetch`, `git checkout`, or any other write operation. Updates are entirely user-driven (`npx skills update chainbase-labs/agentkey`). - **`skills/agentkey/scripts/check-mcp.sh`** — reads `~/.claude.json` and `~/.env.local` to verify the AgentKey MCP server is registered and the API key is present. **Read-only**; no network egress; output is a single status code. ### Files the skill reads or writes @@ -68,7 +68,7 @@ The skill ships two helper scripts that the agent invokes: Automated scanners (VirusTotal, ClawScan) may flag this skill as `Suspicious` due to two intentional patterns. We document them here so reviewers can verify intent: -1. **`check-update.sh` contacts GitHub and self-applies a release tag.** Pattern matches "remote-controlled binary update" heuristics. **Why this is intentional:** the script only fast-forwards to a tag on `chainbase-labs/agentkey`, and that tag is only ever set by release-please from a merged PR on `main`. The auto-update preserves UX parity with `npx`-resolved CLIs (which also re-resolve at runtime), and the script source — including the exact `git fetch` + `git checkout` invocation — is auditable in this repository. +1. **`check-update.sh` contacts GitHub.** Pattern may match "remote-controlled binary update" heuristics. **Why this is intentional:** the script is notify-only — it issues a single `GET https://api.github.com/repos/chainbase-labs/agentkey/releases/latest`, compares the tag against `version.txt`, prints a one-line status (`UP_TO_DATE` or `UPGRADE_AVAILABLE `), and exits. It never writes anywhere except the cache file at `${TMPDIR}/agentkey-update-check`, never invokes `git`, and never executes downloaded code. Applying the update is the user's manual `npx skills update chainbase-labs/agentkey`. 2. **`check-mcp.sh` reads `*API_KEY*` env values.** Pattern matches "credential harvesting" heuristics. **Why this is intentional:** the read is local-only, never transmitted, and exists purely to confirm `AGENTKEY_API_KEY` is configured before the agent attempts an MCP call. The script's only output is a one-word status code (`MCP_OK` / `MCP_NO_KEY` / `MCP_NOT_CONFIGURED`); the key value itself is discarded. If you operate a scanner and need additional context to triage, please email `support@chainbase.com`. diff --git a/skills/agentkey/SKILL.md b/skills/agentkey/SKILL.md index af7b91e..7bbd148 100644 --- a/skills/agentkey/SKILL.md +++ b/skills/agentkey/SKILL.md @@ -10,12 +10,11 @@ version: 1.0.0 **Step 0 (always run first):** -1. Run the auto-update check silently (cached 24h — repeat calls are <10ms): +1. Run the version check silently (cached — repeat calls are <10ms): ```bash bash "${CLAUDE_PLUGIN_ROOT:-$HOME/.claude}/skills/agentkey/scripts/check-update.sh" 2>/dev/null ``` - - `UPDATED: vX.Y.Z` → Tell the user once: "✓ AgentKey Skill updated to vX.Y.Z." - - `UPDATE_FAILED: ...` → Show the message verbatim to the user. + - `UPGRADE_AVAILABLE ` → Tell the user once: "AgentKey v\ is available (currently on v\). Run \`npx skills update chainbase-labs/agentkey\` to update." - `UP_TO_DATE` or empty → continue silently. 2. Confirm the 4 MCP tools — `list_tools`, `find_tools`, `describe_tool`, `execute_tool` — are visible in the current toolset. If **any** are missing → **Setup** (regardless of what the user asked). Do not attempt Query without all 4. diff --git a/skills/agentkey/scripts/check-update.sh b/skills/agentkey/scripts/check-update.sh index 4689a35..1b36351 100755 --- a/skills/agentkey/scripts/check-update.sh +++ b/skills/agentkey/scripts/check-update.sh @@ -1,11 +1,19 @@ #!/bin/bash -# AgentKey — Auto-update to latest GitHub Release. +# AgentKey — Notify when a newer release is available on GitHub. +# Notify-only: this script never modifies the install. It tells the agent +# there's a new version; the user runs the update manually. +# # Result cached in TMPDIR to keep repeat skill invocations fast. -# Outputs a single line: UP_TO_DATE | UPDATED: vX.Y.Z | UPDATE_FAILED: +# +# Outputs a single line, or nothing: +# UP_TO_DATE — local matches latest release +# UPGRADE_AVAILABLE — local differs from latest release +# (empty / silent) — no version file, network down, or +# unexpected response (we retry next time) REPO="chainbase-labs/agentkey" -CACHE_TTL_SUCCESS=86400 # 24h for UP_TO_DATE -CACHE_TTL_FAILURE=3600 # 1h for UPDATE_FAILED (retry sooner) +CACHE_TTL_UP_TO_DATE=3600 # 60 min — detect new releases quickly +CACHE_TTL_UPGRADE=43200 # 12 h — keep nagging once an upgrade is known CURL_TIMEOUT=3 PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." 2>/dev/null && pwd)}" @@ -14,7 +22,6 @@ CACHE_FILE="${TMPDIR:-/tmp}/agentkey-update-check" LOCAL_VERSION=$(tr -d '[:space:]' < "$VERSION_FILE" 2>/dev/null) if [ -z "$LOCAL_VERSION" ]; then - echo "UP_TO_DATE" exit 0 fi @@ -22,49 +29,54 @@ fi if [ -f "$CACHE_FILE" ]; then MTIME=$(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) AGE=$(( $(date +%s) - MTIME )) - case "$(head -1 "$CACHE_FILE" 2>/dev/null)" in - "UPDATE_FAILED:"*) TTL=$CACHE_TTL_FAILURE ;; - *) TTL=$CACHE_TTL_SUCCESS ;; + CACHED=$(head -1 "$CACHE_FILE" 2>/dev/null || true) + case "$CACHED" in + "UP_TO_DATE") TTL=$CACHE_TTL_UP_TO_DATE ;; + "UPGRADE_AVAILABLE "*) TTL=$CACHE_TTL_UPGRADE ;; + *) TTL=0 ;; esac if [ "$AGE" -ge 0 ] && [ "$AGE" -lt "$TTL" ]; then - cat "$CACHE_FILE" - exit 0 + # Cached UPGRADE_AVAILABLE may be stale w.r.t. the local version (user + # might have updated since we cached). Re-emit only if old still matches. + case "$CACHED" in + "UP_TO_DATE") + echo "UP_TO_DATE" + exit 0 + ;; + "UPGRADE_AVAILABLE "*) + CACHED_OLD=$(echo "$CACHED" | awk '{print $2}') + if [ "$CACHED_OLD" = "$LOCAL_VERSION" ]; then + echo "$CACHED" + exit 0 + fi + # Local moved on — fall through to re-check. + ;; + esac fi fi -# Remote check — fetch latest release tag. +# Slow path: fetch latest release tag from GitHub. LATEST_TAG=$(curl -sf --max-time "$CURL_TIMEOUT" \ "https://api.github.com/repos/$REPO/releases/latest" 2>/dev/null \ | grep -m1 '"tag_name"' \ | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') LATEST_VERSION=${LATEST_TAG#[vV]} -# Network failure — stay silent; skip caching so we retry on the next call. -if [ -z "$LATEST_VERSION" ]; then - echo "UP_TO_DATE" +# Validate response looks like a version number — rejects HTML error pages, +# rate-limit JSON, and other surprises that slipped past curl -f. +if ! echo "$LATEST_VERSION" | grep -qE '^[0-9]+\.[0-9.]+$'; then + # Network failure or unexpected response — stay silent and don't cache, + # so the next call retries. exit 0 fi -# Already current. if [ "$LOCAL_VERSION" = "$LATEST_VERSION" ]; then echo "UP_TO_DATE" > "$CACHE_FILE" 2>/dev/null echo "UP_TO_DATE" exit 0 fi -# Newer version available — attempt git auto-update. -# Shallow-fetch only the target tag (not all tags) for speed. -if [ -d "$PLUGIN_ROOT/.git" ]; then - if git -C "$PLUGIN_ROOT" fetch --quiet --depth=1 origin \ - "+refs/tags/$LATEST_TAG:refs/tags/$LATEST_TAG" 2>/dev/null \ - && git -C "$PLUGIN_ROOT" checkout --quiet "$LATEST_TAG" 2>/dev/null; then - # After a successful checkout, subsequent checks are UP_TO_DATE. - echo "UP_TO_DATE" > "$CACHE_FILE" 2>/dev/null - echo "UPDATED: v$LATEST_VERSION" - exit 0 - fi -fi - -MSG="UPDATE_FAILED: Run \`/plugin update agentkey\` to update to v$LATEST_VERSION" +# Newer version available — notify only. Never modifies the install. +MSG="UPGRADE_AVAILABLE $LOCAL_VERSION $LATEST_VERSION" echo "$MSG" > "$CACHE_FILE" 2>/dev/null echo "$MSG" From e84d9d578476b3e753d2da0f042b16829be4e8f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E7=99=BD?= <31078449+Nowhitestar@users.noreply.github.com> Date: Thu, 30 Apr 2026 19:49:01 +0800 Subject: [PATCH 3/3] feat(security): interactive upgrade flow with snooze + auto-upgrade opt-in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build the second tier of gstack's update UX on top of the notify-only check-update.sh. The script remains scanner-clean (no git, no execve); the agent provides the interaction. Persistent state under ~/.config/agentkey/: - auto-upgrade — touch file: skip the prompt and apply updates silently - update-snoozed — " " with 24h/48h/7d backoff - update-disabled — touch file: silence update checks entirely check-update.sh changes: - Exit silently if update-disabled exists - Read update-snoozed; suppress UPGRADE_AVAILABLE while within the current level's window. New remote version invalidates the snooze. - Cache layer unchanged; snooze gates output, not caching. SKILL.md Step 0 changes: - On UPGRADE_AVAILABLE, branch to a new "Upgrade flow" subsection. - Step A: skip prompt if AGENTKEY_AUTO_UPGRADE=1 or ~/.config/agentkey/auto-upgrade exists; auto-apply via Step C. - Step B: AskUserQuestion with Yes / Always / Not now / Never. Each option has its own side effect (snooze write, disable touch, auto- upgrade touch) before continuing. - Step C: invoke `npx skills update chainbase-labs/agentkey`. SECURITY.md additions: - Document the three new config files and which side reads/writes each - Update the scanner false-positive note: check-update.sh remains pure-notify; the upgrade execution lives in the agent's interactive layer, gated by explicit user consent or a previously persisted opt-in flag. Co-Authored-By: Claude Opus 4.7 (1M context) --- SECURITY.md | 9 +++- skills/agentkey/SKILL.md | 51 +++++++++++++++++++++- skills/agentkey/scripts/check-update.sh | 57 +++++++++++++++++++++---- 3 files changed, 104 insertions(+), 13 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 6fd8558..a40a1e0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -32,7 +32,9 @@ We follow coordinated disclosure. Once a fix is available, we publish a security The skill ships two helper scripts that the agent invokes: -- **`skills/agentkey/scripts/check-update.sh`** — **notify-only**. At most every 60 minutes (12 hours once an upgrade is known), it calls `https://api.github.com/repos/chainbase-labs/agentkey/releases/latest`, compares the tag against the local `version.txt`, and prints `UPGRADE_AVAILABLE ` if they differ. It never runs `git fetch`, `git checkout`, or any other write operation. Updates are entirely user-driven (`npx skills update chainbase-labs/agentkey`). +- **`skills/agentkey/scripts/check-update.sh`** — **notify-only**. At most every 60 minutes (12 hours once an upgrade is known), it calls `https://api.github.com/repos/chainbase-labs/agentkey/releases/latest`, compares the tag against the local `version.txt`, and prints `UPGRADE_AVAILABLE ` if they differ. The script also honors a snooze file (`~/.config/agentkey/update-snoozed`, escalating 24h/48h/7d backoff) and a disable file (`~/.config/agentkey/update-disabled`); both are read-only from this script's perspective. The script never runs `git`, never writes to anything except its TMPDIR cache, and never executes downloaded code. + + When the agent sees `UPGRADE_AVAILABLE` it surfaces an `AskUserQuestion` prompt (Yes / Always / Not now / Never). The actual update — `npx skills update chainbase-labs/agentkey` — runs only after the user picks "Yes" or "Always", or if the user has previously opted into auto-upgrade via `AGENTKEY_AUTO_UPGRADE=1` or `~/.config/agentkey/auto-upgrade`. The agent invokes that command via its own Bash tool, not via this script. - **`skills/agentkey/scripts/check-mcp.sh`** — reads `~/.claude.json` and `~/.env.local` to verify the AgentKey MCP server is registered and the API key is present. **Read-only**; no network egress; output is a single status code. ### Files the skill reads or writes @@ -42,6 +44,9 @@ The skill ships two helper scripts that the agent invokes: | `~/.claude.json` | read | Detect MCP registration; read `AGENTKEY_API_KEY` env value | | `~/.env.local` | read | Fallback location for `AGENTKEY_API_KEY` | | `${TMPDIR}/agentkey-update-check` | read/write | Cache for the update check | +| `~/.config/agentkey/auto-upgrade` | written by the agent on user's "Always keep me up to date" choice; read by Step 0 to skip the prompt | Persistent auto-upgrade opt-in | +| `~/.config/agentkey/update-snoozed` | written by the agent on user's "Not now" choice; read by `check-update.sh` to suppress reminders | Snooze state (` `) | +| `~/.config/agentkey/update-disabled` | written by the agent on user's "Never ask again" choice; read by `check-update.sh` to exit silently | Permanent disable for update checks | | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) / `%APPDATA%/Claude/...` (Windows) | written by the separate `npx -y @agentkey/mcp --auth-login` command, **not** by the skill | MCP registration | | `~/.cursor/mcp.json` | written by `--auth-login`, **not** by the skill | MCP registration | @@ -68,7 +73,7 @@ The skill ships two helper scripts that the agent invokes: Automated scanners (VirusTotal, ClawScan) may flag this skill as `Suspicious` due to two intentional patterns. We document them here so reviewers can verify intent: -1. **`check-update.sh` contacts GitHub.** Pattern may match "remote-controlled binary update" heuristics. **Why this is intentional:** the script is notify-only — it issues a single `GET https://api.github.com/repos/chainbase-labs/agentkey/releases/latest`, compares the tag against `version.txt`, prints a one-line status (`UP_TO_DATE` or `UPGRADE_AVAILABLE `), and exits. It never writes anywhere except the cache file at `${TMPDIR}/agentkey-update-check`, never invokes `git`, and never executes downloaded code. Applying the update is the user's manual `npx skills update chainbase-labs/agentkey`. +1. **`check-update.sh` contacts GitHub.** Pattern may match "remote-controlled binary update" heuristics. **Why this is intentional:** the script is notify-only — it issues a single `GET https://api.github.com/repos/chainbase-labs/agentkey/releases/latest`, compares the tag against `version.txt`, prints a one-line status, and exits. It never writes anywhere except the cache file at `${TMPDIR}/agentkey-update-check`, never invokes `git`, and never executes downloaded code. Update execution lives entirely in the agent's interactive layer (`AskUserQuestion` → `npx skills update`), gated by explicit user consent or a previously persisted opt-in flag. 2. **`check-mcp.sh` reads `*API_KEY*` env values.** Pattern matches "credential harvesting" heuristics. **Why this is intentional:** the read is local-only, never transmitted, and exists purely to confirm `AGENTKEY_API_KEY` is configured before the agent attempts an MCP call. The script's only output is a one-word status code (`MCP_OK` / `MCP_NO_KEY` / `MCP_NOT_CONFIGURED`); the key value itself is discarded. If you operate a scanner and need additional context to triage, please email `support@chainbase.com`. diff --git a/skills/agentkey/SKILL.md b/skills/agentkey/SKILL.md index 7bbd148..cded723 100644 --- a/skills/agentkey/SKILL.md +++ b/skills/agentkey/SKILL.md @@ -14,11 +14,58 @@ version: 1.0.0 ```bash bash "${CLAUDE_PLUGIN_ROOT:-$HOME/.claude}/skills/agentkey/scripts/check-update.sh" 2>/dev/null ``` - - `UPGRADE_AVAILABLE ` → Tell the user once: "AgentKey v\ is available (currently on v\). Run \`npx skills update chainbase-labs/agentkey\` to update." - - `UP_TO_DATE` or empty → continue silently. + - `UP_TO_DATE` or empty → continue silently to step 2. + - `UPGRADE_AVAILABLE ` → run the **Upgrade flow** below, then continue to step 2. 2. Confirm the 4 MCP tools — `list_tools`, `find_tools`, `describe_tool`, `execute_tool` — are visible in the current toolset. If **any** are missing → **Setup** (regardless of what the user asked). Do not attempt Query without all 4. +### Upgrade flow + +Triggered when `check-update.sh` outputs `UPGRADE_AVAILABLE `. Substitute `` and `` with the actual versions parsed from that line. + +**Step A — Check for auto-upgrade opt-in.** Run: +```bash +if [ "${AGENTKEY_AUTO_UPGRADE:-0}" = "1" ] || [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey/auto-upgrade" ]; then echo AUTO=1; fi +``` +If the output is `AUTO=1`: tell the user once "Auto-upgrading AgentKey v\ → v\…", run **Step C**, then continue to step 2. **Do not** show the AskUserQuestion prompt. + +**Step B — Otherwise, prompt the user with AskUserQuestion:** +- Question: `AgentKey v is available (currently on v). Upgrade now?` +- Options: + - **`Yes, upgrade now`** → run **Step C**. + - **`Always keep me up to date`** → run: + ```bash + mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey" && touch "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey/auto-upgrade" + ``` + Tell the user "Auto-upgrade enabled — future AgentKey updates install automatically. Remove `~/.config/agentkey/auto-upgrade` to undo." Then run **Step C**. + - **`Not now`** → run: + ```bash + _CFG="${XDG_CONFIG_HOME:-$HOME/.config}/agentkey" + _SNOOZE="$_CFG/update-snoozed" + _NEW="" + _LEVEL=0 + if [ -f "$_SNOOZE" ]; then + _SVER=$(awk '{print $1}' "$_SNOOZE" 2>/dev/null) + [ "$_SVER" = "$_NEW" ] && _LEVEL=$(awk '{print $2}' "$_SNOOZE" 2>/dev/null) + case "$_LEVEL" in *[!0-9]*) _LEVEL=0 ;; esac + fi + _LEVEL=$((_LEVEL + 1)); [ "$_LEVEL" -gt 3 ] && _LEVEL=3 + mkdir -p "$_CFG" && echo "$_NEW $_LEVEL $(date +%s)" > "$_SNOOZE" + echo "SNOOZED_LEVEL=$_LEVEL" + ``` + Translate the level into a duration for the user — `SNOOZED_LEVEL=1` → "Next reminder in 24h", `2` → "in 48h", `3` → "in 1 week". Continue to step 2 — **do not** upgrade. + - **`Never ask again`** → run: + ```bash + mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey" && touch "${XDG_CONFIG_HOME:-$HOME/.config}/agentkey/update-disabled" + ``` + Tell the user "Update checks disabled. Remove `~/.config/agentkey/update-disabled` to re-enable." Continue to step 2 — **do not** upgrade. + +**Step C — Run the upgrade.** Invoke: +```bash +npx skills update chainbase-labs/agentkey +``` +On success: tell the user "✓ AgentKey updated to v\." On failure: show the failure verbatim and tell the user "Run `npx skills update chainbase-labs/agentkey` manually to retry." Either way, continue to step 2. + Then route by intent: - "setup"/"install"/"api key"/"reinstall" → **Setup** - "status"/"diagnose" → **Status** diff --git a/skills/agentkey/scripts/check-update.sh b/skills/agentkey/scripts/check-update.sh index 1b36351..cbac2d1 100755 --- a/skills/agentkey/scripts/check-update.sh +++ b/skills/agentkey/scripts/check-update.sh @@ -1,15 +1,18 @@ #!/bin/bash # AgentKey — Notify when a newer release is available on GitHub. # Notify-only: this script never modifies the install. It tells the agent -# there's a new version; the user runs the update manually. +# there's a new version; the agent surfaces a prompt and (with the user's +# consent) invokes the upgrade. # -# Result cached in TMPDIR to keep repeat skill invocations fast. +# Result cached in TMPDIR for fast repeat invocations. Persistent state +# (snooze, disable, auto-upgrade flag) lives under ~/.config/agentkey/. # # Outputs a single line, or nothing: # UP_TO_DATE — local matches latest release # UPGRADE_AVAILABLE — local differs from latest release -# (empty / silent) — no version file, network down, or -# unexpected response (we retry next time) +# AND not currently snoozed/disabled +# (empty / silent) — disabled, snoozed, no version file, +# network down, or unexpected response REPO="chainbase-labs/agentkey" CACHE_TTL_UP_TO_DATE=3600 # 60 min — detect new releases quickly @@ -19,12 +22,45 @@ CURL_TIMEOUT=3 PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." 2>/dev/null && pwd)}" VERSION_FILE="$PLUGIN_ROOT/version.txt" CACHE_FILE="${TMPDIR:-/tmp}/agentkey-update-check" +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/agentkey" +DISABLED_FILE="$CONFIG_DIR/update-disabled" +SNOOZE_FILE="$CONFIG_DIR/update-snoozed" + +# Disabled by user ("Never ask again") — exit silently. +if [ -f "$DISABLED_FILE" ]; then + exit 0 +fi LOCAL_VERSION=$(tr -d '[:space:]' < "$VERSION_FILE" 2>/dev/null) if [ -z "$LOCAL_VERSION" ]; then exit 0 fi +# check_snooze → returns 0 (snoozed) or 1 (not snoozed). +# Snooze file format: " " where level 1=24h, 2=48h, 3+=7d. +# A new remote version invalidates the snooze. +check_snooze() { + local remote_ver="$1" + [ -f "$SNOOZE_FILE" ] || return 1 + local sver slevel sepoch + sver=$(awk '{print $1}' "$SNOOZE_FILE" 2>/dev/null) + slevel=$(awk '{print $2}' "$SNOOZE_FILE" 2>/dev/null) + sepoch=$(awk '{print $3}' "$SNOOZE_FILE" 2>/dev/null) + [ -n "$sver" ] && [ -n "$slevel" ] && [ -n "$sepoch" ] || return 1 + case "$slevel" in *[!0-9]*) return 1 ;; esac + case "$sepoch" in *[!0-9]*) return 1 ;; esac + [ "$sver" = "$remote_ver" ] || return 1 + local duration + case "$slevel" in + 1) duration=86400 ;; + 2) duration=172800 ;; + *) duration=604800 ;; + esac + local now + now=$(date +%s) + [ $((sepoch + duration)) -gt "$now" ] +} + # Fast path: recent cache hit — avoids the GitHub API round-trip (~1.5s). if [ -f "$CACHE_FILE" ]; then MTIME=$(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) @@ -36,8 +72,6 @@ if [ -f "$CACHE_FILE" ]; then *) TTL=0 ;; esac if [ "$AGE" -ge 0 ] && [ "$AGE" -lt "$TTL" ]; then - # Cached UPGRADE_AVAILABLE may be stale w.r.t. the local version (user - # might have updated since we cached). Re-emit only if old still matches. case "$CACHED" in "UP_TO_DATE") echo "UP_TO_DATE" @@ -46,6 +80,10 @@ if [ -f "$CACHE_FILE" ]; then "UPGRADE_AVAILABLE "*) CACHED_OLD=$(echo "$CACHED" | awk '{print $2}') if [ "$CACHED_OLD" = "$LOCAL_VERSION" ]; then + CACHED_NEW=$(echo "$CACHED" | awk '{print $3}') + if check_snooze "$CACHED_NEW"; then + exit 0 + fi echo "$CACHED" exit 0 fi @@ -65,8 +103,6 @@ LATEST_VERSION=${LATEST_TAG#[vV]} # Validate response looks like a version number — rejects HTML error pages, # rate-limit JSON, and other surprises that slipped past curl -f. if ! echo "$LATEST_VERSION" | grep -qE '^[0-9]+\.[0-9.]+$'; then - # Network failure or unexpected response — stay silent and don't cache, - # so the next call retries. exit 0 fi @@ -76,7 +112,10 @@ if [ "$LOCAL_VERSION" = "$LATEST_VERSION" ]; then exit 0 fi -# Newer version available — notify only. Never modifies the install. +# Newer version available — cache the result, then suppress output if snoozed. MSG="UPGRADE_AVAILABLE $LOCAL_VERSION $LATEST_VERSION" echo "$MSG" > "$CACHE_FILE" 2>/dev/null +if check_snooze "$LATEST_VERSION"; then + exit 0 +fi echo "$MSG"