failproofai 0.0.6-beta.5 → 0.0.6
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]__0u49i20._.js → [root-of-the-server]__0ow37ro._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0xezw2w._.js → [root-of-the-server]__0t3ka1q._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0w6l33k._.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/{0af6vx3nh6v77.js → 0-igg2k65fzo_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0q~7ymasjp~n7.js → 04iuhj_-h-21-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0hrob6m-1.n1d.js → 061hxr2b-j.6q.js} +1 -1
- package/.next/standalone/.next/static/chunks/{11vga8q3t~527.js → 0k5t-n0s8p2nr.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ms9sfkvw5h5i.js → 0lq8ary5l4s8t.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03xexm0jftw.x.js → 0ubv3x~0zdd_w.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0on5naeqa0w.x.js → 0v23ca5xty1n~.js} +2 -2
- package/.next/standalone/.next/static/chunks/{0_-7qmtiagi-~.js → 13cot7j99xkb~.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 +30 -12
- package/package.json +1 -1
- package/src/hooks/builtin-policies.ts +13 -5
- package/src/hooks/policy-evaluator.ts +31 -7
- package/src/hooks/policy-registry.ts +19 -2
- /package/.next/standalone/.next/static/{Q-y1SELeezrTyQx0E2uqg → my01WPjry7ohRUHyTaYp4}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{Q-y1SELeezrTyQx0E2uqg → my01WPjry7ohRUHyTaYp4}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{Q-y1SELeezrTyQx0E2uqg → my01WPjry7ohRUHyTaYp4}/_ssgManifest.js +0 -0
|
@@ -7,7 +7,7 @@ import { execSync, execFileSync } from "node:child_process";
|
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import type { BuiltinPolicyDefinition, PolicyContext, PolicyResult, PolicyParamsSchema } from "./policy-types";
|
|
9
9
|
import { allow, deny, instruct } from "./policy-helpers";
|
|
10
|
-
import { registerPolicy } from "./policy-registry";
|
|
10
|
+
import { normalizePolicyName, registerPolicy } from "./policy-registry";
|
|
11
11
|
import { hookLogWarn } from "./hook-logger";
|
|
12
12
|
|
|
13
13
|
function isClaudeInternalPath(resolved: string): boolean {
|
|
@@ -1254,7 +1254,7 @@ function requireNoConflictsBeforeStop(ctx: PolicyContext): PolicyResult {
|
|
|
1254
1254
|
|
|
1255
1255
|
let prJson: string;
|
|
1256
1256
|
try {
|
|
1257
|
-
prJson = execSync("gh pr view --json mergeable,number,url", {
|
|
1257
|
+
prJson = execSync("gh pr view --json mergeable,number,url,state", {
|
|
1258
1258
|
cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000,
|
|
1259
1259
|
}).trim();
|
|
1260
1260
|
} catch {
|
|
@@ -1265,13 +1265,19 @@ function requireNoConflictsBeforeStop(ctx: PolicyContext): PolicyResult {
|
|
|
1265
1265
|
);
|
|
1266
1266
|
}
|
|
1267
1267
|
|
|
1268
|
-
let pr: { mergeable: string; number: number; url: string };
|
|
1268
|
+
let pr: { mergeable: string; number: number; url: string; state: string };
|
|
1269
1269
|
try {
|
|
1270
1270
|
pr = JSON.parse(prJson);
|
|
1271
1271
|
} catch {
|
|
1272
1272
|
return allow("Could not parse gh pr view output, skipping PR mergeability check.");
|
|
1273
1273
|
}
|
|
1274
1274
|
|
|
1275
|
+
// GitHub stops computing mergeability for non-OPEN PRs (returns UNKNOWN forever).
|
|
1276
|
+
// Skip the check entirely so a merged or closed PR doesn't trap Stop in a wait loop.
|
|
1277
|
+
if (pr.state !== "OPEN") {
|
|
1278
|
+
return allow(`PR #${pr.number} is ${pr.state.toLowerCase()}; skipping conflict check.`);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1275
1281
|
if (pr.mergeable === "CONFLICTING") {
|
|
1276
1282
|
return deny(
|
|
1277
1283
|
`PR #${pr.number} has merge conflicts per GitHub (${pr.url}). ` +
|
|
@@ -1718,9 +1724,11 @@ export const BUILTIN_POLICIES: BuiltinPolicyDefinition[] = [
|
|
|
1718
1724
|
];
|
|
1719
1725
|
|
|
1720
1726
|
export function registerBuiltinPolicies(enabledNames: string[]): void {
|
|
1721
|
-
|
|
1727
|
+
// Tolerate both flat ("sanitize-jwt") and qualified ("exospherehost/sanitize-jwt")
|
|
1728
|
+
// forms in the user's enabledPolicies config — canonicalize both sides.
|
|
1729
|
+
const enabledSet = new Set(enabledNames.map(normalizePolicyName));
|
|
1722
1730
|
for (const policy of BUILTIN_POLICIES) {
|
|
1723
|
-
if (enabledSet.has(policy.name)) {
|
|
1731
|
+
if (enabledSet.has(normalizePolicyName(policy.name))) {
|
|
1724
1732
|
registerPolicy(policy.name, policy.description, policy.fn, policy.match);
|
|
1725
1733
|
}
|
|
1726
1734
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { HookEventType, SessionMetadata } from "./types";
|
|
6
6
|
import type { PolicyContext, HooksConfig } from "./policy-types";
|
|
7
7
|
import { BUILTIN_POLICIES } from "./builtin-policies";
|
|
8
|
-
import { getPoliciesForEvent } from "./policy-registry";
|
|
8
|
+
import { DEFAULT_POLICY_NAMESPACE, getPoliciesForEvent, normalizePolicyName } from "./policy-registry";
|
|
9
9
|
import { hookLogInfo, hookLogWarn } from "./hook-logger";
|
|
10
10
|
|
|
11
11
|
function appendHint(baseReason: string, hint: unknown): string {
|
|
@@ -26,11 +26,33 @@ export interface EvaluationResult {
|
|
|
26
26
|
decision: "allow" | "deny" | "instruct";
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Build a map from policy name to its params schema (for injecting defaults)
|
|
29
|
+
// Build a map from canonical policy name to its params schema (for injecting defaults).
|
|
30
|
+
// Keyed by canonical name because registered policies always carry the canonical form.
|
|
30
31
|
const POLICY_PARAMS_MAP = new Map(
|
|
31
|
-
BUILTIN_POLICIES.filter((p) => p.params).map((p) => [p.name, p.params!]),
|
|
32
|
+
BUILTIN_POLICIES.filter((p) => p.params).map((p) => [normalizePolicyName(p.name), p.params!]),
|
|
32
33
|
);
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Look up policy params for a canonical policy name in the user config,
|
|
37
|
+
* tolerating either flat ("block-force-push") or qualified
|
|
38
|
+
* ("exospherehost/block-force-push") config keys for built-in policies.
|
|
39
|
+
*
|
|
40
|
+
* The flat-key fallback is intentionally limited to the default namespace
|
|
41
|
+
* so namespace isolation is preserved: `policyParams.foo` only matches
|
|
42
|
+
* `exospherehost/foo`, never `myorg/foo` or `custom/foo`.
|
|
43
|
+
*/
|
|
44
|
+
function getConfigParamsFor(
|
|
45
|
+
config: HooksConfig | undefined,
|
|
46
|
+
canonicalName: string,
|
|
47
|
+
): Record<string, unknown> | undefined {
|
|
48
|
+
if (!config?.policyParams) return undefined;
|
|
49
|
+
const canonicalParams = config.policyParams[canonicalName];
|
|
50
|
+
if (canonicalParams) return canonicalParams;
|
|
51
|
+
const defaultPrefix = `${DEFAULT_POLICY_NAMESPACE}/`;
|
|
52
|
+
if (!canonicalName.startsWith(defaultPrefix)) return undefined;
|
|
53
|
+
return config.policyParams[canonicalName.slice(defaultPrefix.length)];
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
export async function evaluatePolicies(
|
|
35
57
|
eventType: HookEventType,
|
|
36
58
|
payload: Record<string, unknown>,
|
|
@@ -63,11 +85,13 @@ export async function evaluatePolicies(
|
|
|
63
85
|
const allowEntries: Array<{ policyName: string; reason: string }> = [];
|
|
64
86
|
|
|
65
87
|
for (const policy of policies) {
|
|
66
|
-
// Inject params: merge policyParams[policy.name] over schema defaults
|
|
88
|
+
// Inject params: merge policyParams[policy.name] over schema defaults.
|
|
89
|
+
// policy.name is canonical (e.g. "exospherehost/block-force-push"); user
|
|
90
|
+
// config keys may be flat or canonical — getConfigParamsFor accepts both.
|
|
67
91
|
const schema = POLICY_PARAMS_MAP.get(policy.name);
|
|
68
92
|
let ctx: PolicyContext;
|
|
69
93
|
if (schema) {
|
|
70
|
-
const userParams = config
|
|
94
|
+
const userParams = getConfigParamsFor(config, policy.name) ?? {};
|
|
71
95
|
const resolvedParams: Record<string, unknown> = {};
|
|
72
96
|
for (const [key, spec] of Object.entries(schema)) {
|
|
73
97
|
resolvedParams[key] = key in userParams ? userParams[key] : spec.default;
|
|
@@ -89,7 +113,7 @@ export async function evaluatePolicies(
|
|
|
89
113
|
if (result.decision === "deny") {
|
|
90
114
|
const reason = appendHint(
|
|
91
115
|
result.reason ?? `Blocked by policy: ${policy.name}`,
|
|
92
|
-
config
|
|
116
|
+
getConfigParamsFor(config, policy.name)?.hint,
|
|
93
117
|
);
|
|
94
118
|
hookLogInfo(`deny by "${policy.name}": ${reason}`);
|
|
95
119
|
|
|
@@ -156,7 +180,7 @@ export async function evaluatePolicies(
|
|
|
156
180
|
if (result.decision === "instruct") {
|
|
157
181
|
const reason = appendHint(
|
|
158
182
|
result.reason ?? `Instruction from policy: ${policy.name}`,
|
|
159
|
-
config
|
|
183
|
+
getConfigParamsFor(config, policy.name)?.hint,
|
|
160
184
|
);
|
|
161
185
|
instructEntries.push({ policyName: policy.name, reason });
|
|
162
186
|
hookLogInfo(`instruct by "${policy.name}": ${reason}`);
|
|
@@ -11,6 +11,22 @@ import type { PolicyFunction, PolicyMatcher, RegisteredPolicy } from "./policy-t
|
|
|
11
11
|
const REGISTRY_KEY = "__FAILPROOFAI_POLICY_REGISTRY__";
|
|
12
12
|
const INDEX_CACHE_KEY = "__FAILPROOFAI_POLICY_INDEX_CACHE__";
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The default namespace applied to any policy name registered without a
|
|
16
|
+
* `<namespace>/` prefix. Builtins live under this namespace; custom hooks
|
|
17
|
+
* loaded by the handler get their own prefixes (e.g. `custom/foo`).
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_POLICY_NAMESPACE = "exospherehost";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Canonicalize a policy name. If the name already contains a `/`, it is
|
|
23
|
+
* treated as already-namespaced and returned unchanged. Otherwise the
|
|
24
|
+
* default namespace is prepended.
|
|
25
|
+
*/
|
|
26
|
+
export function normalizePolicyName(name: string): string {
|
|
27
|
+
return name.includes("/") ? name : `${DEFAULT_POLICY_NAMESPACE}/${name}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
interface GlobalWithRegistry {
|
|
15
31
|
[REGISTRY_KEY]?: RegisteredPolicy[];
|
|
16
32
|
}
|
|
@@ -42,9 +58,10 @@ export function registerPolicy(
|
|
|
42
58
|
match: PolicyMatcher,
|
|
43
59
|
priority: number = 0,
|
|
44
60
|
): void {
|
|
61
|
+
const canonical = normalizePolicyName(name);
|
|
45
62
|
const registry = getRegistry();
|
|
46
|
-
const idx = registry.findIndex((p) => p.name ===
|
|
47
|
-
const entry: RegisteredPolicy = { name, description, fn, match, priority };
|
|
63
|
+
const idx = registry.findIndex((p) => p.name === canonical);
|
|
64
|
+
const entry: RegisteredPolicy = { name: canonical, description, fn, match, priority };
|
|
48
65
|
if (idx >= 0) {
|
|
49
66
|
registry[idx] = entry;
|
|
50
67
|
} else {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|