@seanmozeik/tripwire 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # `@seanmozeik/tripwire`
2
+
3
+ Opinionated hooks dispatcher for AI coding agents (Claude Code, Codex, Devin, etc.) with configurable safety rules. Blocks or rewrites dangerous commands with actionable error messages.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun install @seanmozeik/tripwire
9
+ ```
10
+
11
+ ## CLI
12
+
13
+ ```bash
14
+ tripwire test '<command>' # Test a command
15
+ tripwire test --tool=Read --path=.env # Test Read tool
16
+ tripwire test --post --tool=Bash --stdout='ghp_TOKEN' # Test PostToolUse
17
+ ```
18
+
19
+ ## Hook Configuration
20
+
21
+ Configure your AI agent to call `tripwire-hook` for hook events:
22
+
23
+ ### Claude Code
24
+
25
+ `~/.claude/settings.json`:
26
+
27
+ ```jsonc
28
+ {
29
+ "hooks": {
30
+ "PreToolUse": [{ "hooks": [{ "type": "command", "command": "/path/to/tripwire-hook" }] }],
31
+ "PostToolUse": [{ "hooks": [{ "type": "command", "command": "/path/to/tripwire-hook" }] }],
32
+ },
33
+ }
34
+ ```
35
+
36
+ ### Codex
37
+
38
+ Same as Claude Code — Codex uses the same hook format.
39
+
40
+ ### Devin
41
+
42
+ Configure in your Devin settings to call `tripwire-hook` for tool events.
43
+
44
+ ## Configuration
45
+
46
+ Create `~/.config/tripwire/config.json` to customize behavior:
47
+
48
+ ```json
49
+ {
50
+ "rtk": { "enabled": true, "path": "/opt/homebrew/bin/rtk" },
51
+ "git": {
52
+ "protectedBranches": ["main", "master", "develop", "production", "release"],
53
+ "enforceConventionalCommits": true
54
+ },
55
+ "safePaths": {
56
+ "relative": ["dist", "build", ".next", "node_modules"],
57
+ "absolute": ["/tmp", "/var/tmp"]
58
+ },
59
+ "blockedCommands": [
60
+ { "pattern": "dangerous-tool", "message": "Use safer-alternative instead", "action": "deny" }
61
+ ],
62
+ "allowedCommands": [
63
+ {
64
+ "pattern": "my-custom-tool",
65
+ "message": "Allowing my-custom-tool per your configuration",
66
+ "action": "allow"
67
+ }
68
+ ]
69
+ }
70
+ ```
71
+
72
+ ### Configuration Options
73
+
74
+ #### `rtk`
75
+
76
+ - `enabled` (boolean, default: `false`) — Enable rtk token-saver integration
77
+ - `path` (string, optional) — Path to rtk binary. If not specified, searches common locations.
78
+
79
+ #### `git`
80
+
81
+ - `protectedBranches` (string[], default: `["main", "master", "develop", "production", "release"]`) — Branches that require PR for push
82
+ - `enforceConventionalCommits` (boolean, default: `true`) — Enforce Conventional Commits format for commit messages
83
+
84
+ #### `safePaths`
85
+
86
+ - `relative` (string[], optional) — Additional relative paths considered safe for destructive operations
87
+ - `absolute` (string[], optional) — Additional absolute paths considered safe for destructive operations
88
+
89
+ Default safe paths include: `dist`, `build`, `.next`, `node_modules`, `/tmp`, `/var/tmp`, and other common build/cache directories.
90
+
91
+ #### `blockedCommands`
92
+
93
+ Array of custom command blocks:
94
+
95
+ - `pattern` (string) — Command pattern to block (uses shell parsing for matching)
96
+ - `message` (string) — Error message shown when blocked
97
+ - `action` (`"deny"` | `"ask"`, default: `"deny"`) — Whether to deny or ask for confirmation
98
+
99
+ #### `allowedCommands`
100
+
101
+ Array of custom command allows (overrides blocks):
102
+
103
+ - `pattern` (string) — Command pattern to allow
104
+ - `message` (string) — Message shown when allowed
105
+ - `action` (string, default: `"allow"`) — Always `"allow"` for this context
106
+
107
+ ### Shell-Based Command Matching
108
+
109
+ Command patterns in `blockedCommands` and `allowedCommands` use the same shell parsing as the rest of tripwire. This means:
110
+
111
+ - `rm` matches any `rm` command
112
+ - `git push` matches `git push` with any arguments
113
+ - Patterns are parsed using shell-quote for accurate matching
114
+ - More sophisticated than simple regex
115
+
116
+ Example:
117
+
118
+ ```json
119
+ {
120
+ "blockedCommands": [
121
+ {
122
+ "pattern": "brew install",
123
+ "message": "Use brew install with explicit version pinning",
124
+ "action": "ask"
125
+ }
126
+ ]
127
+ }
128
+ ```
129
+
130
+ ## Default Behavior
131
+
132
+ Tripwire comes with opinionated but reasonable defaults:
133
+
134
+ ### Bash Safety
135
+
136
+ - Blocks catastrophic commands: `rm -rf /`, fork bombs, `dd` to disks
137
+ - Blocks macOS system mutations: `defaults write`, `launchctl`, `diskutil erase`
138
+ - Blocks cloud destructive operations: `gh repo delete`, `flyctl destroy`
139
+ - Scopes `rm` and `find -delete` to safe paths (build outputs, cache directories)
140
+ - Blocks network install scripts: `curl | bash`, `wget | sh`
141
+ - Enforces package manager policy: Bun-only, no npm/pnpm/yarn/pip
142
+
143
+ ### Git Policy
144
+
145
+ - Read-only operations allowed: `status`, `log`, `diff`, `fetch`, etc.
146
+ - Blocks working-tree destruction: `reset --hard`, `clean -fd`, `checkout .`
147
+ - Blocks history rewriting: `rebase -i`, `filter-branch`, `commit --amend`
148
+ - Blocks force push and protected branch pushes
149
+ - Enforces Conventional Commits format (configurable)
150
+ - Requires `-m "message"` for commits (no editor mode)
151
+ - Asks for confirmation on merge/rebase/cherry-pick
152
+
153
+ ### File Protection
154
+
155
+ - Blocks reads/writes to `.env`, `.ssh/`, `*.pem`, `id_rsa*`, etc.
156
+ - Warns on TODO/FIXME/placeholder in code (configurable)
157
+ - Scrubs secrets from tool output
158
+
159
+ ## Bypass
160
+
161
+ Add `# tripwire-allow: <reason>` to bypass any rule:
162
+
163
+ ```bash
164
+ rm -rf /tmp/test # tripwire-allow: cleaning test directory
165
+ git reset --hard HEAD~1 # tripwire-allow: undoing mistaken commit
166
+ ```
167
+
168
+ ## Library Usage
169
+
170
+ ```typescript
171
+ import { allow, deny, ask, warn } from '@seanmozeik/tripwire';
172
+ import type { Decision, Config } from '@seanmozeik/tripwire';
173
+ ```
174
+
175
+ ## Development
176
+
177
+ ```bash
178
+ bun install
179
+ bun run build # Build dist/tripwire.js and dist/tripwire-cli.js
180
+ bun run check # Format + lint + typecheck
181
+ bun test # Run tests
182
+ ```
183
+
184
+ ## License
185
+
186
+ MIT
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ // @bun @bytecode @bun-cjs
3
+ (function(exports, require, module, __filename, __dirname) {var L=require("child_process"),M="/Users/sean/dev/tripwire/src/../dist/tripwire.js",O=(f)=>{let j="Bash",z=!1,x,F,G,J,K;for(let q of f){if(q==="--post"){z=!0;continue}if(q.startsWith("--tool=")){j=q.slice(7);continue}if(q.startsWith("--path=")){x=q.slice(7);continue}if(q.startsWith("--stdout=")){G=q.slice(9);continue}if(q.startsWith("--stderr=")){J=q.slice(9);continue}if(q.startsWith("--content=")){K=q.slice(10);continue}F??=q}return{tool:j,post:z,path:x,command:F,stdout:G,stderr:J,content:K}},Q=(f,j)=>{if(f==="Bash")return{command:j.command??""};if(f==="Read")return{file_path:j.path??""};if(f==="Write")return{file_path:j.path??"",content:j.content??""};if(f==="Edit"||f==="MultiEdit")return{file_path:j.path??"",old_string:"",new_string:j.content??""};return},R=(f)=>{let j=f.post?"PostToolUse":"PreToolUse",z=f.tool,x={hook_event_name:j,tool_name:z,cwd:process.cwd(),session_id:"tripwire-cli-test",tool_input:Q(z,f)};if(f.post)x.tool_response=z==="Bash"?{stdout:f.stdout??"",stderr:f.stderr??""}:{content:f.content??""};return x},V=()=>{process.stdout.write(["tripwire CLI \u2014 synthetic-event tester","","Usage:"," tripwire test '<command>' # PreToolUse Bash"," tripwire test --tool=Read --path=.env # PreToolUse Read"," tripwire test --tool=Write --path=foo.ts --content='TODO finish'"," tripwire test --post --tool=Bash --stdout='ghp_REAL_TOKEN' # PostToolUse",""].join(`
4
+ `))},W=()=>{let f=process.argv.slice(2);if(f.length===0||f[0]!=="test")V(),process.exit(0);let j=O(f.slice(1)),z=R(j),x=L.spawnSync(M,[],{input:JSON.stringify(z),encoding:"utf8",timeout:1e4});if(x.error!==void 0)process.stderr.write(`error: ${String(x.error)}
5
+ `),process.exit(1);let F=x.stdout;try{let G=JSON.parse(F);process.stdout.write(`${JSON.stringify(G,null,2)}
6
+ `)}catch{process.stdout.write(F)}};W();})
Binary file