npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vinctor-claude-code-hook

v0.5.0

Published

Claude Code PreToolUse hook routing tool calls through Vinctor runtime authorization.

Readme

Vinctor Claude Code Hook

Status: Boundary Preview

CI

A Claude Code PreToolUse hook that routes tool calls through runtime authorization before execution.

Set up: To wire Claude Code to Vinctor end to end, follow the Set up Claude Code guide.

Why This Exists

AI agents are no longer just generating text. Across agent systems, they are increasingly executing tools — running shell commands, editing files, querying databases, calling third-party APIs, triggering deployments, and reaching into systems that hold sensitive data. The surface area of "what an agent can touch" is expanding faster than the surface area of "what an agent has been explicitly authorized to do."

Static credentials and prompt-level safety guidelines do not cover this gap. An agent's allowed scope depends on the task, the operator, the target resource, and the moment. It changes during a session, can be revoked mid-run, and must be evaluated against operator-defined policy — not against the agent's own reasoning.

A runtime authorization boundary — one that decides, per tool call, whether a specific agent may perform a specific action on a specific resource right now — narrows that gap by making selected tool calls subject to an authorization decision before execution. This repository implements that boundary for Claude Code.

What This Repository Contains

This repo holds the runtime boundary that selected Claude Code tool calls pass through before they execute. It is one piece of the broader Vinctor runtime authorization infrastructure. The authorization service itself — grant model, policy evaluation, audit log, revocation — lives separately.

The hook does two things, in this order:

  1. Asks the authorization service for a decision. When Claude Code is about to invoke a configured tool (e.g. a Bash command), Claude Code invokes the configured hook for the PreToolUse event, the hook maps the tool input to an (action, resource) pair, and then calls the authorization service for a permit-or-deny decision dynamically.

  2. Acts on the decision.

    • Service permit → the hook returns allow and Claude Code proceeds.
    • Service deny, unreachable, timeout, malformed input, missing required env → the hook fails closed and returns deny with a fixed-template reason.
    • Tool call cannot be classified by config or built-in defaults → the hook returns ask, deferring the decision to the user via Claude Code's native permission prompt.

    The hook does not include the underlying grant reference, audit event id, or matched scope in any reason string.

Claude Code (PreToolUse event)
        │
        ▼
   This hook ── maps → asks the authorization service
        │                          │
        │                  permit / deny / fail-closed
        ▼                          │
   allow / deny  ◀──────────────────
        │
        ▼
Claude Code (executes tool, or blocks)

Tool Coverage

Built-in defaults cover selected high-impact command families and file/network actions. They are not a complete command catalog. A command the built-ins don't recognize returns ask unless the operator maps it in config. For example, npm test / npm publish / git commit / git push / docker push / curl … | sh are mapped (per the Vinctor Action Taxonomy canon), but e.g. git checkout, npm view, docker ps, and unknown first tokens are not — they return ask. Use vinctor-claude-hook explain <event> to see exactly how any single call maps, or vinctor-claude-hook list-defaults to print the full built-in catalog (pattern rules, Bash classifier families, and the recognized MCP tools per server).

The Runtime column is measured against Claude Code 2.1.169 — whether that build actually sends a PreToolUse event to the hook for the tool (see the coverage matrix). Cells not exercised by that run are marked unmeasured; do not read them as covered.

| Tool | Runtime (2.1.169) | Coverage | Default behavior | |---|---|---|---| | Bash | measured: observed | high-impact subset | classifier-aware for a selected subset of git / npm·pnpm·yarn·npx / docker / gh / rm·rmdir, plus pipe-to-shell (… | sh) and pattern defaults (secrets, protected files, release, infra, exfiltration). Resources follow the Vinctor Action Taxonomy canon (shell/git, pkg/npm/<name>, container/<registry>/<image>, github/<owner>/<repo>/<kind>, fs/<path>). Everything else → ask. | | Read / Write / Edit | measured: observed | secret + protected paths | pattern defaults (secrets, protected files). Reading and writing .env, SSH keys, and cloud-credential files is in-boundary (read/write:secret/<kind>). Ordinary files → ask. | | MultiEdit | measured: not present in 2.1.169 | n/a | the matcher token is harmless, but this build emits no MultiEdit events (the model falls back to Edit). | | WebFetch | measured: observed | full | every fetch is mapped to send:net/internal/<host> or send:net/external/<host>. Operator config can override per-host. | | WebSearch | measured: observed | matcher only | no built-in mapping; operator config required, otherwise ask | | mcp__<server>__<tool> | unmeasured (no MCP server in probe) | built-in classifiers for filesystem, github, slack; other servers: matcher only | These three servers' common tools are mapped out of the box. Tools they don't recognize, and all other MCP servers, require operator config — otherwise ask. |

What counts as net/internal. WebFetch host classification treats these as internal: localhost, *.local, *.internal (the ICANN reserved private-use TLD), private/loopback/link-local IPv4 (10., 172.16–31., 192.168., 127., 169.254.), and IPv6 loopback / ULA (fc00::/7) / link-local (fe80::/10). Everything else is net/external. Operator config can override per-host.

Bash sees only the first token/segment. Prefix wrappers (sudo, env, VAR=…, bash -c, xargs) drop a command to ask; chaining with &&/| classifies only the first segment and leaves the appended command unseen — e.g. npm publish && curl … is enforced only as deploy:pkg/npm/<name>, and cat .env | curl … only as read:secret/env. (One deliberate exception: piping into a shellcurl … | sh — is classified as its own operation, execute:shell/<first-token>, per the taxonomy canon.) This applies to operator config rules too, not just built-in defaults — a config rule is matched against the same whole command, so an exact rule dies on any wrapper/chaining and a prefix/glob rule maps only its matched segment. Do not treat a config rule as a command-level security boundary; it authorizes the classified scope, not the literal command line. Measured limitation, detailed in the coverage matrix §3.

v0.3.0 ships built-in classifiers for the filesystem, github, and slack MCP servers. Coverage tracks each server's documented tool set (for slack, the reference @modelcontextprotocol/server-slack slack_* tools and the Zencoder-fork conversations_* / channels_* tools, matched on those servers' field names). Tool names or input fields outside a server's recognized set fall to ask; add an operator config rule to route them. Built-in MCP coverage is not exhaustive and is not "any tool on these servers."

Surface coverage caveat

Enforcement applies wherever Claude Code runs the agent loop on your machine and loads your local settings.json PreToolUse hook. Per the Claude Code docs, settings.json hooks are shared between the CLI and the VS Code extension, and Remote Control keeps the session running locally — so this hook is in the path on the CLI, the VS Code / JetBrains IDE surfaces, the Desktop app, and Remote Control sessions. Only the CLI is empirically measured (against 2.1.169); the other local surfaces are doc-reasoned and unverified here. Two surfaces are explicitly different:

  • Jupyter cell execution via the IDE MCP (mcp__ide__executeCode) — the PreToolUse hook does fire (the docs list it "as seen by hooks", so this hook can deny a cell), but VS Code adds a separate Quick Pick confirmation that the docs state is "separate from PreToolUse hooks." A hook allow therefore does not by itself run the cell, and the hook neither sees nor controls that Quick Pick. Not verified here.
  • Claude Code on the web (cloud-hosted) — runs in Anthropic-managed cloud, which does not load your local settings.json. This hook is not in the path there; treat web/cloud as not covered.

Details and doc citations are in the coverage matrix §4b Surface coverage.

What This Is Not

Vinctor authorizes configured, mediated tool calls routed through an adapter boundary. Unwrapped tool paths remain outside Vinctor's boundary. Vinctor does not provide OS/process/account isolation, sandboxing, raw tool interception, provider credential control, or rollback of already-started work.

This repository is not an official Claude Code plugin. It is a Claude Code PreToolUse hook boundary for the Vinctor authorization service.

Before You Start

You need:

  • Node.js 20+ and npm — to install (or build) the hook CLI.
  • A running Vinctor authorization service endpoint — the hook only classifies a tool call and asks the service; it does not run the service. (You can still evaluate the hook offline without one — see Getting a grant, and evaluating offline.)
  • A valid agent grant — the agent API key (aak_…) and grant reference (grt_…) issued for your agent by the Vinctor service. Live allow / deny decisions require these.

Install (Boundary Preview)

From npm (recommended):

npm install -g vinctor-claude-code-hook
vinctor-claude-hook --version

Or from source (contributors):

git clone https://github.com/vinctor-ai/vinctor-claude-code-hook.git
cd vinctor-claude-code-hook
npm install
npm run build          # CLI at dist/src/cli.js

Wire the CLI into Claude Code's settings.json hooks.PreToolUse:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Read|Write|Edit|MultiEdit|WebFetch|WebSearch|mcp__.*",
        "hooks": [{ "type": "command", "command": "vinctor-claude-hook" }]
      }
    ]
  }
}

If the harness session doesn't inherit your shell PATH, use the absolute bin path instead (command -v vinctor-claude-hook), or the built <absolute path>/dist/src/cli.js for a source install.

Required env in the Claude Code session:

  • VINCTOR_ENDPOINT — base URL of the Vinctor authorization service
  • VINCTOR_AGENT_KEY — agent API key (aak_…)
  • VINCTOR_GRANT_REF — opaque grant reference (grt_…)

Optional:

  • VINCTOR_BOUNDARY_ID — optional boundary id from the local Vinctor service. When set, the hook sends it as X-Vinctor-Boundary-Id so service audit rows can record which configured runtime boundary made the enforce call.
  • VINCTOR_CLAUDE_CODE_HOOK_CONFIG overrides the default config path .vinctor/claude-code-hook.json.
  • VINCTOR_HOOK_DEBUG=1 makes hook mode write one diagnostic line to stderr per event, naming the resolved config path and whether it was found — handy when a config isn't being picked up. It never touches stdout (the hook decision) and never echoes secret env values. Leave it unset in normal use.

This is a Boundary Preview. Not production-ready. Not an official Claude Code plugin.

Getting a grant, and evaluating offline

Real permit/deny decisions require a running Vinctor authorization service and a grant issued for your agent. The hook itself does not run that service and does not issue grants — it only classifies a tool call and asks the service.

  • VINCTOR_ENDPOINT, VINCTOR_AGENT_KEY (aak_…), VINCTOR_GRANT_REF (grt_…), and optional VINCTOR_BOUNDARY_ID come from the Vinctor authorization service or local evaluation setup, not from this repo.
  • There is no public Vinctor service yet. Design partners receive an endpoint and the aak_… / grt_… values from the Vinctor service / local evaluation setup (maintained separately).
  • Without those env vars you can still evaluate the hook offline, because its decision is observable without ever reaching the service:
    • A mapped call with missing/incomplete auth env returns deny: missing_auth_env — that is the success signal that the hook classified the call and would have asked the service.
    • An unmapped call returns ask — the hook could not classify it and defers to Claude Code's permission flow.
    • A mapped call whose service is unreachable returns deny: service_unavailable — the hook fails closed.

So offline you can fully validate mapping, ask, missing_auth_env, and fail-closed behavior; only the live allow / deny: action_denied outcomes need a running service with a valid grant.

Quickstart (end to end)

# 1. Install
npm install -g vinctor-claude-code-hook

# 2. (optional) Write an operator policy
mkdir -p .vinctor
cat > .vinctor/claude-code-hook.json <<'JSON'
{ "version": 1, "rules": [
  { "tool": "Edit", "matchType": "glob", "pattern": "**/.github/workflows/*.yml",
    "action": "write", "resource": "ci/workflow" }
] }
JSON

# 3. Validate the policy (offline; exit 0 valid, 1 invalid, 2 unreadable)
vinctor-claude-hook validate .vinctor/claude-code-hook.json

# 4. See how a specific call would map (offline; no service call)
printf '%s' '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"docker push docker.io/acme/api:1.4.2"}}' > /tmp/e.json
vinctor-claude-hook explain /tmp/e.json --json   # → mapped deploy:container/docker.io/acme/api

# 5. Run one event through the hook itself (no auth env → fails closed at the boundary)
printf '%s' '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"docker push docker.io/acme/api:1.4.2"}}' \
  | vinctor-claude-hook          # → deny: missing_auth_env  (mapped, would ask the service)

Then wire the CLI into Claude Code's settings.json (above) and, once you have a service endpoint + grant, set the three VINCTOR_* env vars in the session. With those set, step 5's docker push becomes a real allow or deny: action_denied from the service. Expected decisions at a glance: a mapped call in the grant → allow; mapped but not in the grant → deny; unmapped → ask; service down or env missing → deny (fail-closed).

Configuration

Operator policy lives in an optional JSON file at .vinctor/claude-code-hook.json (override the path with VINCTOR_CLAUDE_CODE_HOOK_CONFIG). It adds or overrides mappings from a Claude Code tool call to an (action, resource) pair. The file is optional — without it, the hook uses only its built-in mappings.

{
  "version": 1,
  "rules": [
    { "tool": "WebSearch", "matchType": "prefix", "pattern": "salary",
      "action": "send", "resource": "web/search/sensitive" },
    { "tool": "mcp__filesystem__read_file", "matchType": "glob", "pattern": "**/etc/**",
      "inputField": "path", "action": "read", "resource": "secret/etc" }
  ]
}

Each rule has: tool (a Claude Code tool name or mcp__<server>__<tool>), matchType (exact / prefix / glob), pattern, action (one of read/write/execute/deploy/delete/send), and resource. Operator rules always take precedence over built-in mappings; the most specific matching rule wins. A call that matches nothing falls to ask.

To protect an existing file path, map Read for it — not just Write/Edit. Measured against Claude Code 2.1.169: editing an existing file makes the runtime issue a Read first. If only Write/Edit are mapped (and Read is left to ask), the model stops at the unmapped Read and never reaches the mapped Write, so the deny never fires — the guard then covers only net-new file creation. Add a Read rule for the same path to close this. See the coverage matrix §4a.

Full reference — every field, the per-tool "what pattern matches against" table, glob and specificity semantics, MCP inputField, and worked examples — is in docs/configuration.md.

Inspecting output

The hook writes a single-line JSON decision on stdout (so Claude Code can parse it). To read it by hand, pipe through jq, and use --version / --help to check the binary without sending an event:

node dist/src/cli.js --version
printf '%s' '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"ls"}}' \
  | node dist/src/cli.js | jq

Checking a config or a tool call offline

Two subcommands let an agent or operator check things without a running service:

# Lint a config file (every error at once); exit 0 valid, 1 invalid, 2 unreadable
node dist/src/cli.js validate .vinctor/claude-code-hook.json --json

# Show how an event would map (action, resource, which rule won); no service call
printf '%s' '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"docker push docker.io/acme/api:1.4.2"}}' > /tmp/e.json
node dist/src/cli.js explain /tmp/e.json --json

--json prints a structured object (intended for agents); omit it for human-readable text. explain never calls /v1/enforce.

Self-checking the install with doctor

vinctor-claude-hook doctor          # or: --json

doctor checks everything verifiable from outside a live Claude Code session and prints a // line per check: the resolved hook-command path + version (paste into settings.json), Node ≥ 20, whether a known settings.json (~/.claude/settings.json or ./.claude/settings.json) wires this hook under hooks.PreToolUse (and warns on an over-broad .* matcher), the VINCTOR_* env (presence only — values are never printed), endpoint reachability (best-effort, no credentials sent), and a classifier smoke (docker push docker.io/acme/api:1.4.2 should map to deploy:container/docker.io/acme/api). Exit 0 when nothing is blocking, 1 on a .

It deliberately cannot prove Claude Code is invoking the hook — only running a mapped tool call and watching the PreToolUse hook fire can. doctor prints that caveat every run.

For what each decision and deny code means — and where to look when a permit you expected comes back denied — see docs/troubleshooting.md.

Status

Boundary Preview. Interfaces, configuration shape, and supported tool coverage may change before a stable release. Not production-ready.

Audience

Developers and design partners evaluating runtime authorization boundaries for Claude Code tool execution.

License

MIT