ML-175: parse bash commands structurally via unbash
Replace regex-based command blocking with structural AST parsing using unbash (zero-dependency TypeScript bash parser). The walker collects command names only from executable positions (simple commands, pipelines, subshells, command substitutions, control-flow bodies), excluding arguments, comments, and quoted strings. - Convert to directory-style extension with pinned unbash@3.0.0 - Add AST walker (ast.ts) covering 15 shell construct types - Update blocked_commands to plain names instead of regexes - Keep regex fallback for parse failures (degradation path) - Path-based blocking unchanged for non-bash tools - 41 tests cover blocked/allowed cases including false positives
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
import { parse } from "unbash";
|
||||
|
||||
/**
|
||||
* Walk a parsed `unbash` AST and collect all command names from executable
|
||||
* positions (simple commands, subshell bodies, command/substitution bodies,
|
||||
* pipeline members). Non-executed text (comments, quoted strings, echo
|
||||
* arguments) is structurally excluded and never inspected.
|
||||
*
|
||||
* Returns a `Set<string>` of lowercased command names, or an empty set if
|
||||
* parsing fails.
|
||||
*/
|
||||
export function extractCommandNames(command: string): Set<string> {
|
||||
const names = new Set<string>();
|
||||
|
||||
try {
|
||||
const script = parse(command);
|
||||
if (script && typeof script === "object" && script.commands) {
|
||||
walkScript(script, names);
|
||||
}
|
||||
} catch {
|
||||
// Parse failure — return empty set so caller can fall back to regex
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
function walkScript(script: any, names: Set<string>): void {
|
||||
const commands = script?.commands;
|
||||
if (!Array.isArray(commands)) return;
|
||||
for (const stmt of commands) {
|
||||
walkStatement(stmt, names);
|
||||
}
|
||||
}
|
||||
|
||||
function walkStatement(stmt: any, names: Set<string>): void {
|
||||
if (!stmt) return;
|
||||
walkNode(stmt.command, names);
|
||||
}
|
||||
|
||||
function walkCompoundList(node: any, names: Set<string>): void {
|
||||
if (!node?.commands || !Array.isArray(node.commands)) return;
|
||||
for (const stmt of node.commands) {
|
||||
walkStatement(stmt, names);
|
||||
}
|
||||
}
|
||||
|
||||
/** Recursively walk a command node and collect executable command names. */
|
||||
function walkNode(node: any, names: Set<string>): void {
|
||||
if (!node) return;
|
||||
|
||||
switch (node.type) {
|
||||
case "Command": {
|
||||
if (node.name) {
|
||||
// Collect the command name text
|
||||
const nameText = node.name.text || node.name.value;
|
||||
if (nameText && typeof nameText === "string") {
|
||||
names.add(nameText.toLowerCase());
|
||||
}
|
||||
// Walk name parts for CommandExpansion/ProcessSubstitution
|
||||
if (node.name.parts && Array.isArray(node.name.parts)) {
|
||||
for (const part of node.name.parts) {
|
||||
walkNode(part, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Walk prefix (assignment) value parts for nested expansions
|
||||
if (node.prefix && Array.isArray(node.prefix)) {
|
||||
for (const p of node.prefix) {
|
||||
if (p?.value?.parts && Array.isArray(p.value.parts)) {
|
||||
for (const part of p.value.parts) {
|
||||
walkNode(part, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Walk suffix parts for nested expansions (do NOT collect suffix
|
||||
// text — arguments are not commands)
|
||||
if (node.suffix && Array.isArray(node.suffix)) {
|
||||
for (const s of node.suffix) {
|
||||
if (s?.parts && Array.isArray(s.parts)) {
|
||||
for (const part of s.parts) {
|
||||
walkNode(part, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Pipeline": {
|
||||
if (node.commands && Array.isArray(node.commands)) {
|
||||
for (const cmd of node.commands) {
|
||||
walkNode(cmd, names);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "AndOr": {
|
||||
if (node.commands && Array.isArray(node.commands)) {
|
||||
for (const cmd of node.commands) {
|
||||
walkNode(cmd, names);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Subshell": {
|
||||
walkCompoundList(node.body, names);
|
||||
break;
|
||||
}
|
||||
|
||||
case "If": {
|
||||
walkCompoundList(node.clause, names);
|
||||
walkCompoundList(node.then, names);
|
||||
if (node.else) {
|
||||
walkCompoundList(node.else, names);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "While": {
|
||||
walkCompoundList(node.clause, names);
|
||||
walkCompoundList(node.body, names);
|
||||
break;
|
||||
}
|
||||
|
||||
case "For":
|
||||
case "Select": {
|
||||
// Walk the wordlist for command substitutions (e.g. for f in $(env);)
|
||||
if (node.wordlist && Array.isArray(node.wordlist)) {
|
||||
for (const w of node.wordlist) {
|
||||
if (w?.parts && Array.isArray(w.parts)) {
|
||||
for (const part of w.parts) {
|
||||
walkNode(part, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walkCompoundList(node.body, names);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Case": {
|
||||
// Walk the word being matched for command substitutions (e.g. case $(env) in)
|
||||
if (node.word?.parts && Array.isArray(node.word.parts)) {
|
||||
for (const part of node.word.parts) {
|
||||
walkNode(part, names);
|
||||
}
|
||||
}
|
||||
if (node.items && Array.isArray(node.items)) {
|
||||
for (const item of node.items) {
|
||||
// Walk patterns for command substitutions
|
||||
if (item.pattern && Array.isArray(item.pattern)) {
|
||||
for (const p of item.pattern) {
|
||||
if (p?.parts && Array.isArray(p.parts)) {
|
||||
for (const part of p.parts) {
|
||||
walkNode(part, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walkCompoundList(item.body, names);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Function": {
|
||||
// Function definition — walk the body for blocked commands
|
||||
if (node.body) {
|
||||
walkNode(node.body, names);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "BraceGroup": {
|
||||
// { cmd; } grouping
|
||||
walkCompoundList(node.body, names);
|
||||
break;
|
||||
}
|
||||
|
||||
case "ArithmeticFor": {
|
||||
// for ((i=0; i<10; i++)); do ...; done
|
||||
walkCompoundList(node.body, names);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Coproc": {
|
||||
// coproc cmd — the body is a Command node
|
||||
if (node.body) {
|
||||
walkNode(node.body, names);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "CommandExpansion":
|
||||
case "ProcessSubstitution": {
|
||||
if (node.script) {
|
||||
walkScript(node.script, names);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "CompoundList": {
|
||||
walkCompoundList(node, names);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import { extractCommandNames } from "./ast.ts";
|
||||
|
||||
// --- Test runner ---
|
||||
const blocked = new Set(["printenv", "env"]);
|
||||
|
||||
const tests: {
|
||||
cmd: string;
|
||||
expected: "BLOCKED" | "ALLOWED";
|
||||
reason: string;
|
||||
}[] = [
|
||||
// Simple commands
|
||||
{ cmd: "printenv", expected: "BLOCKED", reason: "Simple command match" },
|
||||
{ cmd: "env FOO=bar", expected: "BLOCKED", reason: "Simple command match" },
|
||||
{
|
||||
cmd: "FOO=bar printenv",
|
||||
expected: "BLOCKED",
|
||||
reason: "Assignment prefix — printenv is still the command",
|
||||
},
|
||||
{
|
||||
cmd: "FOO=bar env",
|
||||
expected: "BLOCKED",
|
||||
reason: "Assignment prefix — env is still the command",
|
||||
},
|
||||
|
||||
// Command substitution — blocked commands inside $() or backticks run
|
||||
{ cmd: "$(env)", expected: "BLOCKED", reason: "Command substitution" },
|
||||
{ cmd: "$(printenv)", expected: "BLOCKED", reason: "Command substitution" },
|
||||
{
|
||||
cmd: "`env`",
|
||||
expected: "BLOCKED",
|
||||
reason: "Backtick command substitution",
|
||||
},
|
||||
{
|
||||
cmd: "echo $(env)",
|
||||
expected: "BLOCKED",
|
||||
reason: "env inside command substitution in echo argument",
|
||||
},
|
||||
{
|
||||
cmd: "FOO=$(env) echo hi",
|
||||
expected: "BLOCKED",
|
||||
reason: "env inside command substitution in assignment value",
|
||||
},
|
||||
|
||||
// Subshell
|
||||
{ cmd: "(env)", expected: "BLOCKED", reason: "Subshell — env runs inside" },
|
||||
|
||||
// Pipeline — any member triggers a block
|
||||
{
|
||||
cmd: "env | grep FOO",
|
||||
expected: "BLOCKED",
|
||||
reason: "Pipeline — env is first command",
|
||||
},
|
||||
{
|
||||
cmd: "grep FOO | env",
|
||||
expected: "BLOCKED",
|
||||
reason: "Pipeline — env is second command",
|
||||
},
|
||||
{
|
||||
cmd: "printenv | env",
|
||||
expected: "BLOCKED",
|
||||
reason: "Both blocked commands in pipeline",
|
||||
},
|
||||
|
||||
// Compound lists and control flow
|
||||
{
|
||||
cmd: "for f in *; do env; done",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in for loop body",
|
||||
},
|
||||
{
|
||||
cmd: "for f in $(env); do echo $f; done",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in for wordlist command substitution",
|
||||
},
|
||||
{
|
||||
cmd: "for ((i=0;i<10;i++)); do env; done",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in arithmetic for",
|
||||
},
|
||||
{
|
||||
cmd: "while true; do env; done",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in while body",
|
||||
},
|
||||
{
|
||||
cmd: "until false; do env; done",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in until body (parsed as While with kind:until)",
|
||||
},
|
||||
{
|
||||
cmd: "if true; then env; fi",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in if-then",
|
||||
},
|
||||
{
|
||||
cmd: "case x in a) env;; esac",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in case body",
|
||||
},
|
||||
{
|
||||
cmd: "case $(env) in a) echo hi;; esac",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in Case.word substitution",
|
||||
},
|
||||
{ cmd: "true && env", expected: "BLOCKED", reason: "env in AndOr (&&)" },
|
||||
{
|
||||
cmd: "select f in $(env); do echo $f; done",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in select wordlist",
|
||||
},
|
||||
|
||||
// Function definition, brace group, coproc
|
||||
{
|
||||
cmd: "foo() { env; }",
|
||||
expected: "BLOCKED",
|
||||
reason: "env in function body",
|
||||
},
|
||||
{ cmd: "{ env; }", expected: "BLOCKED", reason: "env in brace group" },
|
||||
{ cmd: "coproc env", expected: "BLOCKED", reason: "env in coproc body" },
|
||||
|
||||
// --- ALLOWED: blocked command name is NOT at an executable position ---
|
||||
|
||||
{
|
||||
cmd: "echo printenv",
|
||||
expected: "ALLOWED",
|
||||
reason: "printenv is an argument to echo, not a command",
|
||||
},
|
||||
{ cmd: "# printenv", expected: "ALLOWED", reason: "Comment — not executed" },
|
||||
{
|
||||
cmd: 'echo "use env to..."',
|
||||
expected: "ALLOWED",
|
||||
reason: "Inside double-quoted string",
|
||||
},
|
||||
{
|
||||
cmd: "echo 'printenv'",
|
||||
expected: "ALLOWED",
|
||||
reason: "Inside single-quoted string",
|
||||
},
|
||||
{
|
||||
cmd: "(echo printenv)",
|
||||
expected: "ALLOWED",
|
||||
reason: "printenv is argument to echo inside subshell",
|
||||
},
|
||||
{
|
||||
cmd: "$(echo env)",
|
||||
expected: "ALLOWED",
|
||||
reason: "env is argument to echo inside command substitution",
|
||||
},
|
||||
{
|
||||
cmd: 'echo "use env" | grep FOO',
|
||||
expected: "ALLOWED",
|
||||
reason: "env in double-quoted string in pipeline",
|
||||
},
|
||||
|
||||
// --- ALLOWED: command is not in the blocked set ---
|
||||
{
|
||||
cmd: "export FOO=bar",
|
||||
expected: "ALLOWED",
|
||||
reason: "export is not in blocked_commands",
|
||||
},
|
||||
{
|
||||
cmd: "ls -la",
|
||||
expected: "ALLOWED",
|
||||
reason: "ls is not in blocked_commands",
|
||||
},
|
||||
{
|
||||
cmd: "echo hi; echo there",
|
||||
expected: "ALLOWED",
|
||||
reason: "Semicolon-separated harmless commands",
|
||||
},
|
||||
{
|
||||
cmd: "echo hi || echo fail",
|
||||
expected: "ALLOWED",
|
||||
reason: "Or-separated harmless commands",
|
||||
},
|
||||
{
|
||||
cmd: "[[ -f file ]]",
|
||||
expected: "ALLOWED",
|
||||
reason: "TestCommand — no commands executed",
|
||||
},
|
||||
|
||||
// --- ALLOWED: no command at all ---
|
||||
{ cmd: "", expected: "ALLOWED", reason: "Empty command" },
|
||||
{
|
||||
cmd: "FOO=bar",
|
||||
expected: "ALLOWED",
|
||||
reason: "Bare assignment, no command name",
|
||||
},
|
||||
{
|
||||
cmd: ">/dev/null",
|
||||
expected: "ALLOWED",
|
||||
reason: "Redirection only, no command name",
|
||||
},
|
||||
];
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const { cmd, expected, reason } of tests) {
|
||||
const names = extractCommandNames(cmd);
|
||||
const hasBlocked = [...names].some((n) => blocked.has(n));
|
||||
const actual = hasBlocked ? "BLOCKED" : "ALLOWED";
|
||||
|
||||
if (actual === expected) {
|
||||
passed++;
|
||||
console.log(`✅ ${expected.padEnd(7)} | ${cmd.padEnd(45)} | ${reason}`);
|
||||
} else {
|
||||
failed++;
|
||||
console.log(
|
||||
`❌ expected ${expected.padEnd(7)} got ${actual.padEnd(7)} | ${cmd.padEnd(45)} | ${reason}`,
|
||||
);
|
||||
console.log(` Command names found: [${[...names].join(", ")}]`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n${passed} passed, ${failed} failed`);
|
||||
if (failed > 0) process.exit(1);
|
||||
+35
-8
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { normalize, resolve } from "node:path";
|
||||
import { extractCommandNames } from "./ast.ts";
|
||||
|
||||
interface Config {
|
||||
blocked_paths: string[];
|
||||
@@ -30,6 +31,11 @@ export default function (pi: ExtensionAPI) {
|
||||
}
|
||||
|
||||
const pathRegexes = config.blocked_paths.map(patternToRegex);
|
||||
// Build a Set of lowercased blocked command names for exact matching.
|
||||
// Also build regexes for regex fallback when parsing fails.
|
||||
const blockedCommands = new Set(
|
||||
config.blocked_commands.map((c) => c.toLowerCase()),
|
||||
);
|
||||
const commandRegexes = config.blocked_commands.map((r) => new RegExp(r, "i"));
|
||||
|
||||
// Proactively tell the agent which paths are off-limits so it doesn't try in the first place
|
||||
@@ -98,14 +104,35 @@ export default function (pi: ExtensionAPI) {
|
||||
return { block: true, reason };
|
||||
}
|
||||
|
||||
// Check for blocked commands (env, printenv, set)
|
||||
const cmdHit = commandRegexes.find((r) => r.test(command));
|
||||
if (cmdHit) {
|
||||
const reason =
|
||||
`🚫 ACCESS DENIED by sensitive-file-guard: this command matches a blocked pattern. ` +
|
||||
`DO NOT retry. Tell the user the command is blocked and STOP.`;
|
||||
if (ctx.hasUI) ctx.ui.notify(`Blocked sensitive command`, "warning");
|
||||
return { block: true, reason };
|
||||
// --- Parse command structurally to detect blocked commands ---
|
||||
const parsedNames = extractCommandNames(command);
|
||||
|
||||
// Use parsed names if we got any (or if the command parsed to empty
|
||||
// commands, which means it's a comment or similar non-executable text).
|
||||
// Fall back to regex matching if parsing returned an empty set for a
|
||||
// non-empty command — this handles parse failures gracefully.
|
||||
if (parsedNames.size > 0 || command.trim().length === 0) {
|
||||
const cmdHit = [...parsedNames].find((name) =>
|
||||
blockedCommands.has(name),
|
||||
);
|
||||
if (cmdHit) {
|
||||
const reason =
|
||||
`🚫 ACCESS DENIED by sensitive-file-guard: this command matches a blocked pattern. ` +
|
||||
`DO NOT retry. Tell the user the command is blocked and STOP.`;
|
||||
if (ctx.hasUI) ctx.ui.notify(`Blocked sensitive command`, "warning");
|
||||
return { block: true, reason };
|
||||
}
|
||||
} else {
|
||||
// Fallback: parsing returned nothing for a non-empty command.
|
||||
// Use regex-based matching as a conservative safety net.
|
||||
const cmdHit = commandRegexes.find((r) => r.test(command));
|
||||
if (cmdHit) {
|
||||
const reason =
|
||||
`🚫 ACCESS DENIED by sensitive-file-guard: this command matches a blocked pattern. ` +
|
||||
`DO NOT retry. Tell the user the command is blocked and STOP.`;
|
||||
if (ctx.hasUI) ctx.ui.notify(`Blocked sensitive command`, "warning");
|
||||
return { block: true, reason };
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
+567
@@ -0,0 +1,567 @@
|
||||
{
|
||||
"name": "sensitive-file-guard",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sensitive-file-guard",
|
||||
"dependencies": {
|
||||
"unbash": "3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
|
||||
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
|
||||
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
|
||||
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
|
||||
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
|
||||
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
|
||||
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
|
||||
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
|
||||
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
|
||||
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
|
||||
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
|
||||
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
|
||||
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.7",
|
||||
"@esbuild/android-arm": "0.27.7",
|
||||
"@esbuild/android-arm64": "0.27.7",
|
||||
"@esbuild/android-x64": "0.27.7",
|
||||
"@esbuild/darwin-arm64": "0.27.7",
|
||||
"@esbuild/darwin-x64": "0.27.7",
|
||||
"@esbuild/freebsd-arm64": "0.27.7",
|
||||
"@esbuild/freebsd-x64": "0.27.7",
|
||||
"@esbuild/linux-arm": "0.27.7",
|
||||
"@esbuild/linux-arm64": "0.27.7",
|
||||
"@esbuild/linux-ia32": "0.27.7",
|
||||
"@esbuild/linux-loong64": "0.27.7",
|
||||
"@esbuild/linux-mips64el": "0.27.7",
|
||||
"@esbuild/linux-ppc64": "0.27.7",
|
||||
"@esbuild/linux-riscv64": "0.27.7",
|
||||
"@esbuild/linux-s390x": "0.27.7",
|
||||
"@esbuild/linux-x64": "0.27.7",
|
||||
"@esbuild/netbsd-arm64": "0.27.7",
|
||||
"@esbuild/netbsd-x64": "0.27.7",
|
||||
"@esbuild/openbsd-arm64": "0.27.7",
|
||||
"@esbuild/openbsd-x64": "0.27.7",
|
||||
"@esbuild/openharmony-arm64": "0.27.7",
|
||||
"@esbuild/sunos-x64": "0.27.7",
|
||||
"@esbuild/win32-arm64": "0.27.7",
|
||||
"@esbuild/win32-ia32": "0.27.7",
|
||||
"@esbuild/win32-x64": "0.27.7"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.14.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
|
||||
"integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/unbash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unbash/-/unbash-3.0.0.tgz",
|
||||
"integrity": "sha512-FeFPZ/WFT0mbRCuydiZzpPFlrYN8ZUpphQKoq4EeElVIYjYyGzPMxQR/simUwCOJIyVhpFk4RbtyO7RuMpMnHA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "sensitive-file-guard",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"unbash": "3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.21.0"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"_comment": "Blocked paths for pi. Glob-like patterns, matched case-insensitively against absolute paths.",
|
||||
"_comment": "Blocked paths for pi. Glob-like patterns, matched case-insensitively against absolute paths. blocked_commands are exact command names (not regexes), matched case-insensitively against structurally parsed bash AST command names.",
|
||||
"blocked_paths": [
|
||||
"mise.local.toml",
|
||||
"*.pem",
|
||||
@@ -15,6 +15,6 @@
|
||||
],
|
||||
"blocked_commands": [
|
||||
"printenv",
|
||||
"\\benv\\b"
|
||||
"env"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user