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

tool-guard

v2.0.0

Published

Reliable permission system for Claude Code using PreToolUse hooks

Readme

tool-guard

npm version License: MIT

A PreToolUse hook that actually enforces permissions in Claude Code — typed config, glob patterns, and injection-proof command validation.


Why?

The built-in permissions doesn't do what you think

Claude Code has a permissions setting in settings.json with allow and deny arrays. This is not a security feature. Here's what it actually does:

| Setting | What you might expect | What it actually does | |---------|----------------------|----------------------| | allow: ["Bash(git *)"] | "Only allow git commands" | "Don't ask me again for git commands" (auto-approve prompt) | | deny: ["Read(.env)"] | "Block reading .env files" | Nothing. It's ignored for Read/Write/Edit tools. |

The permissions system is essentially a prompt suppression mechanism, not a security boundary:

  • allow = "Auto-click Yes for me" — saves you from clicking approve
  • deny = Broken for most tools (works partially for Bash only)

Proof it's broken

Multiple GitHub issues confirm this:

  • #6699: "deny permission system is completely non-functional"
  • #6631: "Permission Deny Configuration Not Enforced for Read/Write Tools"
  • #8961: "Claude Code arbitrarily ignoring deny rules"

Example from issue #6631:

// settings.json
{ "permissions": { "deny": ["Read(.env)"] } }
// Result: Claude reads .env anyway
✓ Successfully read .env file content

The solution: PreToolUse hooks

Hooks are actually enforced by Claude Code before any tool execution. tool-guard provides a typed, injection-proof permission system built on hooks.

┌─────────────┐     stdin (JSON)     ┌──────────────┐     stdout (JSON)   ┌─────────────┐
│ Claude Code │ ───────────────────▶ │  tool-guard  │ ──────────────────▶ │ Claude Code │
│             │  { toolName, input } │              │  { allow | deny }   │  (enforced) │
└─────────────┘                      └──────┬───────┘                     └─────────────┘
                                            │
                                            ▼
                                 ┌────────────────────┐
                                 │  guard.config.ts   │
                                 └────────────────────┘

Install

pnpm add -D tool-guard

Add the hook to .claude/settings.local.json:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": ".*",
      "hooks": [{
        "type": "command",
        "command": "pnpm exec tool-guard"
      }]
    }]
  }
}

Unconfigured tools are denied by default.


Configuration

Create .claude/guard.config.ts. The key feature: the same policy objects work in both ToolGuards and extractables, so file restrictions are consistent across Read/Write/Edit tools and Bash commands.

import { type PolicyDefinition } from 'tool-guard/policy'
import { defineGuard } from 'tool-guard/guard'
import { command, spread } from 'tool-guard/command'
import { BashToolGuard } from 'tool-guard/guards/bash'
import { ReadToolGuard } from 'tool-guard/guards/read'
import { WriteToolGuard } from 'tool-guard/guards/write'
import { EditToolGuard } from 'tool-guard/guards/edit'
import { GlobToolGuard } from 'tool-guard/guards/glob'
import { GrepToolGuard } from 'tool-guard/guards/grep'
import { SafeFilePath } from 'tool-guard/extractables/safeFilePath'
import { safeString } from 'tool-guard/extractables/safeString'
import { safeBranch } from 'tool-guard/extractables/safeBranch'
import { safeNumber } from 'tool-guard/extractables/safeNumber'
import { safePackage } from 'tool-guard/extractables/safePackage'

// ── Shared file policies ────────────────────────────────────────────────────
// Define once, reuse across guards AND extractables

const fileAccessPolicies: Array<PolicyDefinition<string>> = [
  { deny: ['.env', '*.env', '.env.*', '**/*.pem', '**/*.key'] },
]

const fileReadPolicies: Array<PolicyDefinition<string>> = [
  { allow: ['*'] },
  ...fileAccessPolicies,
]

const fileWritePolicies: Array<PolicyDefinition<string>> = [
  { allow: ['src/**', 'tests/**', '*.config.ts'] },
  ...fileAccessPolicies,
]

// ── Extractables with shared policies ───────────────────────────────────────
// Same policies, applied inside command templates

const readableFile = SafeFilePath(...fileReadPolicies)
const writableFile = SafeFilePath(...fileWritePolicies)

// ── Config ──────────────────────────────────────────────────────────────────

export default defineGuard({
  // File tools — same policies as the extractables above
  Read: ReadToolGuard(...fileReadPolicies),
  Write: WriteToolGuard(...fileWritePolicies),
  Edit: EditToolGuard(...fileWritePolicies),
  Glob: GlobToolGuard(...fileReadPolicies),
  Grep: GrepToolGuard(...fileReadPolicies),

  // Bash — extractables enforce the SAME file policies inside commands
  Bash: BashToolGuard({ allow: [
    // Read-only commands use readableFile (allow *, deny secrets)
    command`cat ${readableFile}`,
    command`head -n ${safeNumber} ${readableFile}`,

    // Write commands use writableFile (allow src/tests only, deny env)
    command`git add ${spread(writableFile)}`,
    command`git commit -m ${safeString}`,
    command`git checkout ${safeBranch}`,
    command`git push`,
    command`git pull`,

    // Package managers
    command`pnpm install`,
    command`pnpm add -D ${safePackage}`,
    command`pnpm test`,
    command`pnpm build`,
  ] }),
})

If .env is denied in ReadToolGuard, it's also denied in cat .env via readableFile. One source of truth.


Documentation

| Document | Description | |----------|-------------| | Pattern matching | String glob, path patterns (picomatch), command patterns | | Command templates | Composition splitting, spread(), backtracking, security | | Extractables | All extractables with imports, examples, and path scopes | | Guard factories | All 16 guard factories with field reference and examples | | Reusable policies | Shared deny arrays, command arrays, Vite env secrets preset | | Logging | Log levels, environment variables, denial output | | Security model | Threat model, quote-aware extraction, TOCTOU, fail-safe defaults |


Comparison with native permissions

| | Native permissions | tool-guard | |--|---------------------|------------| | Deny Read/Write/Edit | Ignored | Enforced | | Deny Bash | Partial | Enforced | | Command injection protection | None | command template + extractables | | Path traversal protection | None | Scope-isolated path extractables | | Type-safe config | No | Full TypeScript with autocompletion | | Custom validation | No | Guard functions + extractable policies | | Logging | No | Configurable (file + stderr) |


Contributing

pnpm install
pnpm test       # 640+ tests
pnpm lint       # tsc + eslint
pnpm build

License

MIT — Clément Pasquier