failproofai 0.0.6-beta.5 → 0.0.7
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]__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]__0okos0k._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0xezw2w._.js → [root-of-the-server]__0tjjyb9._.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]__0u49i20._.js → [root-of-the-server]__0zn7uo6._.js} +2 -2
- 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/{0ms9sfkvw5h5i.js → 01l2mh88iy.ga.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0q~7ymasjp~n7.js → 0388wpenm9-a4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03xexm0jftw.x.js → 0a0lh_a4f_xs-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0_-7qmtiagi-~.js → 0f_9854du76y2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0af6vx3nh6v77.js → 0j2o20pqkib~d.js} +1 -1
- package/.next/standalone/.next/static/chunks/{11vga8q3t~527.js → 0kkzzoo.s-t3p.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0on5naeqa0w.x.js → 0x0o8~u4jsatb.js} +2 -2
- package/.next/standalone/.next/static/chunks/{0hrob6m-1.n1d.js → 12wu.28cbx4dl.js} +1 -1
- package/.next/standalone/failproofai-hq.gif +0 -0
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/README.md +6 -2
- package/dist/cli.mjs +227 -52
- package/package.json +1 -1
- package/src/hooks/builtin-policies.ts +201 -41
- package/src/hooks/custom-hooks-loader.ts +6 -3
- package/src/hooks/hooks-config.ts +31 -5
- package/src/hooks/policy-evaluator.ts +31 -7
- package/src/hooks/policy-registry.ts +19 -2
- /package/.next/standalone/.next/static/{Q-y1SELeezrTyQx0E2uqg → 9FNjQiktocMN-qDiGqDL5}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{Q-y1SELeezrTyQx0E2uqg → 9FNjQiktocMN-qDiGqDL5}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{Q-y1SELeezrTyQx0E2uqg → 9FNjQiktocMN-qDiGqDL5}/_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) {
|
|
@@ -198,6 +213,9 @@ function instruct(reason) {
|
|
|
198
213
|
}
|
|
199
214
|
|
|
200
215
|
// src/hooks/policy-registry.ts
|
|
216
|
+
function normalizePolicyName(name) {
|
|
217
|
+
return name.includes("/") ? name : `${DEFAULT_POLICY_NAMESPACE}/${name}`;
|
|
218
|
+
}
|
|
201
219
|
function getIndexCache() {
|
|
202
220
|
return globalThis[INDEX_CACHE_KEY];
|
|
203
221
|
}
|
|
@@ -212,9 +230,10 @@ function getRegistry() {
|
|
|
212
230
|
return g[REGISTRY_KEY];
|
|
213
231
|
}
|
|
214
232
|
function registerPolicy(name, description, fn, match, priority = 0) {
|
|
233
|
+
const canonical = normalizePolicyName(name);
|
|
215
234
|
const registry = getRegistry();
|
|
216
|
-
const idx = registry.findIndex((p) => p.name ===
|
|
217
|
-
const entry = { name, description, fn, match, priority };
|
|
235
|
+
const idx = registry.findIndex((p) => p.name === canonical);
|
|
236
|
+
const entry = { name: canonical, description, fn, match, priority };
|
|
218
237
|
if (idx >= 0) {
|
|
219
238
|
registry[idx] = entry;
|
|
220
239
|
} else {
|
|
@@ -251,7 +270,7 @@ function clearPolicies() {
|
|
|
251
270
|
g[REGISTRY_KEY] = [];
|
|
252
271
|
setIndexCache(null);
|
|
253
272
|
}
|
|
254
|
-
var REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__", INDEX_CACHE_KEY = "__FAILPROOFAI_POLICY_INDEX_CACHE__";
|
|
273
|
+
var REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__", INDEX_CACHE_KEY = "__FAILPROOFAI_POLICY_INDEX_CACHE__", DEFAULT_POLICY_NAMESPACE = "exospherehost";
|
|
255
274
|
|
|
256
275
|
// src/hooks/builtin-policies.ts
|
|
257
276
|
import { resolve as resolve2, join as join2 } from "node:path";
|
|
@@ -764,6 +783,38 @@ function blockFailproofaiCommands(ctx) {
|
|
|
764
783
|
}
|
|
765
784
|
return allow();
|
|
766
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
|
+
}
|
|
767
818
|
async function warnRepeatedToolCalls(ctx) {
|
|
768
819
|
const THRESHOLD = 3;
|
|
769
820
|
const transcriptPath = ctx.session?.transcriptPath;
|
|
@@ -1056,7 +1107,31 @@ function requireNoConflictsBeforeStop(ctx) {
|
|
|
1056
1107
|
if (branch === baseBranch) {
|
|
1057
1108
|
return allow(`On base branch "${baseBranch}", skipping conflict check.`);
|
|
1058
1109
|
}
|
|
1059
|
-
|
|
1110
|
+
try {
|
|
1111
|
+
execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
1112
|
+
} catch {
|
|
1113
|
+
return allow("gh CLI not installed, skipping conflict check.");
|
|
1114
|
+
}
|
|
1115
|
+
let prJson;
|
|
1116
|
+
try {
|
|
1117
|
+
prJson = execSync("gh pr view --json mergeable,number,url,state", {
|
|
1118
|
+
cwd,
|
|
1119
|
+
encoding: "utf8",
|
|
1120
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1121
|
+
timeout: 15000
|
|
1122
|
+
}).trim();
|
|
1123
|
+
} catch {
|
|
1124
|
+
return allow("No pull request found for branch, skipping conflict check.");
|
|
1125
|
+
}
|
|
1126
|
+
let pr;
|
|
1127
|
+
try {
|
|
1128
|
+
pr = JSON.parse(prJson);
|
|
1129
|
+
} catch {
|
|
1130
|
+
return allow("Could not parse gh pr view output, skipping conflict check.");
|
|
1131
|
+
}
|
|
1132
|
+
if (pr.state !== "OPEN") {
|
|
1133
|
+
return allow(`PR #${pr.number} is ${pr.state.toLowerCase()}; skipping conflict check.`);
|
|
1134
|
+
}
|
|
1060
1135
|
try {
|
|
1061
1136
|
execFileSync("git", ["rev-parse", "--verify", `origin/${baseBranch}`], {
|
|
1062
1137
|
cwd,
|
|
@@ -1065,9 +1140,7 @@ function requireNoConflictsBeforeStop(ctx) {
|
|
|
1065
1140
|
timeout: 3000
|
|
1066
1141
|
});
|
|
1067
1142
|
const ahead = execFileSync("git", ["log", `origin/${baseBranch}..HEAD`, "--oneline"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
1068
|
-
if (
|
|
1069
|
-
localSkipped = true;
|
|
1070
|
-
} else {
|
|
1143
|
+
if (ahead) {
|
|
1071
1144
|
execFileSync("git", ["merge-tree", "--write-tree", "--name-only", `origin/${baseBranch}`, "HEAD"], { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 });
|
|
1072
1145
|
}
|
|
1073
1146
|
} catch (err) {
|
|
@@ -1086,29 +1159,6 @@ function requireNoConflictsBeforeStop(ctx) {
|
|
|
1086
1159
|
const fileList = files.length ? files.join(", ") : "one or more files";
|
|
1087
1160
|
return deny(`Branch "${branch}" has merge conflicts with ${baseBranch} in: ${fileList}. ` + `Rebase or merge origin/${baseBranch} now and resolve the conflicts.`);
|
|
1088
1161
|
}
|
|
1089
|
-
localSkipped = true;
|
|
1090
|
-
}
|
|
1091
|
-
try {
|
|
1092
|
-
execSync("gh --version", { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 3000 });
|
|
1093
|
-
} catch {
|
|
1094
|
-
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).`);
|
|
1095
|
-
}
|
|
1096
|
-
let prJson;
|
|
1097
|
-
try {
|
|
1098
|
-
prJson = execSync("gh pr view --json mergeable,number,url", {
|
|
1099
|
-
cwd,
|
|
1100
|
-
encoding: "utf8",
|
|
1101
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1102
|
-
timeout: 15000
|
|
1103
|
-
}).trim();
|
|
1104
|
-
} catch {
|
|
1105
|
-
return allow(localSkipped ? "No pull request found for branch, skipping conflict check." : `Branch "${branch}" merges cleanly with ${baseBranch} locally (no PR to verify against).`);
|
|
1106
|
-
}
|
|
1107
|
-
let pr;
|
|
1108
|
-
try {
|
|
1109
|
-
pr = JSON.parse(prJson);
|
|
1110
|
-
} catch {
|
|
1111
|
-
return allow("Could not parse gh pr view output, skipping PR mergeability check.");
|
|
1112
1162
|
}
|
|
1113
1163
|
if (pr.mergeable === "CONFLICTING") {
|
|
1114
1164
|
return deny(`PR #${pr.number} has merge conflicts per GitHub (${pr.url}). ` + `Rebase or merge origin/${baseBranch} now and resolve the conflicts.`);
|
|
@@ -1164,14 +1214,14 @@ function requireCiGreenBeforeStop(ctx) {
|
|
|
1164
1214
|
}
|
|
1165
1215
|
}
|
|
1166
1216
|
function registerBuiltinPolicies(enabledNames) {
|
|
1167
|
-
const enabledSet = new Set(enabledNames);
|
|
1217
|
+
const enabledSet = new Set(enabledNames.map(normalizePolicyName));
|
|
1168
1218
|
for (const policy of BUILTIN_POLICIES) {
|
|
1169
|
-
if (enabledSet.has(policy.name)) {
|
|
1219
|
+
if (enabledSet.has(normalizePolicyName(policy.name))) {
|
|
1170
1220
|
registerPolicy(policy.name, policy.description, policy.fn, policy.match);
|
|
1171
1221
|
}
|
|
1172
1222
|
}
|
|
1173
1223
|
}
|
|
1174
|
-
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;
|
|
1224
|
+
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;
|
|
1175
1225
|
var init_builtin_policies = __esm(() => {
|
|
1176
1226
|
init_hook_logger();
|
|
1177
1227
|
SHELL_OPERATORS = new Set(["&&", "||", "|", ";"]);
|
|
@@ -1244,6 +1294,13 @@ var init_builtin_policies = __esm(() => {
|
|
|
1244
1294
|
TMUX_DETACH_RE = /\btmux\s+(?:new-session|new)\b[^|&;]*-d\b/;
|
|
1245
1295
|
DISOWN_RE = /\bdisown\b/;
|
|
1246
1296
|
BACKGROUND_AMPERSAND_RE = /(?<![&|])\s?&\s*(?:$|#|;)/;
|
|
1297
|
+
KUBECTL_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*kubectl(?:\s|$)/;
|
|
1298
|
+
TERRAFORM_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*(?:terraform|tofu)(?:\s|$)/;
|
|
1299
|
+
AWS_CLI_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*aws(?:\s|$)/;
|
|
1300
|
+
GCLOUD_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*gcloud(?:\s|$)/;
|
|
1301
|
+
AZ_CLI_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*az(?:\s|$)/;
|
|
1302
|
+
HELM_RE = /(?:^|[;\n]|&&|\|\|?|&)\s*helm(?:\s|$)/;
|
|
1303
|
+
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/;
|
|
1247
1304
|
gitBranchCache = new Map;
|
|
1248
1305
|
READ_LIKE_CMDS = /(?:^|;|&&|\|\||\|)\s*(?:ls|find|cat|head|tail|less|more|wc|file|stat|tree|du)\s/;
|
|
1249
1306
|
SEGMENT_SPLIT_RE = /\s*(?:&&|\|\||\||;)\s*/;
|
|
@@ -1372,6 +1429,111 @@ var init_builtin_policies = __esm(() => {
|
|
|
1372
1429
|
defaultEnabled: true,
|
|
1373
1430
|
category: "Dangerous Commands"
|
|
1374
1431
|
},
|
|
1432
|
+
{
|
|
1433
|
+
name: "block-kubectl",
|
|
1434
|
+
description: "Block kubectl commands (Kubernetes cluster mutations)",
|
|
1435
|
+
fn: blockKubectl,
|
|
1436
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1437
|
+
defaultEnabled: false,
|
|
1438
|
+
category: "Infra Commands",
|
|
1439
|
+
params: {
|
|
1440
|
+
allowPatterns: {
|
|
1441
|
+
type: "string[]",
|
|
1442
|
+
description: "kubectl command patterns to allow, matched token-by-token (e.g. 'kubectl get *', 'kubectl describe *')",
|
|
1443
|
+
default: []
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
name: "block-terraform",
|
|
1449
|
+
description: "Block terraform and tofu (OpenTofu) commands",
|
|
1450
|
+
fn: blockTerraform,
|
|
1451
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1452
|
+
defaultEnabled: false,
|
|
1453
|
+
category: "Infra Commands",
|
|
1454
|
+
params: {
|
|
1455
|
+
allowPatterns: {
|
|
1456
|
+
type: "string[]",
|
|
1457
|
+
description: "terraform/tofu command patterns to allow (e.g. 'terraform plan', 'terraform validate')",
|
|
1458
|
+
default: []
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
name: "block-aws-cli",
|
|
1464
|
+
description: "Block aws CLI commands",
|
|
1465
|
+
fn: blockAwsCli,
|
|
1466
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1467
|
+
defaultEnabled: false,
|
|
1468
|
+
category: "Infra Commands",
|
|
1469
|
+
params: {
|
|
1470
|
+
allowPatterns: {
|
|
1471
|
+
type: "string[]",
|
|
1472
|
+
description: "aws CLI command patterns to allow (e.g. 'aws s3 ls *', 'aws sts get-caller-identity')",
|
|
1473
|
+
default: []
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
name: "block-gcloud",
|
|
1479
|
+
description: "Block gcloud (Google Cloud) CLI commands",
|
|
1480
|
+
fn: blockGcloud,
|
|
1481
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1482
|
+
defaultEnabled: false,
|
|
1483
|
+
category: "Infra Commands",
|
|
1484
|
+
params: {
|
|
1485
|
+
allowPatterns: {
|
|
1486
|
+
type: "string[]",
|
|
1487
|
+
description: "gcloud command patterns to allow (e.g. 'gcloud auth list', 'gcloud config list')",
|
|
1488
|
+
default: []
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
{
|
|
1493
|
+
name: "block-az-cli",
|
|
1494
|
+
description: "Block az (Azure) CLI commands",
|
|
1495
|
+
fn: blockAzCli,
|
|
1496
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1497
|
+
defaultEnabled: false,
|
|
1498
|
+
category: "Infra Commands",
|
|
1499
|
+
params: {
|
|
1500
|
+
allowPatterns: {
|
|
1501
|
+
type: "string[]",
|
|
1502
|
+
description: "az CLI command patterns to allow (e.g. 'az account show', 'az group list')",
|
|
1503
|
+
default: []
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
},
|
|
1507
|
+
{
|
|
1508
|
+
name: "block-helm",
|
|
1509
|
+
description: "Block helm commands",
|
|
1510
|
+
fn: blockHelm,
|
|
1511
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1512
|
+
defaultEnabled: false,
|
|
1513
|
+
category: "Infra Commands",
|
|
1514
|
+
params: {
|
|
1515
|
+
allowPatterns: {
|
|
1516
|
+
type: "string[]",
|
|
1517
|
+
description: "helm command patterns to allow (e.g. 'helm list', 'helm status *')",
|
|
1518
|
+
default: []
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
name: "block-gh-pipeline",
|
|
1524
|
+
description: "Block gh CLI pipeline-trigger subcommands (workflow run, run rerun/cancel, pr merge, release create/delete, cache delete, secret set/delete)",
|
|
1525
|
+
fn: blockGhPipeline,
|
|
1526
|
+
match: { events: ["PreToolUse"], toolNames: ["Bash"] },
|
|
1527
|
+
defaultEnabled: false,
|
|
1528
|
+
category: "Infra Commands",
|
|
1529
|
+
params: {
|
|
1530
|
+
allowPatterns: {
|
|
1531
|
+
type: "string[]",
|
|
1532
|
+
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",
|
|
1533
|
+
default: []
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
},
|
|
1375
1537
|
{
|
|
1376
1538
|
name: "block-secrets-write",
|
|
1377
1539
|
description: "Block writing secret key files",
|
|
@@ -1611,6 +1773,17 @@ function appendHint(baseReason, hint) {
|
|
|
1611
1773
|
return normalizedHint;
|
|
1612
1774
|
return `${base}. ${normalizedHint}`;
|
|
1613
1775
|
}
|
|
1776
|
+
function getConfigParamsFor(config, canonicalName) {
|
|
1777
|
+
if (!config?.policyParams)
|
|
1778
|
+
return;
|
|
1779
|
+
const canonicalParams = config.policyParams[canonicalName];
|
|
1780
|
+
if (canonicalParams)
|
|
1781
|
+
return canonicalParams;
|
|
1782
|
+
const defaultPrefix = `${DEFAULT_POLICY_NAMESPACE}/`;
|
|
1783
|
+
if (!canonicalName.startsWith(defaultPrefix))
|
|
1784
|
+
return;
|
|
1785
|
+
return config.policyParams[canonicalName.slice(defaultPrefix.length)];
|
|
1786
|
+
}
|
|
1614
1787
|
async function evaluatePolicies(eventType, payload, session, config) {
|
|
1615
1788
|
const toolName = payload.tool_name;
|
|
1616
1789
|
const toolInput = payload.tool_input;
|
|
@@ -1632,7 +1805,7 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1632
1805
|
const schema = POLICY_PARAMS_MAP.get(policy.name);
|
|
1633
1806
|
let ctx;
|
|
1634
1807
|
if (schema) {
|
|
1635
|
-
const userParams = config
|
|
1808
|
+
const userParams = getConfigParamsFor(config, policy.name) ?? {};
|
|
1636
1809
|
const resolvedParams = {};
|
|
1637
1810
|
for (const [key, spec] of Object.entries(schema)) {
|
|
1638
1811
|
resolvedParams[key] = key in userParams ? userParams[key] : spec.default;
|
|
@@ -1649,7 +1822,7 @@ async function evaluatePolicies(eventType, payload, session, config) {
|
|
|
1649
1822
|
continue;
|
|
1650
1823
|
}
|
|
1651
1824
|
if (result.decision === "deny") {
|
|
1652
|
-
const reason = appendHint(result.reason ?? `Blocked by policy: ${policy.name}`, config
|
|
1825
|
+
const reason = appendHint(result.reason ?? `Blocked by policy: ${policy.name}`, getConfigParamsFor(config, policy.name)?.hint);
|
|
1653
1826
|
hookLogInfo(`deny by "${policy.name}": ${reason}`);
|
|
1654
1827
|
const displayTool = ctx.toolName ?? "unknown tool";
|
|
1655
1828
|
if (eventType === "PreToolUse") {
|
|
@@ -1707,7 +1880,7 @@ You MUST complete the above action NOW. Do NOT ask the user for confirmation —
|
|
|
1707
1880
|
};
|
|
1708
1881
|
}
|
|
1709
1882
|
if (result.decision === "instruct") {
|
|
1710
|
-
const reason = appendHint(result.reason ?? `Instruction from policy: ${policy.name}`, config
|
|
1883
|
+
const reason = appendHint(result.reason ?? `Instruction from policy: ${policy.name}`, getConfigParamsFor(config, policy.name)?.hint);
|
|
1711
1884
|
instructEntries.push({ policyName: policy.name, reason });
|
|
1712
1885
|
hookLogInfo(`instruct by "${policy.name}": ${reason}`);
|
|
1713
1886
|
}
|
|
@@ -1766,7 +1939,7 @@ var POLICY_PARAMS_MAP;
|
|
|
1766
1939
|
var init_policy_evaluator = __esm(() => {
|
|
1767
1940
|
init_builtin_policies();
|
|
1768
1941
|
init_hook_logger();
|
|
1769
|
-
POLICY_PARAMS_MAP = new Map(BUILTIN_POLICIES.filter((p) => p.params).map((p) => [p.name, p.params]));
|
|
1942
|
+
POLICY_PARAMS_MAP = new Map(BUILTIN_POLICIES.filter((p) => p.params).map((p) => [normalizePolicyName(p.name), p.params]));
|
|
1770
1943
|
});
|
|
1771
1944
|
|
|
1772
1945
|
// src/hooks/custom-hooks-registry.ts
|
|
@@ -1963,8 +2136,9 @@ async function loadCustomHooks(customPoliciesPath, opts) {
|
|
|
1963
2136
|
async function loadAllCustomHooks(customPoliciesPath, opts) {
|
|
1964
2137
|
clearCustomHooks();
|
|
1965
2138
|
const conventionSources = [];
|
|
2139
|
+
const projectRoot = findProjectConfigDir(opts?.sessionCwd ?? process.cwd());
|
|
1966
2140
|
if (customPoliciesPath) {
|
|
1967
|
-
const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(
|
|
2141
|
+
const absPath = isAbsolute(customPoliciesPath) ? customPoliciesPath : resolve4(projectRoot, customPoliciesPath);
|
|
1968
2142
|
if (existsSync3(absPath)) {
|
|
1969
2143
|
await loadSingleFile(absPath);
|
|
1970
2144
|
} else {
|
|
@@ -1972,7 +2146,7 @@ async function loadAllCustomHooks(customPoliciesPath, opts) {
|
|
|
1972
2146
|
}
|
|
1973
2147
|
}
|
|
1974
2148
|
const hooksBeforeConvention = getCustomHooks().length;
|
|
1975
|
-
const projectDir = resolve4(
|
|
2149
|
+
const projectDir = resolve4(projectRoot, ".failproofai", "policies");
|
|
1976
2150
|
const projectFiles = discoverPolicyFiles(projectDir);
|
|
1977
2151
|
for (const file of projectFiles) {
|
|
1978
2152
|
const hooksBefore = getCustomHooks().length;
|
|
@@ -2027,6 +2201,7 @@ var init_custom_hooks_loader = __esm(() => {
|
|
|
2027
2201
|
init_hook_logger();
|
|
2028
2202
|
init_custom_hooks_registry();
|
|
2029
2203
|
init_loader_utils();
|
|
2204
|
+
init_hooks_config();
|
|
2030
2205
|
CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
|
|
2031
2206
|
});
|
|
2032
2207
|
|
|
@@ -2039,7 +2214,7 @@ import {
|
|
|
2039
2214
|
readdirSync as readdirSync2,
|
|
2040
2215
|
mkdirSync as mkdirSync3,
|
|
2041
2216
|
existsSync as existsSync4,
|
|
2042
|
-
statSync as
|
|
2217
|
+
statSync as statSync3,
|
|
2043
2218
|
unlinkSync
|
|
2044
2219
|
} from "node:fs";
|
|
2045
2220
|
import { join as join3 } from "node:path";
|
|
@@ -2061,7 +2236,7 @@ function acquireLock() {
|
|
|
2061
2236
|
if (e.code !== "EEXIST")
|
|
2062
2237
|
return;
|
|
2063
2238
|
try {
|
|
2064
|
-
const s =
|
|
2239
|
+
const s = statSync3(lockPath);
|
|
2065
2240
|
if (Date.now() - s.mtimeMs > LOCK_STALE_MS) {
|
|
2066
2241
|
writeFileSync2(lockPath, String(process.pid), "utf-8");
|
|
2067
2242
|
return;
|
|
@@ -2153,7 +2328,7 @@ var init_hook_activity_store = __esm(() => {
|
|
|
2153
2328
|
});
|
|
2154
2329
|
|
|
2155
2330
|
// package.json
|
|
2156
|
-
var version2 = "0.0.
|
|
2331
|
+
var version2 = "0.0.7";
|
|
2157
2332
|
var init_package = () => {};
|
|
2158
2333
|
|
|
2159
2334
|
// src/posthog-key.ts
|
|
@@ -2331,7 +2506,7 @@ import {
|
|
|
2331
2506
|
mkdirSync as mkdirSync5,
|
|
2332
2507
|
existsSync as existsSync6,
|
|
2333
2508
|
readFileSync as readFileSync4,
|
|
2334
|
-
statSync as
|
|
2509
|
+
statSync as statSync4,
|
|
2335
2510
|
renameSync as renameSync4,
|
|
2336
2511
|
unlinkSync as unlinkSync3,
|
|
2337
2512
|
readdirSync as readdirSync3,
|
|
@@ -2377,7 +2552,7 @@ function appendToServerQueue(entry) {
|
|
|
2377
2552
|
return;
|
|
2378
2553
|
ensureDir2();
|
|
2379
2554
|
try {
|
|
2380
|
-
if (existsSync6(PENDING_FILE) &&
|
|
2555
|
+
if (existsSync6(PENDING_FILE) && statSync4(PENDING_FILE).size > MAX_QUEUE_BYTES) {
|
|
2381
2556
|
return;
|
|
2382
2557
|
}
|
|
2383
2558
|
} catch {}
|
|
@@ -2390,7 +2565,7 @@ function appendToServerQueue(entry) {
|
|
|
2390
2565
|
}
|
|
2391
2566
|
function queueSizeBytes() {
|
|
2392
2567
|
try {
|
|
2393
|
-
return
|
|
2568
|
+
return statSync4(PENDING_FILE).size;
|
|
2394
2569
|
} catch {
|
|
2395
2570
|
return 0;
|
|
2396
2571
|
}
|
|
@@ -2399,7 +2574,7 @@ function claimPendingBatch() {
|
|
|
2399
2574
|
if (!existsSync6(PENDING_FILE))
|
|
2400
2575
|
return null;
|
|
2401
2576
|
try {
|
|
2402
|
-
const size =
|
|
2577
|
+
const size = statSync4(PENDING_FILE).size;
|
|
2403
2578
|
if (size === 0)
|
|
2404
2579
|
return null;
|
|
2405
2580
|
} catch {
|
|
@@ -4466,7 +4641,7 @@ import { realpathSync as realpathSync2 } from "fs";
|
|
|
4466
4641
|
import { dirname as dirname7, resolve as resolve8 } from "path";
|
|
4467
4642
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4468
4643
|
// package.json
|
|
4469
|
-
var version = "0.0.
|
|
4644
|
+
var version = "0.0.7";
|
|
4470
4645
|
|
|
4471
4646
|
// bin/failproofai.mjs
|
|
4472
4647
|
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.7",
|
|
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"
|