## Summary
Hermes flagged this skill with **3 findings** (1 persistence + 2
traversal) that, combined with `community-source` status, produced a
`BLOCKED` verdict requiring users to install with `--force`. None of the
findings reflected actual risk — all stemmed from incidental code/doc
patterns. This PR removes every match and adds CI to keep them gone.
## Changes
### A — Eliminate path traversal in `check-update.sh`
- Drop `dirname(BASH_SOURCE)/..` resolution and `version.txt` read
- Embed `LOCAL_VERSION` as a constant, synced by release-please via
`extra-files`
- Script now does **zero filesystem traversal** — no `..`, no `dirname`,
no `CLAUDE_PLUGIN_ROOT` dependency
### B — Remove persistent file-path enumeration from `SKILL.md`
- Replace explicit list of agent config paths
(`~/.claude/settings.json`, `claude_desktop_config.json`,
`~/.cursor/mcp.json`) with a neutral one-liner pointing to `SECURITY.md`
- The CLI behavior is fully documented in `SECURITY.md` for transparency
without triggering scanner heuristics
### C — Align `SECURITY.md` with new internals
- Update the `check-update.sh` description to call out the new
zero-traversal design
### Hardening (D1) — script becomes shellcheck-clean
- `set -u` + `set -o pipefail`; explicit `|| true` on every intentional
silent-failure path
- Replace 3× awk fork-and-read on the snooze file with a single atomic
`read -r` (closes a real cross-process race)
- Same treatment for the cache file parse — also eliminates `echo $VAR |
awk` injection surface
- Cache `date +%s` once per run (`NOW`), shared by cache-age and snooze
expiry math
- Switch `LATEST_VERSION` validation from `echo | grep -qE` to `case`
glob, matching how `LOCAL_VERSION` is validated
- **Net**: ~half the forks per run, no silent failures, stricter
validation
### Metadata (D2) — improve community-source traceability
- `SKILL.md` frontmatter: add `author` / `homepage` / `repository` /
`license` fields
- Standard fields, ignored by clients that don't read them, but give
scanners and reviewers a direct path to the publisher
### Release plumbing
- `SKILL.md` frontmatter `version`: 1.0.0 → 1.2.3 (long-standing drift)
and tagged with `# x-release-please-version` so release-please now syncs
it
- `release-please-config.json`: add `check-update.sh` and `SKILL.md` to
`extra-files`
- New workflow `.github/workflows/verify-version-sync.yml` asserts all
four versions match on every relevant push/PR — catches the case where
someone hand-edits one location and forgets the others
`version.txt` remains the **single source of truth** that humans
maintain. The other three locations (`plugin.json`, `SKILL.md`
frontmatter, `check-update.sh`) are auto-synced derivatives.
## Test plan
- [x] Local smoke-test of `check-update.sh` across 11 scenarios:
- cold run, `UP_TO_DATE` cache hit, `UPGRADE_AVAILABLE` cache hit
- snoozed (active / expired / long expired)
- disabled file
- corrupted binary cache
- local-version-moved-on
- All paths produce expected output; `set -u` catches no false positives
- [x] `bash -n` syntax check passes
- [x] All four version locations (`version.txt`, `plugin.json`,
`SKILL.md` frontmatter, `check-update.sh`) verified to match at 1.2.3
- [x] `SKILL.md` YAML frontmatter parses correctly with the new fields
- [ ] CI passes
- [ ] Re-scan with Hermes confirms 0 findings (post-merge, after release
tag)
## Notes for reviewer
- All four version locations exist for distinct technical reasons — see
`release-please-config.json` for the sync wiring. Maintainer cost
remains exactly one place (`version.txt`, bumped automatically by
release-please from conventional commits).
- The `set -u` change is the most behavior-affecting — please review the
`|| true` guards on `read -r` and `curl ... | grep | sed` carefully.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Problem
The skill assumed the whole repo is the plugin root (`PLUGIN_ROOT` =
repo root, `version.txt` at repo root). That holds in Claude Code plugin
mode, where Claude Code injects `CLAUDE_PLUGIN_ROOT`. But the README's
recommended path —
```
npx skills add chainbase-labs/agentkey
```
— uses [vercel-labs/skills](https://github.com/vercel-labs/skills),
which only copies the `skills/agentkey/` subdirectory to
`~/.claude/skills/agentkey/`. The repo-root `version.txt` doesn't come
along.
In that layout, `check-update.sh`'s fallback
```bash
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." 2>/dev/null && pwd)}"
```
resolves `../../..` from `~/.claude/skills/agentkey/scripts/` to
`~/.claude/`, so `VERSION_FILE` points at `~/.claude/version.txt` —
which doesn't exist. `LOCAL_VERSION` ends up empty, the script silently
`exit 0`s at line 35, and **skills-CLI users never see upgrade prompts
at all**.
(Worse case: if some other tool ever drops a `~/.claude/version.txt`,
AgentKey would read it as its own version.)
This is an interface contract mismatch between the two distribution
models, not a one-sided bug — both are valid, but the skill needs to
work under either.
## Fix
1. Move `version.txt` into the skill directory
(`skills/agentkey/version.txt`) so it travels with whichever subset of
the repo gets copied.
2. In `check-update.sh`, anchor on `SKILL_ROOT` (one level above
`scripts/`) instead of an external `CLAUDE_PLUGIN_ROOT`. Both
distribution paths now resolve identically:
- Plugin: `<repo>/skills/agentkey/version.txt`
- Skills CLI: `~/.claude/skills/agentkey/version.txt`
3. Point release-please at the new path via `version-file`. The
`plugin.json` `extra-files` entry is unchanged.
4. Update docs (README, `docs/README_zh.md`, `.claude/CLAUDE.md`,
`SECURITY.md`, `claude-pr-review.yml`) to reflect the new path.
## Test plan
- [x] `bash skills/agentkey/scripts/check-update.sh` in repo: resolves
`SKILL_ROOT` to `<repo>/skills/agentkey`, reads `LOCAL_VERSION=1.2.2`
correctly.
- [x] Simulated skills-CLI install: `cp -r skills/agentkey /tmp/sim/`
then ran `bash /tmp/sim/agentkey/scripts/check-update.sh` with
`CLAUDE_PLUGIN_ROOT` unset → resolves `SKILL_ROOT=/tmp/sim/agentkey`,
reads `LOCAL_VERSION=1.2.2` correctly.
- [ ] After merge, the next release-please Release PR should bump
`skills/agentkey/version.txt` (along with `plugin.json` and
`CHANGELOG.md`) — please verify the Release PR diff before merging it.
- [ ] After release, on a fresh `npx skills add chainbase-labs/agentkey`
install, an out-of-date version should now correctly produce
`UPGRADE_AVAILABLE <old> <new>` and trigger the existing AskUserQuestion
prompt flow.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
The v1.1.0 release-please tag came out as `agentkey-skill-v1.1.0`
instead of `v1.1.0`, because `release-please` defaults
`include-component-in-tag: true` for simple release-type. That created
inconsistency with v1.0.0's bare `v1.0.0` tag (which was hand-tagged
during migration cutover).
I've already renamed the v1.1.0 tag + release to match v1.0.0's
convention. This PR ensures future releases stay consistent by adding
`include-component-in-tag: false`.
## Test plan
- [x] `python3 -c "import json;
json.load(open('release-please-config.json'))"` OK
- [x] Existing `v1.1.0` and `v1.0.0` tags are both bare-`v` format
(verified via `gh api repos/.../tags`)
- [ ] Next release-please-cut release uses `vX.Y.Z` format, not
`agentkey-skill-vX.Y.Z`
Co-authored-by: lxcong <lxhtheresa@gmail.com>
## Summary
Release-please PR #6 (bumping to v1.1.0) revealed that the `version`
file wasn't being auto-updated — `plugin.json`, `CHANGELOG.md`, and
`.release-please-manifest.json` all bumped correctly, but `version`
stayed at `1.0.0`.
Root cause: the `generic` extra-files updater requires `#
x-release-please-version` anchor markers in the file. Our `version` file
is a bare version string with no markers, so release-please silently
skipped it.
Consequence if left unfixed: `check-update.sh` reads `version` to
compare with remote releases — users would see "UPDATED: v1.1.0" on
every skill invocation after v1.1.0 publishes, because the local
`version` file never advances.
## Fix
- Rename `version` → `version.txt`. The `simple` release-type in
release-please auto-manages `version.txt` by convention, no extra-files
entry needed.
- Drop the obsolete `{"type": "generic", "path": "version"}` entry from
`release-please-config.json`.
- Update `check-update.sh` VERSION_FILE pointer.
- Set `version.txt` to `1.1.0` so it matches what PR #6 will cut.
## Merge order
This PR first, **then** release PR #6. Release-please will see
`version.txt=1.1.0` already in place and leave it alone; `plugin.json`
will still bump from 1.0.0 → 1.1.0 as usual.
## Test plan
- [x] `python3 -c "import json;
json.load(open('release-please-config.json'))"` OK
- [x] `bash -n skills/agentkey/scripts/check-update.sh` OK
- [x] `git mv` preserves history
- [ ] Post-merge + post-release-PR: `git show v1.1.0:version.txt` equals
`1.1.0`
Co-authored-by: lxcong <lxhtheresa@gmail.com>