Skip to content

fix(cli): harden skillx add against large or hostile sources and credential leaks - #167

Merged
mrgoonie merged 4 commits into
feat/skillx-add-commandfrom
claude/admiring-ritchie-dnfgue
Oct 29, 2026
Merged

mrgoonie merged 4 commits into
feat/skillx-add-commandfrom
claude/admiring-ritchie-dnfgue

Conversation

@mrgoonie

Copy link
Copy Markdown
Contributor

Closes #163. The skillx add code exists only on the branch of #120, which is still a draft, so this PR targets feat/skillx-add-command and lands with #120.

Changes

  1. Bounded reads.
    • The clone uses --filter=blob:none, and the listing comes from tree objects. Only the blobs that discovery and the chosen skills need are fetched, in one batch each.
    • Sizes are read from the local object headers before any content. Content is then read in batches of at most 64 MiB, which replaces the old single 1 GiB maxBuffer.
    • A search path with more than 1,000 SKILL.md files is a usage_error. A SKILL.md over 1 MiB is skipped without its content being read, and front matter is parsed in chunks.
    • Example: github:torvalds/linux/Documentation now lists in under 3 s.
    • Limit: a blob that is fetched still downloads whole, because git cannot report a size first. Download time is bounded only by the 120 s timeout on each git call. The docs say this.
  2. Credentials never on disk.
    • A URL token is removed from the URL that git sees. It is passed instead as an Authorization header through GIT_CONFIG_* environment entries, so it never appears in argv, .git/config or a credential helper.
    • Redirects are turned off whenever a header is passed. Otherwise git sends the header to whatever host a redirect points to; the isolated review reproduced this.
    • Temporary clones are removed on exit, and on SIGINT, SIGTERM or SIGHUP, including while git is running. The process exits with 130, 143 or 129.
  3. Redaction.
    • Redaction now masks up to the last @ of a URL.
    • A URL whose password contains / is refused without being echoed. So is any URL with an @ that does not parse, or with an @ in its path, and any refused input that contains @.
  4. /tree/<ref>/<path> with a / in the branch name. The ref is resolved against the remote with an exact-pattern git ls-remote, and the longest matching branch or tag wins.
  5. Design gaps accepted for the first version. Commit-SHA refs are now documented as unsupported. The lock, update and scope gaps are tracked in feat(cli): close the accepted design gaps of skillx add (lock, update, commit refs) #166.

Validation

  • pnpm typecheck passes for every workspace package.
  • pnpm exec vitest run packages/cli: 24 files, 276 tests pass. The new tests cover:
    • a partial clone that never fetches a blob outside the skills;
    • skipping an over-size SKILL.md;
    • the 1,000-candidate cap;
    • resolving a branch name that contains /;
    • redaction and refusals;
    • removing the scratch directory and its handlers;
    • a redirect regression test that puts a real git http-backend on another host. This test fails, with the header leaked, when http.followRedirects=false is removed.
  • Manual runs against the built CLI and live GitHub:
    • list, and install of pdf;
    • a /tree/cj/claude-api-resync/skills/pdf URL resolved to ref cj/claude-api-resync;
    • Ctrl+C at the confirm prompt: exit 130, temporary directory removed;
    • SIGINT to the process group while git clone was running: exit 130, no envelope, temporary directory removed;
    • a password containing /: refused with exit 2, not echoed;
    • a git-level check that the env-scoped header reaches only its own origin.
  • An isolated reviewer subagent checked the change. Its findings (the redirect leak, the signal during git, overstated claims, the promisor check, echoes of inputs with @) are fixed in 54fc381.

Generated by Claude Code

…ential leaks

- Clone with --filter=blob:none and fetch only the blobs discovery and the
  chosen skills need; read sizes from object headers before any content and
  read blobs in bounded batches instead of one 1 GiB buffer.
- Refuse more than 1,000 SKILL.md candidates and skip a SKILL.md over 1 MiB
  unread; discovery parses front matter in chunks.
- Keep URL credentials out of git's argv and .git/config: pass them as an
  origin-scoped http.extraHeader through GIT_CONFIG_* environment entries.
- Remove the temporary clone on exit, SIGINT, SIGTERM and SIGHUP.
- Redact userinfo up to the last @ of a URL and refuse, without echoing, a
  URL whose password holds a slash.
- Resolve /tree/<ref>/<path> URLs against the remote's branches and tags so
  a branch name containing / is split correctly.

Refs #163

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FL32runPrLpWgWsxUBpdCD
…g git

- Turn http.followRedirects off whenever a URL credential is passed as a
  header: git matches http.<url>.extraHeader once and then sends it to the
  host a redirect leads to. A regression test runs a redirecting server in
  front of a real git http-backend on another host.
- A git call killed by SIGINT, SIGTERM or SIGHUP removes every temporary
  clone and exits with 130, 143 or 129 instead of reporting a network error.
- Refusals echo the input only when it holds no @.
- Drop the promisor check (git sets it whenever --filter is passed) and say
  plainly that fetched blobs download whole, bounded by the per-call timeout.

Refs #163

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FL32runPrLpWgWsxUBpdCD
A user's URL-scoped http.<url>.followRedirects=true outranked the unscoped
http.followRedirects=false, so a token header could still follow a redirect.
The guard is now keyed to the whole credential-free URL. A token URL that
redirects fails as usage_error with a hint to use the final URL instead of a
bare network_error.

Refs #163

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FL32runPrLpWgWsxUBpdCD
…aude/admiring-ritchie-dnfgue

# Conflicts:
#	packages/cli/src/commands/add.ts
#	packages/cli/src/lib/add/git-source.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants