failproofai 0.0.6 → 0.0.8
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09icjsf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0t3ka1q._.js → [root-of-the-server]__0_rr1ty._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ow37ro._.js → [root-of-the-server]__0h3orxc._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0okos0k._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.js +7 -7
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/{061hxr2b-j.6q.js → 096~b1zwv69ph.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0k5t-n0s8p2nr.js → 0eowehbf5egcz.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ubv3x~0zdd_w.js → 0lua3p__elu_..js} +1 -1
- package/.next/standalone/.next/static/chunks/{13cot7j99xkb~.js → 0mbc8hyeqe2c4.js} +1 -1
- package/.next/standalone/.next/static/chunks/0s_18.dox44e9.js +1 -0
- package/.next/standalone/.next/static/chunks/{0-igg2k65fzo_.js → 0t3euwspxi_zg.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0lq8ary5l4s8t.js → 151bdxm9n-pry.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0v23ca5xty1n~.js → 175-vim0.ztb2.js} +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +204 -62
- package/package.json +1 -1
- package/src/hooks/builtin-policies.ts +206 -71
- package/src/hooks/custom-hooks-loader.ts +6 -3
- package/src/hooks/hooks-config.ts +31 -5
- package/.next/standalone/.next/static/chunks/04iuhj_-h-21-.js +0 -1
- /package/.next/standalone/.next/static/{my01WPjry7ohRUHyTaYp4 → RYld7TSCDXm2_WhJq20rD}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{my01WPjry7ohRUHyTaYp4 → RYld7TSCDXm2_WhJq20rD}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{my01WPjry7ohRUHyTaYp4 → RYld7TSCDXm2_WhJq20rD}/_ssgManifest.js +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -103,7 +103,7 @@ var init_hook_logger = __esm(() => {
|
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
// src/hooks/hooks-config.ts
|
|
106
|
-
import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
106
|
+
import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, statSync as statSync2 } from "node:fs";
|
|
107
107
|
import { resolve, dirname } from "node:path";
|
|
108
108
|
import { homedir as homedir2 } from "node:os";
|
|
109
109
|
function readConfigAt(path) {
|
|
@@ -117,8 +117,24 @@ function readConfigAt(path) {
|
|
|
117
117
|
return {};
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
+
function findProjectConfigDir(start) {
|
|
121
|
+
const home = homedir2();
|
|
122
|
+
let dir = resolve(start);
|
|
123
|
+
while (dir !== home) {
|
|
124
|
+
const marker = resolve(dir, ".failproofai");
|
|
125
|
+
try {
|
|
126
|
+
if (statSync2(marker).isDirectory())
|
|
127
|
+
return dir;
|
|
128
|
+
} catch {}
|
|
129
|
+
const parent = dirname(dir);
|
|
130
|
+
if (parent === dir)
|
|
131
|
+
break;
|
|
132
|
+
dir = parent;
|
|
133
|
+
}
|
|
134
|
+
return resolve(start);
|
|
135
|
+
}
|
|
120
136
|
function readMergedHooksConfig(cwd) {
|
|
121
|
-
const base =
|
|
137
|
+
const base = findProjectConfigDir(cwd ?? process.cwd());
|
|
122
138
|
const projectPath = resolve(base, ".failproofai", "policies-config.json");
|
|
123
139
|
const localPath = resolve(base, ".failproofai", "policies-config.local.json");
|
|
124
140
|
const globalPath = resolve(homedir2(), ".failproofai", "policies-config.json");
|
|
@@ -150,14 +166,13 @@ function readMergedHooksConfig(cwd) {
|
|
|
150
166
|
};
|
|
151
167
|
}
|
|
152
168
|
function getConfigPathForScope(scope, cwd) {
|
|
153
|
-
const base = cwd ? resolve(cwd) : process.cwd();
|
|
154
169
|
switch (scope) {
|
|
155
170
|
case "user":
|
|
156
171
|
return resolve(homedir2(), ".failproofai", "policies-config.json");
|
|
157
172
|
case "project":
|
|
158
|
-
return resolve(
|
|
173
|
+
return resolve(findProjectConfigDir(cwd ?? process.cwd()), ".failproofai", "policies-config.json");
|
|
159
174
|
case "local":
|
|
160
|
-
return resolve(
|
|
175
|
+
return resolve(findProjectConfigDir(cwd ?? process.cwd()), ".failproofai", "policies-config.local.json");
|
|
161
176
|
}
|
|
162
177
|
}
|
|
163
178
|
function readScopedHooksConfig(scope, cwd) {
|
|
@@ -768,6 +783,38 @@ function blockFailproofaiCommands(ctx) {
|
|
|
768
783
|
}
|
|
769
784
|
return allow();
|
|
770
785
|
}
|
|
786
|
+
function blockInfraCli(ctx, re, denyMsg) {
|
|
787
|
+
if (ctx.toolName !== "Bash")
|
|
788
|
+
return allow();
|
|
789
|
+
const cmd = getCommand(ctx);
|
|
790
|
+
if (!re.test(cmd))
|
|
791
|
+
return allow();
|
|
792
|
+
const allowPatterns = ctx.params?.allowPatterns ?? [];
|
|
793
|
+
if (allowPatterns.some((p) => matchesAllowedPattern(cmd, p)))
|
|
794
|
+
return allow();
|
|
795
|
+
return deny(denyMsg);
|
|
796
|
+
}
|
|
797
|
+
function blockKubectl(ctx) {
|
|
798
|
+
return blockInfraCli(ctx, KUBECTL_RE, "kubectl commands are blocked");
|
|
799
|
+
}
|
|
800
|
+
function blockTerraform(ctx) {
|
|
801
|
+
return blockInfraCli(ctx, TERRAFORM_RE, "terraform/tofu commands are blocked");
|
|
802
|
+
}
|
|
803
|
+
function blockAwsCli(ctx) {
|
|
804
|
+
return blockInfraCli(ctx, AWS_CLI_RE, "aws CLI commands are blocked");
|
|
805
|
+
}
|
|
806
|
+
function blockGcloud(ctx) {
|
|
807
|
+
return blockInfraCli(ctx, GCLOUD_RE, "gcloud commands are blocked");
|
|
808
|
+
}
|
|
809
|
+
function blockAzCli(ctx) {
|
|
810
|
+
return blockInfraCli(ctx, AZ_CLI_RE, "az (Azure) CLI commands are blocked");
|
|
811
|
+
}
|
|
812
|
+
function blockHelm(ctx) {
|
|
813
|
+
return blockInfraCli(ctx, HELM_RE, "helm commands are blocked");
|
|
814
|
+
}
|
|
815
|
+
function blockGhPipeline(ctx) {
|
|
816
|
+
return blockInfraCli(ctx, GH_PIPELINE_RE, "gh pipeline-trigger commands are blocked");
|
|
817
|
+
}
|
|
771
818
|
async function warnRepeatedToolCalls(ctx) {
|
|
772
819
|
const THRESHOLD = 3;
|
|
773
820
|
const transcriptPath = ctx.session?.transcriptPath;
|
|
@@ -1027,22 +1074,7 @@ function requirePrBeforeStop(ctx) {
|
|
|
1027
1074
|
return allow(`PR #${pr.number} exists: ${pr.url}`);
|
|
1028
1075
|
}
|
|
1029
1076
|
if (pr.state === "MERGED") {
|
|
1030
|
-
|
|
1031
|
-
execFileSync("git", ["fetch", "origin", `+refs/heads/${baseBranch}:refs/remotes/origin/${baseBranch}`], {
|
|
1032
|
-
cwd,
|
|
1033
|
-
encoding: "utf8",
|
|
1034
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1035
|
-
timeout: 1e4
|
|
1036
|
-
});
|
|
1037
|
-
const freshAhead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
1038
|
-
if (!freshAhead) {
|
|
1039
|
-
return allow(`PR #${pr.number} was merged; branch is up to date with ${baseBranch}.`);
|
|
1040
|
-
}
|
|
1041
|
-
const freshDiff = execFileSync("git", ["diff", "--stat", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
1042
|
-
if (!freshDiff) {
|
|
1043
|
-
return allow(`PR #${pr.number} was merged; no file changes vs ${baseBranch}.`);
|
|
1044
|
-
}
|
|
1045
|
-
} catch {}
|
|
1077
|
+
return allow(`PR #${pr.number} was merged: ${pr.url}. ` + `Switch off this branch (e.g. 'git checkout ${baseBranch} && git pull') before stopping again.`);
|
|
1046
1078
|
}
|
|
1047
1079
|
return deny(`Pull request for branch "${branch}" is ${pr.state.toLowerCase()}. Run now: gh pr create`);
|
|
1048
1080
|
} catch {
|
|
@@ -1060,7 +1092,31 @@ function requireNoConflictsBeforeStop(ctx) {
|
|
|
1060
1092
|
if (branch === baseBranch) {
|
|
1061
1093
|
return allow(`On base branch "${baseBranch}", skipping conflict check.`);
|
|
1062
1094
|
}
|
|
1063
|
-
|
|
1095
|
+
try {
|
|
1096
|
+
execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
1097
|
+
} catch {
|
|
1098
|
+
return allow("gh CLI not installed, skipping conflict check.");
|
|
1099
|
+
}
|
|
1100
|
+
let prJson;
|
|
1101
|
+
try {
|
|
1102
|
+
prJson = execSync("gh pr view --json mergeable,number,url,state", {
|
|
1103
|
+
cwd,
|
|
1104
|
+
encoding: "utf8",
|
|
1105
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1106
|
+
timeout: 15000
|
|
1107
|
+
}).trim();
|
|
1108
|
+
} catch {
|
|
1109
|
+
return allow("No pull request found for branch, skipping conflict check.");
|
|
1110
|
+
}
|
|
1111
|
+
let pr;
|
|
1112
|
+
try {
|
|
1113
|
+
pr = JSON.parse(prJson);
|
|
1114
|
+
} catch {
|
|
1115
|
+
return allow("Could not parse gh pr view output, skipping conflict check.");
|
|
1116
|
+
}
|
|
1117
|
+
if (pr.state !== "OPEN") {
|
|
1118
|
+
return allow(`PR #${pr.number} is ${pr.state.toLowerCase()}; skipping conflict check.`);
|
|
1119
|
+
}
|
|
1064
1120
|
try {
|
|
1065
1121
|
execFileSync("git", ["rev-parse", "--verify", `origin/${baseBranch}`], {
|
|
1066
1122
|
cwd,
|
|
@@ -1069,9 +1125,7 @@ function requireNoConflictsBeforeStop(ctx) {
|
|
|
1069
1125
|
timeout: 3000
|
|
1070
1126
|
});
|
|
1071
1127
|
const ahead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
1072
|
-
if (
|
|
1073
|
-
localSkipped = true;
|
|
1074
|
-
} else {
|
|
1128
|
+
if (ahead) {
|
|
1075
1129
|
execFileSync("git", ["merge-tree", "--write-tree", "--name-only", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 });
|
|
1076
1130
|
}
|
|
1077
1131
|
} catch (err) {
|
|
@@ -1090,32 +1144,6 @@ function requireNoConflictsBeforeStop(ctx) {
|
|
|
1090
1144
|
const fileList = files.length ? files.join(", ") : "one or more files";
|
|
1091
1145
|
return deny(`Branch "${branch}" has merge conflicts with ${baseBranch} in: ${fileList}. ` + `Rebase or merge origin/${baseBranch} now and resolve the conflicts.`);
|
|
1092
1146
|
}
|
|
1093
|
-
localSkipped = true;
|
|
1094
|
-
}
|
|
1095
|
-
try {
|
|
1096
|
-
execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
1097
|
-
} catch {
|
|
1098
|
-
return allow(localSkipped ? "Local conflict check skipped and gh CLI not installed, skipping conflict check." : `Branch "${branch}" merges cleanly with ${baseBranch} locally (gh CLI not installed, PR mergeability not verified).`);
|
|
1099
|
-
}
|
|
1100
|
-
let prJson;
|
|
1101
|
-
try {
|
|
1102
|
-
prJson = execSync("gh pr view --json mergeable,number,url,state", {
|
|
1103
|
-
cwd,
|
|
1104
|
-
encoding: "utf8",
|
|
1105
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1106
|
-
timeout: 15000
|
|
1107
|
-
}).trim();
|
|
1108
|
-
} catch {
|
|
1109
|
-
return allow(localSkipped ? "No pull request found for branch, skipping conflict check." : `Branch "${branch}" merges cleanly with ${baseBranch} locally (no PR to verify against).`);
|
|
1110
|
-
}
|
|
1111
|
-
let pr;
|
|
1112
|
-
try {
|
|
1113
|
-
pr = JSON.parse(prJson);
|
|
1114
|
-
} catch {
|
|
1115
|
-
return allow("Could not parse gh pr view output, skipping PR mergeability check.");
|
|
1116
|
-
}
|
|
1117
|
-
if (pr.state !== "OPEN") {
|
|
1118
|
-
return allow(`PR #${pr.number} is ${pr.state.toLowerCase()}; skipping conflict check.`);
|
|
1119
1147
|
}
|
|
1120
1148
|
if (pr.mergeable === "CONFLICTING") {
|
|
1121
1149
|
return deny(`PR #${pr.number} has merge conflicts per GitHub (${pr.url}). ` + `Rebase or merge origin/${baseBranch} now and resolve the conflicts.`);
|
|
@@ -1178,7 +1206,7 @@ function registerBuiltinPolicies(enabledNames) {
|
|
|
1178
1206
|
}
|
|
1179
1207
|
}
|
|
1180
1208
|
}
|
|
1181
|
-
var SHELL_OPERATORS, SHELL_METACHAR_RE, JWT_RE, API_KEY_PATTERNS, CONNECTION_STRING_RE, PRIVATE_KEY_RE, BEARER_TOKEN_RE, SQL_TOOL_RE, DESTRUCTIVE_SQL_RE, DELETE_NO_WHERE_RE, SQL_WHERE_RE, SCHEMA_ALTER_RE, PUBLISH_CMD_RE, ENV_PRINTENV_RE, ECHO_ENV_RE, EXPORT_RE, PS_ENV_VAR_RE, PS_CHILDITEM_ENV_RE, DOTNET_GETENV_RE, CMD_ECHO_ENV_RE, ENV_FILE_PATH_RE, ENV_CMD_RE, SUDO_RE, PS_ELEVATION_RE, RUNAS_RE, CURL_PIPE_SH_RE, PS_WEB_PIPE_RE, FORCE_PUSH_RE, SECRET_FILE_RE, SECRET_FILE_ID_RSA_RE, SECRET_FILE_CREDENTIALS_RE, GIT_COMMIT_MERGE_RE, FAILPROOFAI_CLI_RE, FAILPROOFAI_UNINSTALL_RE, GIT_AMEND_RE, GIT_STASH_DROP_RE, GIT_ADD_ALL_RE, NPM_GLOBAL_RE, YARN_GLOBAL_RE, PNPM_GLOBAL_RE, BUN_GLOBAL_RE, CARGO_INSTALL_RE, PIP_SYSTEM_RE, PKG_MANAGER_DETECTORS, NOHUP_RE, SCREEN_DETACH_RE, TMUX_DETACH_RE, DISOWN_RE, BACKGROUND_AMPERSAND_RE, gitBranchCache, READ_LIKE_CMDS, TOOL_CALL_TRACKER_MAX_BYTES = 65536, SEGMENT_SPLIT_RE, BUILTIN_POLICIES;
|
|
1209
|
+
var SHELL_OPERATORS, SHELL_METACHAR_RE, JWT_RE, API_KEY_PATTERNS, CONNECTION_STRING_RE, PRIVATE_KEY_RE, BEARER_TOKEN_RE, SQL_TOOL_RE, DESTRUCTIVE_SQL_RE, DELETE_NO_WHERE_RE, SQL_WHERE_RE, SCHEMA_ALTER_RE, PUBLISH_CMD_RE, ENV_PRINTENV_RE, ECHO_ENV_RE, EXPORT_RE, PS_ENV_VAR_RE, PS_CHILDITEM_ENV_RE, DOTNET_GETENV_RE, CMD_ECHO_ENV_RE, ENV_FILE_PATH_RE, ENV_CMD_RE, SUDO_RE, PS_ELEVATION_RE, RUNAS_RE, CURL_PIPE_SH_RE, PS_WEB_PIPE_RE, FORCE_PUSH_RE, SECRET_FILE_RE, SECRET_FILE_ID_RSA_RE, SECRET_FILE_CREDENTIALS_RE, GIT_COMMIT_MERGE_RE, FAILPROOFAI_CLI_RE, FAILPROOFAI_UNINSTALL_RE, GIT_AMEND_RE, GIT_STASH_DROP_RE, GIT_ADD_ALL_RE, NPM_GLOBAL_RE, YARN_GLOBAL_RE, PNPM_GLOBAL_RE, BUN_GLOBAL_RE, CARGO_INSTALL_RE, PIP_SYSTEM_RE, PKG_MANAGER_DETECTORS, NOHUP_RE, SCREEN_DETACH_RE, TMUX_DETACH_RE, DISOWN_RE, BACKGROUND_AMPERSAND_RE, KUBECTL_RE, TERRAFORM_RE, AWS_CLI_RE, GCLOUD_RE, AZ_CLI_RE, HELM_RE, GH_PIPELINE_RE, gitBranchCache, READ_LIKE_CMDS, TOOL_CALL_TRACKER_MAX_BYTES = 65536, SEGMENT_SPLIT_RE, BUILTIN_POLICIES;
|
|
1182
1210
|
var init_builtin_policies = __esm(() => {
|
|
1183
1211
|
init_hook_logger();
|
|
1184
1212
|
SHELL_OPERATORS = new Set(["&&", "||", "|", ";"]);
|
|
@@ -1251,6 +1279,13 @@ var init_builtin_policies = __esm(() => {
|
|
|
1251
1279
|
TMUX_DETACH_RE = /\btmux\s+(?:new-session|new)\b[^|&;]*-d\b/;
|
|
1252
1280
|
DISOWN_RE = /\bdisown\b/;
|
|
1253
1281
|
BACKGROUND_AMPERSAND_RE = /(?<![&|])\s?&\s*(?:$|#|;)/;
|
|
1282
|
+
KUBECTL_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*kubectl(?:\s|$)/;
|
|
1283
|
+
TERRAFORM_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*(?:terraform|tofu)(?:\s|$)/;
|
|
1284
|
+
AWS_CLI_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*aws(?:\s|$)/;
|
|
1285
|
+
GCLOUD_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*gcloud(?:\s|$)/;
|
|
1286
|
+
AZ_CLI_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*az(?:\s|$)/;
|
|
1287
|
+
HELM_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*helm(?:\s|$)/;
|
|
1288
|
+
GH_PIPELINE_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*gh\s+(?:workflow\s+(?:run|enable|disable)|run\s+(?:rerun|cancel)|pr\s+merge|release\s+(?:create|delete)|cache\s+delete|secret\s+(?:set|delete))\b/;
|
|
1254
1289
|
gitBranchCache = new Map;
|
|
1255
1290
|
READ_LIKE_CMDS = /(?:^|;|&&|\|\||\|)\s*(?:ls|find|cat|head|tail|less|more|wc|file|stat|tree|du)\s/;
|
|
1256
1291
|
SEGMENT_SPLIT_RE = /\s*(?:&&|\|\||\||;)\s*/;
|
|
@@ -1379,6 +1414,111 @@ var init_builtin_policies = __esm(() => {
|
|
|
1379
1414
|
defaultEnabled: true,
|
|
1380
1415
|
category: "Dangerous Commands"
|
|
1381
1416
|
},
|
|
1417
|
+
{
|
|
1418
|
+
name: "block-kubectl",
|
|
1419
|
+
description: "Block kubectl commands (Kubernetes cluster mutations)",
|
|
1420
|
+
fn: blockKubectl,
|
|
1421
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1422
|
+
defaultEnabled: false,
|
|
1423
|
+
category: "Infra Commands",
|
|
1424
|
+
params: {
|
|
1425
|
+
allowPatterns: {
|
|
1426
|
+
type: "string[]",
|
|
1427
|
+
description: "kubectl command patterns to allow, matched token-by-token (e.g. 'kubectl get *', 'kubectl describe *')",
|
|
1428
|
+
default: []
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
name: "block-terraform",
|
|
1434
|
+
description: "Block terraform and tofu (OpenTofu) commands",
|
|
1435
|
+
fn: blockTerraform,
|
|
1436
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1437
|
+
defaultEnabled: false,
|
|
1438
|
+
category: "Infra Commands",
|
|
1439
|
+
params: {
|
|
1440
|
+
allowPatterns: {
|
|
1441
|
+
type: "string[]",
|
|
1442
|
+
description: "terraform/tofu command patterns to allow (e.g. 'terraform plan', 'terraform validate')",
|
|
1443
|
+
default: []
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
name: "block-aws-cli",
|
|
1449
|
+
description: "Block aws CLI commands",
|
|
1450
|
+
fn: blockAwsCli,
|
|
1451
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1452
|
+
defaultEnabled: false,
|
|
1453
|
+
category: "Infra Commands",
|
|
1454
|
+
params: {
|
|
1455
|
+
allowPatterns: {
|
|
1456
|
+
type: "string[]",
|
|
1457
|
+
description: "aws CLI command patterns to allow (e.g. 'aws s3 ls *', 'aws sts get-caller-identity')",
|
|
1458
|
+
default: []
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
name: "block-gcloud",
|
|
1464
|
+
description: "Block gcloud (Google Cloud) CLI commands",
|
|
1465
|
+
fn: blockGcloud,
|
|
1466
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1467
|
+
defaultEnabled: false,
|
|
1468
|
+
category: "Infra Commands",
|
|
1469
|
+
params: {
|
|
1470
|
+
allowPatterns: {
|
|
1471
|
+
type: "string[]",
|
|
1472
|
+
description: "gcloud command patterns to allow (e.g. 'gcloud auth list', 'gcloud config list')",
|
|
1473
|
+
default: []
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
name: "block-az-cli",
|
|
1479
|
+
description: "Block az (Azure) CLI commands",
|
|
1480
|
+
fn: blockAzCli,
|
|
1481
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1482
|
+
defaultEnabled: false,
|
|
1483
|
+
category: "Infra Commands",
|
|
1484
|
+
params: {
|
|
1485
|
+
allowPatterns: {
|
|
1486
|
+
type: "string[]",
|
|
1487
|
+
description: "az CLI command patterns to allow (e.g. 'az account show', 'az group list')",
|
|
1488
|
+
default: []
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
name: "block-helm",
|
|
1494
|
+
description: "Block helm commands",
|
|
1495
|
+
fn: blockHelm,
|
|
1496
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1497
|
+
defaultEnabled: false,
|
|
1498
|
+
category: "Infra Commands",
|
|
1499
|
+
params: {
|
|
1500
|
+
allowPatterns: {
|
|
1501
|
+
type: "string[]",
|
|
1502
|
+
description: "helm command patterns to allow (e.g. 'helm list', 'helm status *')",
|
|
1503
|
+
default: []
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
},
|
|
1507
|
+
{
|
|
1508
|
+
name: "block-gh-pipeline",
|
|
1509
|
+
description: "Block gh CLI pipeline-trigger subcommands (workflow run, run rerun/cancel, pr merge, release create/delete, cache delete, secret set/delete)",
|
|
1510
|
+
fn: blockGhPipeline,
|
|
1511
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1512
|
+
defaultEnabled: false,
|
|
1513
|
+
category: "Infra Commands",
|
|
1514
|
+
params: {
|
|
1515
|
+
allowPatterns: {
|
|
1516
|
+
type: "string[]",
|
|
1517
|
+
description: "gh pipeline command patterns to allow (e.g. specific scripted invocations); read-only gh subcommands like 'gh pr view' and 'gh run list' are not matched by this policy",
|
|
1518
|
+
default: []
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
},
|
|
1382
1522
|
{
|
|
1383
1523
|
name: "block-secrets-write",
|
|
1384
1524
|
description: "Block writing secret key files",
|
|
@@ -1981,8 +2121,9 @@ async function loadCustomHooks(customPoliciesPath, opts) {
|
|
|
1981
2121
|
async function loadAllCustomHooks(customPoliciesPath, opts) {
|
|
1982
2122
|
clearCustomHooks();
|
|
1983
2123
|
const conventionSources = [];
|
|
2124
|
+
const projectRoot = findProjectConfigDir(opts?.sessionCwd ?? process.cwd());
|
|
1984
2125
|
if (customPoliciesPath) {
|
|
1985
|
-
const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(
|
|
2126
|
+
const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(projectRoot, customPoliciesPath);
|
|
1986
2127
|
if (existsSync3(absPath)) {
|
|
1987
2128
|
await loadSingleFile(absPath);
|
|
1988
2129
|
} else {
|
|
@@ -1990,7 +2131,7 @@ async function loadAllCustomHooks(customPoliciesPath, opts) {
|
|
|
1990
2131
|
}
|
|
1991
2132
|
}
|
|
1992
2133
|
const hooksBeforeConvention = getCustomHooks().length;
|
|
1993
|
-
const projectDir = resolve4(
|
|
2134
|
+
const projectDir = resolve4(projectRoot, ".failproofai", "policies");
|
|
1994
2135
|
const projectFiles = discoverPolicyFiles(projectDir);
|
|
1995
2136
|
for (const file of projectFiles) {
|
|
1996
2137
|
const hooksBefore = getCustomHooks().length;
|
|
@@ -2045,6 +2186,7 @@ var init_custom_hooks_loader = __esm(() => {
|
|
|
2045
2186
|
init_hook_logger();
|
|
2046
2187
|
init_custom_hooks_registry();
|
|
2047
2188
|
init_loader_utils();
|
|
2189
|
+
init_hooks_config();
|
|
2048
2190
|
CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
|
|
2049
2191
|
});
|
|
2050
2192
|
|
|
@@ -2057,7 +2199,7 @@ import {
|
|
|
2057
2199
|
readdirSync as readdirSync2,
|
|
2058
2200
|
mkdirSync as mkdirSync3,
|
|
2059
2201
|
existsSync as existsSync4,
|
|
2060
|
-
statSync as
|
|
2202
|
+
statSync as statSync3,
|
|
2061
2203
|
unlinkSync
|
|
2062
2204
|
} from "node:fs";
|
|
2063
2205
|
import { join as join3 } from "node:path";
|
|
@@ -2079,7 +2221,7 @@ function acquireLock() {
|
|
|
2079
2221
|
if (e.code !== "EEXIST")
|
|
2080
2222
|
return;
|
|
2081
2223
|
try {
|
|
2082
|
-
const s =
|
|
2224
|
+
const s = statSync3(lockPath);
|
|
2083
2225
|
if (Date.now() - s.mtimeMs > LOCK_STALE_MS) {
|
|
2084
2226
|
writeFileSync2(lockPath, String(process.pid), "utf-8");
|
|
2085
2227
|
return;
|
|
@@ -2171,7 +2313,7 @@ var init_hook_activity_store = __esm(() => {
|
|
|
2171
2313
|
});
|
|
2172
2314
|
|
|
2173
2315
|
// package.json
|
|
2174
|
-
var version2 = "0.0.
|
|
2316
|
+
var version2 = "0.0.8";
|
|
2175
2317
|
var init_package = () => {};
|
|
2176
2318
|
|
|
2177
2319
|
// src/posthog-key.ts
|
|
@@ -2349,7 +2491,7 @@ import {
|
|
|
2349
2491
|
mkdirSync as mkdirSync5,
|
|
2350
2492
|
existsSync as existsSync6,
|
|
2351
2493
|
readFileSync as readFileSync4,
|
|
2352
|
-
statSync as
|
|
2494
|
+
statSync as statSync4,
|
|
2353
2495
|
renameSync as renameSync4,
|
|
2354
2496
|
unlinkSync as unlinkSync3,
|
|
2355
2497
|
readdirSync as readdirSync3,
|
|
@@ -2395,7 +2537,7 @@ function appendToServerQueue(entry) {
|
|
|
2395
2537
|
return;
|
|
2396
2538
|
ensureDir2();
|
|
2397
2539
|
try {
|
|
2398
|
-
if (existsSync6(PENDING_FILE) &&
|
|
2540
|
+
if (existsSync6(PENDING_FILE) && statSync4(PENDING_FILE).size > MAX_QUEUE_BYTES) {
|
|
2399
2541
|
return;
|
|
2400
2542
|
}
|
|
2401
2543
|
} catch {}
|
|
@@ -2408,7 +2550,7 @@ function appendToServerQueue(entry) {
|
|
|
2408
2550
|
}
|
|
2409
2551
|
function queueSizeBytes() {
|
|
2410
2552
|
try {
|
|
2411
|
-
return
|
|
2553
|
+
return statSync4(PENDING_FILE).size;
|
|
2412
2554
|
} catch {
|
|
2413
2555
|
return 0;
|
|
2414
2556
|
}
|
|
@@ -2417,7 +2559,7 @@ function claimPendingBatch() {
|
|
|
2417
2559
|
if (!existsSync6(PENDING_FILE))
|
|
2418
2560
|
return null;
|
|
2419
2561
|
try {
|
|
2420
|
-
const size =
|
|
2562
|
+
const size = statSync4(PENDING_FILE).size;
|
|
2421
2563
|
if (size === 0)
|
|
2422
2564
|
return null;
|
|
2423
2565
|
} catch {
|
|
@@ -4484,7 +4626,7 @@ import { realpathSync as realpathSync2 } from "fs";
|
|
|
4484
4626
|
import { dirname as dirname7, resolve as resolve8 } from "path";
|
|
4485
4627
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4486
4628
|
// package.json
|
|
4487
|
-
var version = "0.0.
|
|
4629
|
+
var version = "0.0.8";
|
|
4488
4630
|
|
|
4489
4631
|
// bin/failproofai.mjs
|
|
4490
4632
|
if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs"
|