Catalog npm
PACKAGE EXPLORERView in registry

@0xkobold/pi-whitelist

npm

Tri-state tool permission system (allow/deny/ask) for AI agent tool invocations

ai-agentpermissionspi-packagetool-gatingwhitelist
Versions collected11
Direct dependencies7From release 0.5.2
Last observed21 Oct, 09:03 UTC

README

Current package documentation

pi-whitelist

Tri-state tool permission system (allow/deny/ask) for AI agent tool invocations

A standalone, runtime-agnostic npm module for controlling AI agent tool invocations.

Why?

Every AI agent that executes tools needs permission gating. Without it, agents can run destructive commands without oversight. pi-whitelist gives you a simple, battle-tested model:

#ActionBehavior
1Allow onceLet it run this time, don't persist
2Allow alwaysSave a rule, auto-approve future matches
3DenyBlock the invocation

Every check returns one of three decisions: allow, deny, or ask. No booleans, no ambiguity.

Install

npm install @0xkobold/pi-whitelist

Quick Start

import { PermissionManager } from '@0xkobold/pi-whitelist'

const manager = new PermissionManager()

// Read-only tools are auto-allowed
manager.check({ toolName: 'Read' })
// → { behavior: 'allow', decisionReason: { type: 'other', reason: 'read-only-tool' } }

// Destructive tools require approval
manager.check({ toolName: 'Bash', ruleContent: 'docker build .' })
// → { behavior: 'ask', message: 'Bash: docker build .', suggestions: [...] }

// Add an "allow always" rule
manager.addRule({ toolName: 'Bash', ruleContent: 'git *' }, 'allow', 'session')
manager.check({ toolName: 'Bash', ruleContent: 'git status' })
// → { behavior: 'allow', decisionReason: { type: 'rule', rule: ... } }

// Add a deny rule — deny always wins over allow for the same pattern
manager.addRule({ toolName: 'Bash', ruleContent: 'rm -rf *' }, 'deny', 'session')
manager.check({ toolName: 'Bash', ruleContent: 'rm -rf /tmp' })
// → { behavior: 'deny', message: 'Permission denied for Bash: rm -rf /tmp', ... }

Three-State UI Integration

const decision = manager.check({ toolName: 'Bash', ruleContent: 'docker build -t myapp .' })

if (decision.behavior === 'ask') {
  // Show the prompt:
  // ┌──────────────────────────────────────────────┐
  // │  Bash: docker build -t myapp .                │
  // │                                                │
  // │  1. Allow          (allow this once)           │
  // │  2. Allow always   (add rule to settings)      │
  // │  3. Deny           (block this invocation)     │
  // └──────────────────────────────────────────────┘
  const userChoice = await showPermissionPrompt(decision)

  if (userChoice === 1) {
    // Allow once — no persistence
    return { behavior: 'allow', decisionReason: { type: 'other', reason: 'user-allow-once' } }
  }
  if (userChoice === 2) {
    // Allow always — persist the rule
    manager.addRule(
      { toolName: 'Bash', ruleContent: 'docker *' },
      'allow',
      'projectSettings', // persists to .pi/settings.json
    )
  }
  if (userChoice === 3) {
    // Deny
    return { behavior: 'deny', message: decision.message, ... }
  }
}

Rule Format

Rules use the format ToolName or ToolName(content) with glob patterns:

RuleMatches
BashAny Bash invocation
Bash(git *)Any command starting with git
Bash(npm test)Exactly npm test
FileEdit(/src/**)Any file edit under /src/
ReadThe Read tool (no content filter)
Bash(python -c "print\(1\)")Commands with escaped parentheses

Permission Modes

ModeBehavior
defaultPrompt for destructive tools, auto-allow read-only tools
bypassPermissionsAllow everything — dangerous, for CI/testing only
planAsk for everything, even read-only tools
acceptEditsAuto-allow file edits, ask for everything else
dontAskAllow everything not explicitly denied
const ci = new PermissionManager({ mode: 'bypassPermissions' })
ci.check({ toolName: 'Bash', ruleContent: 'rm -rf /' })
// → { behavior: 'allow', decisionReason: { type: 'mode', mode: 'bypassPermissions' } }

Rule Sources & Priority

Rules come from multiple sources, merged by priority (lowest → highest):

SourceFilePriority
userSettings~/.pi/settings.json0 (lowest)
projectSettings.pi/settings.json1
localSettings.pi/settings.local.json2
flagSettingsCLI flags3
policySettingsEnterprise/org policy4
cliArg--allowedTools5
command/command in session6
sessionIn-memory only7 (highest)

Deny always wins over allow when both match the same tool+content.

Settings File

.pi/settings.json:

{
  "permissions": {
    "defaultMode": "default",
    "allow": [
      "Bash(git *)",
      "Bash(npm test)",
      "FileEdit(/src/**)",
      "Read"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(sudo *)"
    ],
    "ask": [
      "Bash(docker *)"
    ],
    "additionalDirectories": []
  }
}

API Reference

PermissionManager

The main class. Holds rules, resolves checks, persists changes.

const manager = new PermissionManager({
  store?: SettingsStore,              // Storage backend (default: MemorySettingsStore)
  mode?: PermissionMode,              // 'default' | 'bypassPermissions' | 'plan' | 'acceptEdits' | 'dontAsk'
  additionalWorkingDirectories?: Map<string, WorkingDirectorySource>,
  isBypassPermissionsModeAvailable?: boolean,
  shouldAvoidPermissionPrompts?: boolean,
})
MethodDescription
check(input)Evaluate a tool invocation against all rules
addRule(rule, behavior, source)Add a rule and persist it
removeRule(rule, behavior, source)Remove a matching rule
setMode(mode)Switch permission mode
applyUpdates(updates)Batch-apply permission updates
isBashAllowed(command)Convenience: check if Bash command is allowed
isFileEditAllowed(path)Convenience: check if file path edit is allowed
getRulesForTool(toolName)Get all rules for a specific tool
getRulesFromSource(source)Get all rules from a specific source
getContext()Get the full permission context snapshot
invalidateCache()Clear the rule evaluation cache

checkPermission(input)

Standalone one-shot check with default settings:

import { checkPermission } from '@0xkobold/pi-whitelist'

const decision = checkPermission({ toolName: 'Read' })
// → { behavior: 'allow', ... }

Matchers

MatcherForBehavior
GlobMatcherDefault fallbackUses picomatch for glob matching
CommandMatcherBash, PowerShellSplits on &&, ||, ;, | and checks prefix
FileMatcherFileEdit, FileWriteNormalizes paths to POSIX, uses picomatch

Storage

StoreUse
MemorySettingsStoreTesting, ephemeral sessions
FileSettingsStorePersistent settings in .pi/settings.json
import { MemorySettingsStore, FileSettingsStore, mergeSettings } from '@0xkobold/pi-whitelist'

const mem = new MemorySettingsStore({ permissions: { allow: ['Bash(git *)'], deny: [], ask: [], additionalDirectories: [] } })
const file = new FileSettingsStore('.pi/settings.json')
await file.load()

Read-Only Tools

These tools auto-allow in default mode without prompting:

Read, FileRead, Glob, Grep, WebFetch, WebSearch, TaskGet, TaskList, TaskOutput, ListMcpResources, ReadMcpResource, ToolSearch, LSP, AskUser

Error Classes

ErrorCodeWhen
PermissionErrorVariousBase error class
RuleParseErrorRULE_PARSE_ERRORMalformed rule string
StorageErrorSTORAGE_ERRORFile read/write failure
MatcherErrorMATCHER_ERRORGlob/command matching failure

Subpath Exports

import { parseRuleString, serializeRuleString } from '@0xkobold/pi-whitelist/rules'
import { GlobMatcher, CommandMatcher, FileMatcher } from '@0xkobold/pi-whitelist/matchers'
import { MemorySettingsStore, FileSettingsStore, mergeSettings } from '@0xkobold/pi-whitelist/storage'
import type { PermissionDecision, PermissionRule, PermissionMode } from '@0xkobold/pi-whitelist/types'

Test

npm test        # Run all tests
npm run build   # Compile TypeScript

97 tests across 9 suites covering parser, matchers, storage, merge, errors, readonly, constants, manager, and check.

License

MIT

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 11

Collected releases
VersionPublishedDirect dependenciesOpen version
0.5.2Registry latest14 May 20267
0.5.113 May 20267
0.5.013 May 20267
0.4.213 May 20267
0.4.113 May 20267
0.4.013 May 20267
0.3.113 May 20267
0.3.013 May 20267
0.2.113 May 20267
0.2.013 May 20267
0.1.013 May 20266

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

ACCESSIBLE
0xkobold/pi-whitelist

Shared repository data · GitHub

License detected in repository
Not collected
Last push
14 May 2026
Archived
No
Contributor identities
— · partial collection

Public sponsor counts have not been collected. These signals belong to the repository owner.

Downloads & popularity

0count
Gauge · observed snapshots22 Oct 2026
10.50
2026-10-22: 0 · observed 2026-10-22T16:57:25.8520907Z
22 Oct22 Oct

github · 1 observations shown. Missing days are unknown.

View observed values
Date / source periodValueObserved at
2026-10-2202026-10-22T16:57:25.8520907Z

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 09:03 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:5b337584beb5525e892b60ae52538fd182731611a6b01eab7b35ed2a53ceced3