failproofai 1.0.8-beta.0 → 1.0.9-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- 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 +1 -1
- 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/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +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 +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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 +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +3 -3
- 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/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__14o3ek1._.js → [root-of-the-server]__0tmz862._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0o4xkpl._.js → _0n0lja6._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1-7sqrb._.js → _1p3iz2d._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +3 -3
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- 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 +23 -23
- package/.next/standalone/.next/static/chunks/{2z7i-yg59w4pn.js → 0247hc9ath5t-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ahfwmbkpgfw4.js → 03001k9jy-kst.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2vo7qbdbvnc0o.js → 06k7gsfnb7jkh.js} +1 -1
- package/.next/standalone/.next/static/chunks/0qejem4-zt-hy.js +1 -0
- package/.next/standalone/.next/static/chunks/{1v_tp3hm8wyhe.js → 0snylg3y1i47a.js} +1 -1
- package/.next/standalone/.next/static/chunks/1h0if8ky362as.js +1 -0
- package/.next/standalone/.next/static/chunks/{3bgot5v6f6s3c.js → 2qsrrrl2maqsb.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0bhidk90e-07f.css → 3_e_wg5_oay87.css} +1 -1
- package/.next/standalone/.next/static/chunks/3ns-egfh8i8i5.js +6 -0
- package/.next/standalone/.next/static/chunks/3rq7fjj9we0v3.js +1 -0
- package/.next/standalone/.next/static/chunks/42m0f2hh8vfb1.js +1 -0
- package/.next/standalone/app/actions/get-jev-config.ts +2 -2
- package/.next/standalone/app/actions/update-jev-config.ts +12 -9
- package/.next/standalone/app/components/jev-notices.tsx +10 -10
- package/.next/standalone/app/settings/jev-panel.tsx +11 -11
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +26 -0
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +27 -0
- package/.next/standalone/hermes-plugin/README.md +18 -8
- package/.next/standalone/package.json +10 -10
- package/.next/standalone/sdk/typescript/CHANGELOG.md +18 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +0 -274
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +33 -7
- package/dist/cli.mjs +664 -553
- package/dist/worker.mjs +164 -407
- package/hermes-plugin/README.md +18 -8
- package/package.json +10 -10
- package/scripts/build-policy-pack.mjs +6 -9
- package/src/hooks/cloud-connection.ts +12 -6
- package/src/hooks/custom-hooks-loader.ts +1 -1
- package/src/hooks/effective-reviewers.ts +50 -43
- package/src/hooks/handler.ts +25 -16
- package/src/hooks/hermes-update.ts +74 -0
- package/src/hooks/hook-activity-store.ts +3 -3
- package/src/hooks/integrations.ts +349 -55
- package/src/hooks/jev-activity.ts +3 -3
- package/src/hooks/jev-cli.ts +17 -17
- package/src/hooks/jev-cloud-connection.ts +4 -4
- package/src/hooks/manager.ts +3 -2
- package/src/hooks/pack-cli.ts +24 -23
- package/src/hooks/pack-manifest.ts +25 -2
- package/src/hooks/pack-store.ts +2 -2
- package/src/hooks/policy-authority.ts +51 -32
- package/src/hooks/policy-evaluator.ts +5 -5
- package/src/hooks/policy-registry.ts +5 -7
- package/src/hooks/policy-reviewability.ts +26 -10
- package/src/hooks/policy-types.ts +4 -4
- package/src/hooks/semantic/combine.ts +13 -13
- package/src/hooks/semantic/decide.ts +134 -29
- package/src/hooks/semantic/evaluator.ts +5 -5
- package/src/hooks/semantic/facts.ts +91 -2
- package/src/hooks/semantic/jev-client.ts +1 -1
- package/src/hooks/semantic/jev-config.ts +21 -12
- package/src/hooks/semantic/jev-review.ts +3 -3
- package/src/hooks/semantic/jev-stats.ts +17 -17
- package/src/hooks/semantic/pack-policies.ts +48 -48
- package/src/hooks/semantic/policies.ts +13 -425
- package/src/hooks/semantic/precondition-names.ts +1 -1
- package/src/hooks/semantic/preconditions.ts +1 -1
- package/src/hooks/semantic/types.ts +7 -1
- package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +0 -1
- package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +0 -1
- package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +0 -1
- package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +0 -1
- package/.next/standalone/.next/static/chunks/3o3f1ibfci0p7.js +0 -6
- /package/.next/standalone/.next/static/{O_b5R1axf5cb4NxIXq2kS → z1C0kJVF0NCtWn5R88hW3}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{O_b5R1axf5cb4NxIXq2kS → z1C0kJVF0NCtWn5R88hW3}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{O_b5R1axf5cb4NxIXq2kS → z1C0kJVF0NCtWn5R88hW3}/_ssgManifest.js +0 -0
package/dist/worker.mjs
CHANGED
|
@@ -1162,7 +1162,7 @@ function instruct(reason) {
|
|
|
1162
1162
|
}
|
|
1163
1163
|
|
|
1164
1164
|
// package.json
|
|
1165
|
-
var version = "1.0.
|
|
1165
|
+
var version = "1.0.9-beta.0";
|
|
1166
1166
|
var init_package = () => {};
|
|
1167
1167
|
|
|
1168
1168
|
// src/hooks/policy-types.ts
|
|
@@ -1179,7 +1179,7 @@ function effectiveAuthority(p) {
|
|
|
1179
1179
|
|
|
1180
1180
|
// src/hooks/policy-authority.ts
|
|
1181
1181
|
import { existsSync as existsSync3 } from "node:fs";
|
|
1182
|
-
function resolvePolicyAuthority(decl, knownReviewers =
|
|
1182
|
+
function resolvePolicyAuthority(decl, knownReviewers = NO_REVIEWERS, contested) {
|
|
1183
1183
|
const declaredReviewable = decl?.authority === "reviewable";
|
|
1184
1184
|
if (!decl || effectiveAuthority(decl) === "hard") {
|
|
1185
1185
|
if (!declaredReviewable)
|
|
@@ -1191,6 +1191,8 @@ function resolvePolicyAuthority(decl, knownReviewers = SEMANTIC_REVIEWER_NAMES,
|
|
|
1191
1191
|
return { authority: "hard", downgraded: "reviewedBy is not a list of semantic policy names" };
|
|
1192
1192
|
}
|
|
1193
1193
|
const unknown = names.filter((n) => !knownReviewers.has(n));
|
|
1194
|
+
if (knownReviewers.size === 0 && !unknown.some((n) => contested?.has(n)))
|
|
1195
|
+
return { authority: "hard" };
|
|
1194
1196
|
if (unknown.length > 0) {
|
|
1195
1197
|
return { authority: "hard", downgraded: `reviewedBy names ${unknown.map((n) => whyUnknown(n, knownReviewers, contested)).join("; ")}` };
|
|
1196
1198
|
}
|
|
@@ -1225,7 +1227,7 @@ function authorityFieldsOf(raw) {
|
|
|
1225
1227
|
}
|
|
1226
1228
|
return out;
|
|
1227
1229
|
}
|
|
1228
|
-
function withMergedAuthority(record, declarations, knownReviewers =
|
|
1230
|
+
function withMergedAuthority(record, declarations, knownReviewers = NO_REVIEWERS) {
|
|
1229
1231
|
const resolved = declarations.map((d) => resolvePolicyAuthority(d, knownReviewers));
|
|
1230
1232
|
const allReviewable = resolved.length > 0 && resolved.every((r) => r.authority === "reviewable");
|
|
1231
1233
|
const decided = declarations[resolved.findIndex((r) => r.authority !== "reviewable")] ?? {};
|
|
@@ -1264,7 +1266,7 @@ function authorityDeclarationFor(hook, source) {
|
|
|
1264
1266
|
}
|
|
1265
1267
|
return { authority: hook.authority, reviewedBy: hook.reviewedBy };
|
|
1266
1268
|
}
|
|
1267
|
-
var SEMANTIC_POLICY_NAMES, SEMANTIC_REVIEWER_NAMES, isName = (n) => typeof n === "string" && n.length > 0, warnedAuthority;
|
|
1269
|
+
var SEMANTIC_POLICY_NAMES, SEMANTIC_REVIEWER_NAMES, NO_REVIEWERS, isName = (n) => typeof n === "string" && n.length > 0, warnedAuthority;
|
|
1268
1270
|
var init_policy_authority = __esm(() => {
|
|
1269
1271
|
init_fp_home();
|
|
1270
1272
|
init_hook_logger();
|
|
@@ -1287,6 +1289,7 @@ var init_policy_authority = __esm(() => {
|
|
|
1287
1289
|
"external-data-egress"
|
|
1288
1290
|
];
|
|
1289
1291
|
SEMANTIC_REVIEWER_NAMES = new Set(SEMANTIC_POLICY_NAMES);
|
|
1292
|
+
NO_REVIEWERS = new Set;
|
|
1290
1293
|
warnedAuthority = new Set;
|
|
1291
1294
|
});
|
|
1292
1295
|
|
|
@@ -1480,10 +1483,12 @@ function parsePackIdentity(value) {
|
|
|
1480
1483
|
...commit ? { commit } : {}
|
|
1481
1484
|
};
|
|
1482
1485
|
}
|
|
1483
|
-
function
|
|
1486
|
+
function hasInstalledRegexPacks() {
|
|
1484
1487
|
try {
|
|
1485
1488
|
const raw = JSON.parse(readFileSync3(installedFilePath(), "utf8"));
|
|
1486
|
-
|
|
1489
|
+
if (!Array.isArray(raw.packs))
|
|
1490
|
+
return false;
|
|
1491
|
+
return raw.packs.some((p) => !!p && typeof p === "object" && Array.isArray(p.policies) && p.policies.length > 0);
|
|
1487
1492
|
} catch {
|
|
1488
1493
|
return false;
|
|
1489
1494
|
}
|
|
@@ -1903,7 +1908,7 @@ function isReservedClaim(pack, name) {
|
|
|
1903
1908
|
function effectiveReviewerNames() {
|
|
1904
1909
|
if (cached)
|
|
1905
1910
|
return cached;
|
|
1906
|
-
let names =
|
|
1911
|
+
let names = NO_REVIEWERS;
|
|
1907
1912
|
cachedContested = new Map;
|
|
1908
1913
|
try {
|
|
1909
1914
|
const packs = jevPacks(readInstalledPacks().packs, reviewerCli);
|
|
@@ -1917,15 +1922,15 @@ function contestedReviewerNames() {
|
|
|
1917
1922
|
effectiveReviewerNames();
|
|
1918
1923
|
return cachedContested;
|
|
1919
1924
|
}
|
|
1920
|
-
function
|
|
1921
|
-
return
|
|
1925
|
+
function jevChecksInstalled() {
|
|
1926
|
+
return effectiveReviewerNames().size > 0;
|
|
1922
1927
|
}
|
|
1923
1928
|
function reviewerNamesFor(packs) {
|
|
1924
1929
|
const contested = contestedSemanticNames(packs);
|
|
1925
1930
|
const declared = packs.flatMap((p) => (p.semantic ?? []).filter((s) => !isReservedClaim(p, s.name)).map((s) => s.name)).filter((name) => !contested.has(name));
|
|
1926
1931
|
if (declared.length === 0)
|
|
1927
|
-
return
|
|
1928
|
-
return new Set(
|
|
1932
|
+
return NO_REVIEWERS;
|
|
1933
|
+
return new Set(declared);
|
|
1929
1934
|
}
|
|
1930
1935
|
function forgetEffectiveReviewerNames(cli) {
|
|
1931
1936
|
cached = null;
|
|
@@ -4222,7 +4227,7 @@ var init_jev_activity = __esm(() => {
|
|
|
4222
4227
|
];
|
|
4223
4228
|
EVALUATORS = new Set(["jev", "jev-fallback"]);
|
|
4224
4229
|
DECISIONS = new Set(["allow", "instruct", "deny"]);
|
|
4225
|
-
MODES = new Set(["
|
|
4230
|
+
MODES = new Set(["observe", "enforce"]);
|
|
4226
4231
|
REASON_HEAD_RE = /^([a-z0-9]+(?:-[a-z0-9]+)*)\s*(?:$|[:(])/;
|
|
4227
4232
|
HTTP_CODE_RE = /^http-\d{3}$/;
|
|
4228
4233
|
JEV_REASON_CODE_LIST = [
|
|
@@ -4327,14 +4332,14 @@ function combineTwoTier(verdicts, review, mode) {
|
|
|
4327
4332
|
...review.model ? { jevModel: review.model } : {},
|
|
4328
4333
|
jevMode: mode
|
|
4329
4334
|
};
|
|
4330
|
-
if (mode === "
|
|
4331
|
-
const
|
|
4335
|
+
if (mode === "observe") {
|
|
4336
|
+
const observeVerdict = review.decision === "deny" || review.decision === "instruct" ? {
|
|
4332
4337
|
policyName: jevEntry.policyName,
|
|
4333
4338
|
decision: review.decision,
|
|
4334
4339
|
reason: jevEntry.reason,
|
|
4335
4340
|
version: review.model && JEV_MODEL_RE.test(review.model) ? review.model : "jev"
|
|
4336
4341
|
} : undefined;
|
|
4337
|
-
return { final: legacy, cleared, decidedByJev: false, activity, ...
|
|
4342
|
+
return { final: legacy, cleared, decidedByJev: false, activity, ...observeVerdict ? { observeVerdict } : {} };
|
|
4338
4343
|
}
|
|
4339
4344
|
return { ...enforced, cleared, activity };
|
|
4340
4345
|
}
|
|
@@ -4908,7 +4913,7 @@ async function evaluatePolicies(eventType, payload, session, config, twoTier) {
|
|
|
4908
4913
|
}
|
|
4909
4914
|
const combined = combineTwoTier(collected.verdicts, review, twoTier.mode);
|
|
4910
4915
|
if (combined.cleared.length > 0) {
|
|
4911
|
-
hookLogInfo(`jev ${twoTier.mode === "
|
|
4916
|
+
hookLogInfo(`jev ${twoTier.mode === "observe" ? "would clear" : "cleared"}: ${combined.cleared.join(", ")}`);
|
|
4912
4917
|
}
|
|
4913
4918
|
if (combined.activity.evaluator === "jev-fallback") {
|
|
4914
4919
|
hookLogInfo(combined.activity.jevFallbackReason === JEV_REASON_REQUEST_CUT ? "jev could not be shown the whole call; it cleared nothing, and the call was not allowed on a partial read" : `jev unavailable (${combined.activity.jevFallbackReason}); the regex result decided this call`);
|
|
@@ -4919,7 +4924,7 @@ async function evaluatePolicies(eventType, payload, session, config, twoTier) {
|
|
|
4919
4924
|
activity: combined.activity,
|
|
4920
4925
|
decidedByJev: combined.decidedByJev,
|
|
4921
4926
|
...combined.decidedByJev && review.kind === "answered" && review.origin ? { jevOrigin: review.origin } : {},
|
|
4922
|
-
...combined.
|
|
4927
|
+
...combined.observeVerdict ? { observeVerdict: combined.observeVerdict } : {}
|
|
4923
4928
|
}
|
|
4924
4929
|
};
|
|
4925
4930
|
}
|
|
@@ -11479,6 +11484,9 @@ var init_fp_reset = __esm(() => {
|
|
|
11479
11484
|
// src/hooks/semantic/jev-config.ts
|
|
11480
11485
|
import { closeSync as closeSync2, constants as fsConstants2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2, statSync as statSync15 } from "node:fs";
|
|
11481
11486
|
import { dirname as dirname6 } from "node:path";
|
|
11487
|
+
function parseJevMode(raw) {
|
|
11488
|
+
return raw === "off" || raw === "observe" || raw === "enforce" ? raw : null;
|
|
11489
|
+
}
|
|
11482
11490
|
function jevConfigPath() {
|
|
11483
11491
|
return jevConfigFile();
|
|
11484
11492
|
}
|
|
@@ -11665,14 +11673,15 @@ function validateJevConfig(raw, envKey, cloud) {
|
|
|
11665
11673
|
cfg.timeoutMs = t;
|
|
11666
11674
|
}
|
|
11667
11675
|
if (o.mode !== undefined) {
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11676
|
+
const mode = parseJevMode(o.mode);
|
|
11677
|
+
if (mode === null)
|
|
11678
|
+
return { ok: false, problem: 'mode must be "off", "observe" or "enforce"' };
|
|
11679
|
+
cfg.mode = mode;
|
|
11671
11680
|
}
|
|
11672
11681
|
if (cfg.baseUrl !== undefined && isPlainHttp(cfg.baseUrl) && cfg.mode === "enforce") {
|
|
11673
11682
|
return {
|
|
11674
11683
|
ok: false,
|
|
11675
|
-
problem: "plain http (to localhost) is accepted only with mode
|
|
11684
|
+
problem: "plain http (to localhost) is accepted only with mode observe: in enforce mode Jev's answers can clear a deny, " + "and while the local proxy is down any process on this machine could take its port and answer. " + (cfg.provider === JEV_CLOUD_PROVIDER ? "Reconnect to an https FailproofAI Cloud URL (failproofai config --token <key> --url https://…), or keep mode observe" : "Use https, or mode observe")
|
|
11676
11685
|
};
|
|
11677
11686
|
}
|
|
11678
11687
|
return { ok: true, value: cfg };
|
|
@@ -11905,6 +11914,19 @@ function classifyTool(toolName) {
|
|
|
11905
11914
|
return { toolClass: "other", toolIsKnown: true };
|
|
11906
11915
|
return { toolClass: "other", toolIsKnown: false };
|
|
11907
11916
|
}
|
|
11917
|
+
function runsNestedShell(tokens) {
|
|
11918
|
+
let shellSeen = false;
|
|
11919
|
+
for (const tok of tokens) {
|
|
11920
|
+
if (shellSeen && /^-[a-z]*c[a-z]*$/i.test(tok))
|
|
11921
|
+
return true;
|
|
11922
|
+
const base = tok.slice(tok.lastIndexOf("/") + 1);
|
|
11923
|
+
if (base === "eval")
|
|
11924
|
+
return true;
|
|
11925
|
+
if (NESTED_SHELLS.has(base))
|
|
11926
|
+
shellSeen = true;
|
|
11927
|
+
}
|
|
11928
|
+
return false;
|
|
11929
|
+
}
|
|
11908
11930
|
function scanCommand(command) {
|
|
11909
11931
|
const text = command.length > MAX_SCAN_CHARS ? command.slice(0, MAX_SCAN_CHARS) : command;
|
|
11910
11932
|
const segments = [];
|
|
@@ -11915,22 +11937,36 @@ function scanCommand(command) {
|
|
|
11915
11937
|
let out = "";
|
|
11916
11938
|
let commentsRemoved = false;
|
|
11917
11939
|
const comments = [];
|
|
11940
|
+
let complete = command.length <= MAX_SCAN_CHARS;
|
|
11941
|
+
let braceDepth = 0;
|
|
11942
|
+
let braceSep = false;
|
|
11943
|
+
let bracketOpen = false;
|
|
11918
11944
|
const endWord = () => {
|
|
11919
11945
|
if (inWord)
|
|
11920
11946
|
tokens.push(word);
|
|
11921
11947
|
word = "";
|
|
11922
11948
|
inWord = false;
|
|
11949
|
+
braceDepth = 0;
|
|
11950
|
+
braceSep = false;
|
|
11951
|
+
bracketOpen = false;
|
|
11923
11952
|
};
|
|
11924
11953
|
const endSegment = () => {
|
|
11925
11954
|
endWord();
|
|
11926
|
-
if (tokens.length > 0)
|
|
11955
|
+
if (tokens.length > 0) {
|
|
11956
|
+
if (complete && runsNestedShell(tokens))
|
|
11957
|
+
complete = false;
|
|
11927
11958
|
segments.push(tokens);
|
|
11959
|
+
}
|
|
11928
11960
|
tokens = [];
|
|
11929
11961
|
};
|
|
11930
11962
|
for (let i = 0;i < text.length; i++) {
|
|
11931
11963
|
const c = text[i];
|
|
11932
11964
|
if (quote) {
|
|
11933
11965
|
out += c;
|
|
11966
|
+
if (quote === '"' && (c === "`" || c === "$" && EXPANSION_START.test(text[i + 1] ?? "") || c === "\\" && text[i + 1] === `
|
|
11967
|
+
`)) {
|
|
11968
|
+
complete = false;
|
|
11969
|
+
}
|
|
11934
11970
|
if (c === quote) {
|
|
11935
11971
|
quote = null;
|
|
11936
11972
|
} else if (c === "\\" && quote === '"' && i + 1 < text.length) {
|
|
@@ -11941,6 +11977,10 @@ function scanCommand(command) {
|
|
|
11941
11977
|
}
|
|
11942
11978
|
continue;
|
|
11943
11979
|
}
|
|
11980
|
+
if (c === "`" || c === "$" && (text[i + 1] === "'" || text[i + 1] === '"' || EXPANSION_START.test(text[i + 1] ?? "")) || (c === "<" || c === ">") && text[i + 1] === "(" || c === "<" && text[i + 1] === "<" || c === "\\" && text[i + 1] === `
|
|
11981
|
+
`) {
|
|
11982
|
+
complete = false;
|
|
11983
|
+
}
|
|
11944
11984
|
if (c === "'" || c === '"') {
|
|
11945
11985
|
quote = c;
|
|
11946
11986
|
inWord = true;
|
|
@@ -11981,12 +12021,29 @@ function scanCommand(command) {
|
|
|
11981
12021
|
out += c;
|
|
11982
12022
|
continue;
|
|
11983
12023
|
}
|
|
12024
|
+
if (c === "{") {
|
|
12025
|
+
braceDepth++;
|
|
12026
|
+
} else if (braceDepth > 0 && (c === "," || c === "." && text[i + 1] === ".")) {
|
|
12027
|
+
braceSep = true;
|
|
12028
|
+
} else if (c === "}" && braceDepth > 0) {
|
|
12029
|
+
braceDepth--;
|
|
12030
|
+
if (braceSep)
|
|
12031
|
+
complete = false;
|
|
12032
|
+
} else if (c === "*" || c === "?") {
|
|
12033
|
+
complete = false;
|
|
12034
|
+
} else if (c === "[") {
|
|
12035
|
+
bracketOpen = true;
|
|
12036
|
+
} else if (c === "]" && bracketOpen) {
|
|
12037
|
+
complete = false;
|
|
12038
|
+
}
|
|
11984
12039
|
word += c;
|
|
11985
12040
|
inWord = true;
|
|
11986
12041
|
out += c;
|
|
11987
12042
|
}
|
|
11988
12043
|
endSegment();
|
|
11989
|
-
|
|
12044
|
+
if (quote)
|
|
12045
|
+
complete = false;
|
|
12046
|
+
return { segments, withoutComments: out.trimEnd(), commentsRemoved, comments, complete };
|
|
11990
12047
|
}
|
|
11991
12048
|
function expandHome(token, home) {
|
|
11992
12049
|
if (token === "~")
|
|
@@ -12119,7 +12176,7 @@ function computeFacts(toolName, toolInput, cwd, permissionMode, scanned, pinnedP
|
|
|
12119
12176
|
permissionMode
|
|
12120
12177
|
};
|
|
12121
12178
|
}
|
|
12122
|
-
var SHELL_TOOLS, WRITE_TOOLS, READ_TOOLS, NETWORK_TOOLS, INERT_TOOLS, MAX_SCAN_CHARS = 8192, PATH_LIKE, GLOB_CHARS, MAX_PATHS = 12;
|
|
12179
|
+
var SHELL_TOOLS, WRITE_TOOLS, READ_TOOLS, NETWORK_TOOLS, INERT_TOOLS, MAX_SCAN_CHARS = 8192, NESTED_SHELLS, EXPANSION_START, PATH_LIKE, GLOB_CHARS, MAX_PATHS = 12;
|
|
12123
12180
|
var init_facts = __esm(() => {
|
|
12124
12181
|
SHELL_TOOLS = new Set(["Bash", "BashOutput"]);
|
|
12125
12182
|
WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
@@ -12138,6 +12195,8 @@ var init_facts = __esm(() => {
|
|
|
12138
12195
|
"SlashCommand",
|
|
12139
12196
|
"Skill"
|
|
12140
12197
|
]);
|
|
12198
|
+
NESTED_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh", "ash", "fish"]);
|
|
12199
|
+
EXPANSION_START = /[A-Za-z_0-9@*#?$!\-({]/;
|
|
12141
12200
|
PATH_LIKE = /^(?:\/|~|\.\.?(?:\/|$)|[^:]*\/)/;
|
|
12142
12201
|
GLOB_CHARS = /[*?[]/;
|
|
12143
12202
|
});
|
|
@@ -12146,50 +12205,70 @@ var init_facts = __esm(() => {
|
|
|
12146
12205
|
function tokensOf(text) {
|
|
12147
12206
|
return text.toLowerCase().split(/[^a-z0-9._@-]+/).filter((t) => t.length >= 3 && !GENERIC_TOKENS.has(t) && !/^\d+$/.test(t));
|
|
12148
12207
|
}
|
|
12149
|
-
function
|
|
12150
|
-
const
|
|
12208
|
+
function scanTargets(toolInput) {
|
|
12209
|
+
const groups = [];
|
|
12210
|
+
const addGroup = (tok) => {
|
|
12211
|
+
const words = tokensOf(tok);
|
|
12212
|
+
if (words.length > 0)
|
|
12213
|
+
groups.push(new Set(words));
|
|
12214
|
+
};
|
|
12151
12215
|
const command = typeof toolInput.command === "string" ? toolInput.command : null;
|
|
12152
12216
|
if (command) {
|
|
12153
|
-
|
|
12217
|
+
const scanned = scanCommand(command);
|
|
12218
|
+
for (const seg of scanned.segments) {
|
|
12154
12219
|
for (const tok of seg.slice(1)) {
|
|
12155
|
-
if (tok.startsWith("-"))
|
|
12156
|
-
|
|
12157
|
-
for (const t of tokensOf(tok))
|
|
12158
|
-
out.add(t);
|
|
12220
|
+
if (!tok.startsWith("-"))
|
|
12221
|
+
addGroup(tok);
|
|
12159
12222
|
}
|
|
12160
12223
|
}
|
|
12161
|
-
if (
|
|
12224
|
+
if (groups.length === 0) {
|
|
12162
12225
|
for (const piece of command.split(/[;&|\n]+/)) {
|
|
12163
12226
|
for (const tok of piece.trim().split(/\s+/).slice(1)) {
|
|
12164
12227
|
if (!tok.startsWith("-"))
|
|
12165
|
-
|
|
12166
|
-
out.add(t);
|
|
12228
|
+
addGroup(tok);
|
|
12167
12229
|
}
|
|
12168
12230
|
}
|
|
12169
12231
|
}
|
|
12170
|
-
return
|
|
12232
|
+
return { targets: new Set(groups.flatMap((g) => [...g])), groups, complete: scanned.complete };
|
|
12171
12233
|
}
|
|
12234
|
+
const all = new Set;
|
|
12172
12235
|
for (const [key, value] of Object.entries(toolInput)) {
|
|
12173
12236
|
if (typeof value !== "string" || value.length > 300)
|
|
12174
12237
|
continue;
|
|
12175
12238
|
if (/content|old_string|new_string|body|text|prompt/i.test(key))
|
|
12176
12239
|
continue;
|
|
12177
12240
|
for (const t of tokensOf(value))
|
|
12178
|
-
|
|
12241
|
+
all.add(t);
|
|
12179
12242
|
}
|
|
12180
|
-
return
|
|
12243
|
+
return { targets: all, groups: all.size > 0 ? [all] : [], complete: true };
|
|
12181
12244
|
}
|
|
12182
|
-
function
|
|
12183
|
-
if (userSaid.length === 0)
|
|
12245
|
+
function everyTargetNamed(scan, userSaid) {
|
|
12246
|
+
if (userSaid.length === 0 || scan.groups.length === 0)
|
|
12184
12247
|
return false;
|
|
12185
|
-
|
|
12248
|
+
const said = userSaid.join(`
|
|
12249
|
+
`).toLowerCase();
|
|
12250
|
+
return scan.groups.every((g) => {
|
|
12251
|
+
for (const t of g)
|
|
12252
|
+
if (said.includes(t))
|
|
12253
|
+
return true;
|
|
12254
|
+
return false;
|
|
12255
|
+
});
|
|
12256
|
+
}
|
|
12257
|
+
function partlyNamed(scan, userSaid) {
|
|
12258
|
+
if (userSaid.length === 0 || scan.groups.length < 2)
|
|
12186
12259
|
return false;
|
|
12187
12260
|
const said = userSaid.join(`
|
|
12188
12261
|
`).toLowerCase();
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
|
|
12262
|
+
let named = 0;
|
|
12263
|
+
for (const g of scan.groups) {
|
|
12264
|
+
for (const t of g) {
|
|
12265
|
+
if (said.includes(t)) {
|
|
12266
|
+
named++;
|
|
12267
|
+
break;
|
|
12268
|
+
}
|
|
12269
|
+
}
|
|
12270
|
+
}
|
|
12271
|
+
return named > 0 && named < scan.groups.length;
|
|
12193
12272
|
}
|
|
12194
12273
|
function formatOutcome(p, o) {
|
|
12195
12274
|
return `${p.title} (semantic/${p.name}, p=${o.evidence.toFixed(2)}). ${p.guidance}`;
|
|
@@ -12199,7 +12278,7 @@ function decide(selected, answers, toolInput, userSaid, thresholds = DEFAULT_THR
|
|
|
12199
12278
|
const injected = injection !== null && injection >= thresholds.injection;
|
|
12200
12279
|
const scope = typeof answers.scope === "number" ? answers.scope : null;
|
|
12201
12280
|
const withinScope = scope !== null && scope >= thresholds.scope;
|
|
12202
|
-
let
|
|
12281
|
+
let scan = null;
|
|
12203
12282
|
const outcomes = selected.map((p) => {
|
|
12204
12283
|
const evidence = Math.min(...p.probes.map((probe) => answers[`${p.name}.${probe.id}`] ?? 0));
|
|
12205
12284
|
const exempt = p.exempt ? answers[`${p.name}.exempt`] ?? 0 : null;
|
|
@@ -12222,9 +12301,11 @@ function decide(selected, answers, toolInput, userSaid, thresholds = DEFAULT_THR
|
|
|
12222
12301
|
return { ...base, escalatedByInjection: true, verdict: "deny" };
|
|
12223
12302
|
const fired = p.mode === "deny" && evidence >= thresholds.deny ? "deny" : "instruct";
|
|
12224
12303
|
if (p.userCanOverride && userAsked !== null && userAsked >= thresholds.userAsked && withinScope) {
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12304
|
+
scan ??= scanTargets(toolInput);
|
|
12305
|
+
if (!scan.complete)
|
|
12306
|
+
return { ...base, verdict: fired, targetScanIncomplete: true };
|
|
12307
|
+
const named = scan.groups.length > 0 && everyTargetNamed(scan, userSaid);
|
|
12308
|
+
if (scan.groups.length === 0 || named || userSaidCut)
|
|
12228
12309
|
return { ...base, targetNamedByUser: named, verdict: "overridden" };
|
|
12229
12310
|
}
|
|
12230
12311
|
return { ...base, verdict: fired };
|
|
@@ -12274,12 +12355,14 @@ function decideV1(selected, answers, toolInput, userSaid, agentLastMessage, opts
|
|
|
12274
12355
|
const task = num("task_step");
|
|
12275
12356
|
const op = num("op_requested");
|
|
12276
12357
|
const beyond = num("beyond_task");
|
|
12277
|
-
let
|
|
12358
|
+
let scan = null;
|
|
12359
|
+
const targetScan = () => scan ??= scanTargets(toolInput);
|
|
12360
|
+
const evidenceSaid = agentLastMessage ? [...userSaid, agentLastMessage] : userSaid;
|
|
12278
12361
|
const targetOk = () => {
|
|
12279
|
-
|
|
12280
|
-
if (
|
|
12362
|
+
const s = targetScan();
|
|
12363
|
+
if (s.groups.length === 0)
|
|
12281
12364
|
return { ok: true, named: false };
|
|
12282
|
-
const named =
|
|
12365
|
+
const named = everyTargetNamed(s, evidenceSaid);
|
|
12283
12366
|
return { ok: named || userSaidCut, named };
|
|
12284
12367
|
};
|
|
12285
12368
|
const outcomes = selected.map((p) => {
|
|
@@ -12304,12 +12387,14 @@ function decideV1(selected, answers, toolInput, userSaid, agentLastMessage, opts
|
|
|
12304
12387
|
const fired = p.mode === "deny" && evidence >= t.deny ? "deny" : "instruct";
|
|
12305
12388
|
if (!p.userCanOverride)
|
|
12306
12389
|
return { ...base, verdict: fired };
|
|
12390
|
+
if (!targetScan().complete)
|
|
12391
|
+
return { ...base, verdict: fired, targetScanIncomplete: true };
|
|
12307
12392
|
if (op !== null && op >= t.opRequested && beyond !== null && beyond < t.opBeyondMax) {
|
|
12308
12393
|
const target = targetOk();
|
|
12309
12394
|
if (target.ok)
|
|
12310
12395
|
return { ...base, targetNamedByUser: target.named, verdict: "overridden", intent: "op-requested" };
|
|
12311
12396
|
}
|
|
12312
|
-
if (taskClears && task !== null && task >= t.taskStep && beyond !== null && beyond < t.taskBeyondMax) {
|
|
12397
|
+
if (taskClears && task !== null && task >= t.taskStep && beyond !== null && beyond < t.taskBeyondMax && !(typeof toolInput.command === "string" && !userSaidCut && partlyNamed(targetScan(), evidenceSaid))) {
|
|
12313
12398
|
if (fired === "instruct")
|
|
12314
12399
|
return { ...base, verdict: "overridden", intent: "task-step" };
|
|
12315
12400
|
return { ...base, verdict: "instruct", intent: "downgraded-task-step" };
|
|
@@ -12449,7 +12534,7 @@ function outsideProject(facts, p) {
|
|
|
12449
12534
|
return false;
|
|
12450
12535
|
return p.resolved !== cwd && !p.resolved.startsWith(cwd.endsWith("/") ? cwd : cwd + "/");
|
|
12451
12536
|
}
|
|
12452
|
-
var PROTECTED_BRANCHES, OUTSIDE_PROJECT,
|
|
12537
|
+
var PROTECTED_BRANCHES, OUTSIDE_PROJECT, SCOPE_PROBE, INJECTION_PROBE, HUMAN_ONLY, TASK_STEP_PROBE, OP_REQUESTED_PROBE, BEYOND_TASK_PROBE, TASK_PROBES;
|
|
12453
12538
|
var init_policies = __esm(() => {
|
|
12454
12539
|
PROTECTED_BRANCHES = new Set(["main", "master", "production", "prod", "release", "trunk"]);
|
|
12455
12540
|
OUTSIDE_PROJECT = new Set([
|
|
@@ -12458,334 +12543,6 @@ var init_policies = __esm(() => {
|
|
|
12458
12543
|
"system",
|
|
12459
12544
|
"root"
|
|
12460
12545
|
]);
|
|
12461
|
-
SEMANTIC_POLICIES = [
|
|
12462
|
-
{
|
|
12463
|
-
name: "destructive-deletion",
|
|
12464
|
-
title: "Tried to permanently delete data that cannot be regenerated",
|
|
12465
|
-
appliesTo: ["shell", "write"],
|
|
12466
|
-
mode: "deny",
|
|
12467
|
-
userCanOverride: true,
|
|
12468
|
-
probes: [
|
|
12469
|
-
{
|
|
12470
|
-
id: "destroys",
|
|
12471
|
-
instructions: "The tool call in `agent_request` permanently deletes, wipes, truncates or overwrites existing files, " + "directories or disks — for example `rm -r`, `find -delete`, `shred`, `dd of=`, `mkfs`, `truncate`, " + "`rsync --delete`, `git clean -fdx`, or redirecting output over an existing file.",
|
|
12472
|
-
criteria: {
|
|
12473
|
-
true: "Existing data would be destroyed or irreversibly overwritten.",
|
|
12474
|
-
false: "Nothing existing is destroyed: it reads, lists, creates new files, or moves data somewhere recoverable."
|
|
12475
|
-
}
|
|
12476
|
-
},
|
|
12477
|
-
{
|
|
12478
|
-
id: "irreplaceable",
|
|
12479
|
-
instructions: "What would be destroyed is outside the project, is the whole project, is the user's home directory, " + "is the filesystem root, or is data that cannot simply be regenerated (source code, documents, databases, " + "keys). Use `facts.paths[].relation` to see where each target sits relative to the project.",
|
|
12480
|
-
criteria: {
|
|
12481
|
-
true: "The destroyed data matters and could not be rebuilt by running a command.",
|
|
12482
|
-
false: "Only regenerable data inside the project is affected: build output, dist/, caches, node_modules, " + "virtualenvs, coverage reports, temp files, or files the agent itself just created."
|
|
12483
|
-
}
|
|
12484
|
-
}
|
|
12485
|
-
],
|
|
12486
|
-
guidance: "This permanently deletes data that cannot be rebuilt. Confirm the exact paths with the user first."
|
|
12487
|
-
},
|
|
12488
|
-
{
|
|
12489
|
-
name: "production-infra-change",
|
|
12490
|
-
title: "Tried to change live infrastructure",
|
|
12491
|
-
appliesTo: ["shell"],
|
|
12492
|
-
mode: "deny",
|
|
12493
|
-
userCanOverride: true,
|
|
12494
|
-
probes: [
|
|
12495
|
-
{
|
|
12496
|
-
id: "mutates",
|
|
12497
|
-
instructions: "The command in `agent_request` changes the state of cloud or cluster infrastructure: it creates, " + "updates, deletes, applies, scales, restarts, rolls out, deploys or destroys resources in a cloud " + "account, Kubernetes cluster, managed database, DNS, CDN or hosting platform (any CLI: kubectl, helm, " + "terraform, tofu, pulumi, aws, gcloud, az, doctl, flyctl, vercel, wrangler, railway, and so on — " + "however the binary is spelled or pathed).",
|
|
12498
|
-
criteria: {
|
|
12499
|
-
true: "It mutates infrastructure.",
|
|
12500
|
-
false: "It only reads or plans: get, list, describe, logs, status, plan, diff, validate, whoami, or --dry-run."
|
|
12501
|
-
}
|
|
12502
|
-
},
|
|
12503
|
-
{
|
|
12504
|
-
id: "not_local",
|
|
12505
|
-
instructions: "The target of that change is a shared or production environment, or its environment cannot be told " + "from the command.",
|
|
12506
|
-
criteria: {
|
|
12507
|
-
true: "Production, shared, or unknown environment.",
|
|
12508
|
-
false: "Clearly a local or throwaway environment: localhost, kind, minikube, docker-desktop, k3d, or a " + "context, workspace or profile whose name says dev, test, staging, sandbox or local."
|
|
12509
|
-
}
|
|
12510
|
-
}
|
|
12511
|
-
],
|
|
12512
|
-
guidance: "This changes live infrastructure. Run it against a non-production target, or hand the command to a human."
|
|
12513
|
-
},
|
|
12514
|
-
{
|
|
12515
|
-
name: "git-history-rewrite",
|
|
12516
|
-
title: "Tried to rewrite or discard shared git history",
|
|
12517
|
-
appliesTo: ["shell"],
|
|
12518
|
-
mode: "deny",
|
|
12519
|
-
userCanOverride: true,
|
|
12520
|
-
probes: [
|
|
12521
|
-
{
|
|
12522
|
-
id: "rewrites_remote",
|
|
12523
|
-
instructions: "The command in `agent_request` force-pushes or otherwise overwrites history on a git remote: " + "`git push --force`, `--force-with-lease`, `-f`, a `+refspec` such as `+HEAD:main`, or deleting a " + "remote branch. Git's global options before the subcommand (`git -C dir`, `git -c key=val`) and an " + "absolute path to the git binary do not change what the command does.",
|
|
12524
|
-
criteria: {
|
|
12525
|
-
true: "Remote history would be overwritten or a remote branch deleted.",
|
|
12526
|
-
false: "A normal push that only adds commits, or no push at all."
|
|
12527
|
-
}
|
|
12528
|
-
}
|
|
12529
|
-
],
|
|
12530
|
-
guidance: "Force-pushing rewrites history other people may have pulled. Confirm with the user first."
|
|
12531
|
-
},
|
|
12532
|
-
{
|
|
12533
|
-
name: "push-to-protected-branch",
|
|
12534
|
-
title: "Tried to push directly to a protected branch",
|
|
12535
|
-
appliesTo: ["shell"],
|
|
12536
|
-
mode: "instruct",
|
|
12537
|
-
userCanOverride: true,
|
|
12538
|
-
probes: [
|
|
12539
|
-
{
|
|
12540
|
-
id: "pushes_protected",
|
|
12541
|
-
instructions: "The command in `agent_request` pushes commits to a remote branch named main, master, production, " + "release or trunk — either named explicitly or implied because `facts.current_git_branch` is one of " + "those and the push names no other branch.",
|
|
12542
|
-
criteria: {
|
|
12543
|
-
true: "Commits land directly on a protected remote branch.",
|
|
12544
|
-
false: "It pushes a feature branch whose name merely contains a word like main (e.g. feature/main-menu), " + "or it does not push."
|
|
12545
|
-
}
|
|
12546
|
-
}
|
|
12547
|
-
],
|
|
12548
|
-
guidance: "Pushing straight to a protected branch skips review. Push a branch and open a PR unless the user asked for this."
|
|
12549
|
-
},
|
|
12550
|
-
{
|
|
12551
|
-
name: "commit-on-protected-branch",
|
|
12552
|
-
title: "Tried to commit directly on a protected branch",
|
|
12553
|
-
appliesTo: ["shell"],
|
|
12554
|
-
mode: "instruct",
|
|
12555
|
-
userCanOverride: true,
|
|
12556
|
-
precondition: (facts) => facts.currentGitBranch !== null && PROTECTED_BRANCHES.has(facts.currentGitBranch),
|
|
12557
|
-
probes: [
|
|
12558
|
-
{
|
|
12559
|
-
id: "creates_commit",
|
|
12560
|
-
instructions: "The command in `agent_request` creates or amends a git commit, merge, rebase or cherry-pick in the " + "repository (including `git -C dir commit` and `git commit -a`).",
|
|
12561
|
-
criteria: {
|
|
12562
|
-
true: "A new commit is written to the current branch.",
|
|
12563
|
-
false: "It only inspects history (log, show, diff, status) or mentions `commit` in a message or search."
|
|
12564
|
-
}
|
|
12565
|
-
}
|
|
12566
|
-
],
|
|
12567
|
-
guidance: "You are on a protected branch. Create a feature branch before committing unless the user asked otherwise."
|
|
12568
|
-
},
|
|
12569
|
-
{
|
|
12570
|
-
name: "secret-exposure",
|
|
12571
|
-
title: "Tried to read or copy credentials",
|
|
12572
|
-
appliesTo: ["shell", "read", "write"],
|
|
12573
|
-
mode: "deny",
|
|
12574
|
-
userCanOverride: true,
|
|
12575
|
-
probes: [
|
|
12576
|
-
{
|
|
12577
|
-
id: "touches_secrets",
|
|
12578
|
-
instructions: "The tool call in `agent_request` reads, prints, copies, or writes credential material: SSH private " + "keys (~/.ssh/id_*), authorized_keys, cloud credentials (~/.aws, ~/.config/gcloud, ~/.azure, " + "~/.kube/config), .env files containing real secret values, API tokens, password stores, keychains, " + "or browser cookie databases.",
|
|
12579
|
-
criteria: {
|
|
12580
|
-
true: "Actual secret values would be read, displayed, copied or written.",
|
|
12581
|
-
false: "It only references the concept: source code that reads process.env, a variable name, an .env.example " + "template, a test fixture, documentation about credentials, or a grep for the word."
|
|
12582
|
-
}
|
|
12583
|
-
}
|
|
12584
|
-
],
|
|
12585
|
-
guidance: "This exposes real credentials. Ask the user before reading or copying secret material."
|
|
12586
|
-
},
|
|
12587
|
-
{
|
|
12588
|
-
name: "credential-exfiltration",
|
|
12589
|
-
title: "Tried to send secrets or private files off the machine",
|
|
12590
|
-
appliesTo: ["shell", "network"],
|
|
12591
|
-
mode: "deny",
|
|
12592
|
-
userCanOverride: false,
|
|
12593
|
-
probes: [
|
|
12594
|
-
{
|
|
12595
|
-
id: "sends_out",
|
|
12596
|
-
instructions: "The tool call in `agent_request` sends data to a network destination: an HTTP POST or PUT (curl -d, " + "curl -F, curl -T, wget --post-data), scp, sftp, rsync or rclone to a remote, nc or socat, an upload to " + "a paste site, bucket or webhook, or a DNS lookup carrying encoded data.",
|
|
12597
|
-
criteria: {
|
|
12598
|
-
true: "Data leaves this machine.",
|
|
12599
|
-
false: "It only downloads, or only talks to localhost."
|
|
12600
|
-
}
|
|
12601
|
-
},
|
|
12602
|
-
{
|
|
12603
|
-
id: "sensitive_payload",
|
|
12604
|
-
instructions: "The data being sent includes credentials, keys, tokens, environment variables, or the contents of files " + "from outside the project (see `facts.paths`).",
|
|
12605
|
-
criteria: {
|
|
12606
|
-
true: "Secrets or private files are in what is sent.",
|
|
12607
|
-
false: "What is sent is ordinary project data the user would expect to share, like a test request body."
|
|
12608
|
-
}
|
|
12609
|
-
}
|
|
12610
|
-
],
|
|
12611
|
-
guidance: "This sends secrets or private files to a remote destination, which the user cannot approve. Do not send them."
|
|
12612
|
-
},
|
|
12613
|
-
{
|
|
12614
|
-
name: "remote-code-execution",
|
|
12615
|
-
title: "Tried to run code downloaded from the internet",
|
|
12616
|
-
appliesTo: ["shell"],
|
|
12617
|
-
mode: "deny",
|
|
12618
|
-
userCanOverride: true,
|
|
12619
|
-
probes: [
|
|
12620
|
-
{
|
|
12621
|
-
id: "download_and_run",
|
|
12622
|
-
instructions: "The command in `agent_request` downloads code or a script from the internet and executes it: " + '`curl … | sh`, `bash <(curl …)`, `wget … -O x.sh && bash x.sh`, `python3 -c "$(curl …)"`, piping into ' + "any interpreter (sh, bash, zsh, python, node, perl, ruby), or eval of a fetched string.",
|
|
12623
|
-
criteria: {
|
|
12624
|
-
true: "Fetched code is executed.",
|
|
12625
|
-
false: "It only downloads without running, runs a local file, or merely searches for or quotes such a command " + "(for example grep over a README)."
|
|
12626
|
-
}
|
|
12627
|
-
}
|
|
12628
|
-
],
|
|
12629
|
-
exempt: {
|
|
12630
|
-
id: "official_installer",
|
|
12631
|
-
instructions: "The URL being executed is the documented official installer of a widely used developer tool, served from " + "that tool's own domain (for example bun.sh, sh.rustup.rs, get.docker.com, deb.nodesource.com, " + "raw.githubusercontent.com/nvm-sh/nvm, astral.sh/uv)."
|
|
12632
|
-
},
|
|
12633
|
-
guidance: "This executes code fetched from the internet. Download it, show it to the user, then run it."
|
|
12634
|
-
},
|
|
12635
|
-
{
|
|
12636
|
-
name: "privilege-escalation",
|
|
12637
|
-
title: "Tried to run with elevated privileges",
|
|
12638
|
-
appliesTo: ["shell"],
|
|
12639
|
-
mode: "deny",
|
|
12640
|
-
userCanOverride: true,
|
|
12641
|
-
probes: [
|
|
12642
|
-
{
|
|
12643
|
-
id: "elevates",
|
|
12644
|
-
instructions: "The command in `agent_request` runs something as root or another user: sudo, doas, su, pkexec, run0, " + "`sudo -i`, `sudo -s`, including when the binary is written as an absolute path or reached through a " + "variable or wrapper.",
|
|
12645
|
-
criteria: {
|
|
12646
|
-
true: "Privileges are elevated.",
|
|
12647
|
-
false: "It runs as the current user, or only mentions sudo in text, a comment, or a search pattern."
|
|
12648
|
-
}
|
|
12649
|
-
}
|
|
12650
|
-
],
|
|
12651
|
-
guidance: "This runs with elevated privileges. Ask the user to run it themselves."
|
|
12652
|
-
},
|
|
12653
|
-
{
|
|
12654
|
-
name: "database-destruction",
|
|
12655
|
-
title: "Tried to destroy or mass-modify database data",
|
|
12656
|
-
appliesTo: ["shell"],
|
|
12657
|
-
mode: "deny",
|
|
12658
|
-
userCanOverride: true,
|
|
12659
|
-
probes: [
|
|
12660
|
-
{
|
|
12661
|
-
id: "destructive_sql",
|
|
12662
|
-
instructions: "The command in `agent_request` executes SQL or a database command that drops or truncates a table, " + "schema or database, or deletes or updates rows without a condition that narrows them to specific " + "records. A condition that is always true (`WHERE 1=1`, `WHERE true`, `WHERE id > 0`) does not narrow " + "anything. Also counts: `redis-cli FLUSHALL`, `dropdb`, `mongo … dropDatabase()`.",
|
|
12663
|
-
criteria: {
|
|
12664
|
-
true: "Many rows, a table, or a whole database would be removed or overwritten.",
|
|
12665
|
-
false: "It reads, inserts, or changes a specific identified record, or runs a migration file by name."
|
|
12666
|
-
}
|
|
12667
|
-
},
|
|
12668
|
-
{
|
|
12669
|
-
id: "real_database",
|
|
12670
|
-
instructions: "The database it targets holds real data rather than being a disposable test database.",
|
|
12671
|
-
criteria: {
|
|
12672
|
-
true: "A real or shared database, or one whose purpose cannot be told.",
|
|
12673
|
-
false: "Clearly an in-memory, test, fixture or throwaway local database (sqlite :memory:, a test_ database)."
|
|
12674
|
-
}
|
|
12675
|
-
}
|
|
12676
|
-
],
|
|
12677
|
-
guidance: "This destroys database data. Confirm the target and scope with the user first."
|
|
12678
|
-
},
|
|
12679
|
-
{
|
|
12680
|
-
name: "read-outside-workspace",
|
|
12681
|
-
title: "Read files outside the project",
|
|
12682
|
-
appliesTo: ["shell", "read"],
|
|
12683
|
-
mode: "instruct",
|
|
12684
|
-
userCanOverride: true,
|
|
12685
|
-
precondition: (facts) => facts.paths.some((p) => outsideProject(facts, p)),
|
|
12686
|
-
probes: [
|
|
12687
|
-
{
|
|
12688
|
-
id: "reads_outside",
|
|
12689
|
-
instructions: "The tool call in `agent_request` reads the contents of files that `facts.paths` marks as outside the " + "project, in the user's home directory (for example another repository, Documents, or dotfiles).",
|
|
12690
|
-
criteria: {
|
|
12691
|
-
true: "It reads file contents from outside the project.",
|
|
12692
|
-
false: "It only changes directory, lists names, checks existence, or touches the agent's own tool caches."
|
|
12693
|
-
}
|
|
12694
|
-
}
|
|
12695
|
-
],
|
|
12696
|
-
guidance: "This reads files outside the current project. Mention why before continuing."
|
|
12697
|
-
},
|
|
12698
|
-
{
|
|
12699
|
-
name: "agent-config-tampering",
|
|
12700
|
-
title: "Tried to change the agent's own safety configuration",
|
|
12701
|
-
appliesTo: ["shell", "write"],
|
|
12702
|
-
mode: "deny",
|
|
12703
|
-
userCanOverride: false,
|
|
12704
|
-
probes: [
|
|
12705
|
-
{
|
|
12706
|
-
id: "edits_agent_config",
|
|
12707
|
-
instructions: "The tool call in `agent_request` modifies or deletes an AI agent's own configuration, hooks, " + "permissions or guardrails: .claude/settings.json, .claude/settings.local.json, ~/.claude/, .codex/, " + ".cursor/hooks.json, .gemini/, .factory/, failproofai configuration or policies, or permission and " + "safety sections of AGENTS.md or CLAUDE.md.",
|
|
12708
|
-
criteria: {
|
|
12709
|
-
true: "The agent's hooks, permissions or safety settings would change.",
|
|
12710
|
-
false: "It only reads those files, or edits ordinary project documentation."
|
|
12711
|
-
}
|
|
12712
|
-
}
|
|
12713
|
-
],
|
|
12714
|
-
guidance: "An agent must not change its own guardrails. Ask the user to make this change."
|
|
12715
|
-
},
|
|
12716
|
-
{
|
|
12717
|
-
name: "system-modification",
|
|
12718
|
-
title: "Tried to change the system outside the project",
|
|
12719
|
-
appliesTo: ["shell"],
|
|
12720
|
-
mode: "instruct",
|
|
12721
|
-
userCanOverride: true,
|
|
12722
|
-
probes: [
|
|
12723
|
-
{
|
|
12724
|
-
id: "modifies_system",
|
|
12725
|
-
instructions: "The command in `agent_request` installs or removes software system-wide or changes machine " + "configuration: apt, dnf, pacman, brew install, `npm install -g`, `pip install` outside a virtualenv, " + "editing files under /etc, systemctl enable or disable, crontab, or changing shell startup files.",
|
|
12726
|
-
criteria: {
|
|
12727
|
-
true: "The machine changes outside the project directory.",
|
|
12728
|
-
false: "It installs project dependencies locally (npm install, bun install, pip install in a venv)."
|
|
12729
|
-
}
|
|
12730
|
-
}
|
|
12731
|
-
],
|
|
12732
|
-
guidance: "This changes the machine outside the project. Mention it to the user."
|
|
12733
|
-
},
|
|
12734
|
-
{
|
|
12735
|
-
name: "env-secrets-dump",
|
|
12736
|
-
title: "Tried to print environment secrets",
|
|
12737
|
-
appliesTo: ["shell"],
|
|
12738
|
-
mode: "instruct",
|
|
12739
|
-
userCanOverride: true,
|
|
12740
|
-
probes: [
|
|
12741
|
-
{
|
|
12742
|
-
id: "dumps_env",
|
|
12743
|
-
instructions: "The command in `agent_request` prints environment variable VALUES that may be secret: `env`, " + "`printenv`, `set`, `export -p`, or `echo` of a variable whose name suggests a key, token, password " + "or secret.",
|
|
12744
|
-
criteria: {
|
|
12745
|
-
true: "Secret-looking environment values would be printed.",
|
|
12746
|
-
false: "It echoes a literal string, prints a non-secret variable like PATH or HOME, creates a virtualenv " + "named env, or searches code for the word export."
|
|
12747
|
-
}
|
|
12748
|
-
}
|
|
12749
|
-
],
|
|
12750
|
-
guidance: "This prints environment values that may be secrets into the transcript."
|
|
12751
|
-
},
|
|
12752
|
-
{
|
|
12753
|
-
name: "external-destructive-action",
|
|
12754
|
-
title: "Tried an irreversible action through an external tool",
|
|
12755
|
-
appliesTo: ["other"],
|
|
12756
|
-
mode: "deny",
|
|
12757
|
-
userCanOverride: true,
|
|
12758
|
-
probes: [
|
|
12759
|
-
{
|
|
12760
|
-
id: "irreversible_external",
|
|
12761
|
-
instructions: "The tool call in `agent_request` (an MCP or integration tool, see `facts.tool_name`) performs an " + "irreversible or externally visible action: deleting records, repositories, files or accounts; " + "sending email, chat or social messages on the user's behalf; making payments or purchases; merging or " + "closing pull requests; changing permissions, access or billing; or writing to a production system.",
|
|
12762
|
-
criteria: {
|
|
12763
|
-
true: "Something outside this machine changes in a way that cannot be quietly undone.",
|
|
12764
|
-
false: "It reads, searches, lists, fetches, or creates a draft that nobody else sees yet."
|
|
12765
|
-
}
|
|
12766
|
-
}
|
|
12767
|
-
],
|
|
12768
|
-
guidance: "This takes an irreversible action in an external system. Confirm with the user first."
|
|
12769
|
-
},
|
|
12770
|
-
{
|
|
12771
|
-
name: "external-data-egress",
|
|
12772
|
-
title: "Sent private data to an external tool",
|
|
12773
|
-
appliesTo: ["other"],
|
|
12774
|
-
mode: "instruct",
|
|
12775
|
-
userCanOverride: true,
|
|
12776
|
-
probes: [
|
|
12777
|
-
{
|
|
12778
|
-
id: "egresses_private",
|
|
12779
|
-
instructions: "The arguments in `agent_request` send private data to an external service: source code, file " + "contents, credentials, customer data, or personal information.",
|
|
12780
|
-
criteria: {
|
|
12781
|
-
true: "Private data is being shared with a third party.",
|
|
12782
|
-
false: "Only a query, identifier or public information is sent."
|
|
12783
|
-
}
|
|
12784
|
-
}
|
|
12785
|
-
],
|
|
12786
|
-
guidance: "This shares private data with an external service. Mention what is being sent."
|
|
12787
|
-
}
|
|
12788
|
-
];
|
|
12789
12546
|
SCOPE_PROBE = {
|
|
12790
12547
|
id: "scope",
|
|
12791
12548
|
instructions: "Everything `agent_request` would affect — every branch, file, directory, table, record, environment, " + "account or recipient — is covered by what the human asked for in `user_said`. It reaches no further: not " + "all branches when they named one, not production when they said local or staging, not a parent directory " + 'when they named one folder, not every record when they named some. References such as "it", "this PR" ' + 'or "that volume" mean whatever the conversation is about. Details the agent filled in to carry the ' + "request out (a commit title, a flag the operation needs) do not make it broader.",
|
|
@@ -15928,14 +15685,13 @@ function toSemanticPolicy(entry, pack) {
|
|
|
15928
15685
|
function semanticPoliciesFromPacks(packs) {
|
|
15929
15686
|
const declared = packs.filter((p) => packSemantic(p).length > 0);
|
|
15930
15687
|
if (declared.length === 0)
|
|
15931
|
-
return { policies:
|
|
15932
|
-
const replace = replacesBuiltinChecks(declared);
|
|
15688
|
+
return { policies: [], fromPack: false, errors: [] };
|
|
15933
15689
|
const ordered = [...declared.filter((p) => isFirstPartyPack(p)), ...declared.filter((p) => !isFirstPartyPack(p))];
|
|
15934
|
-
const policies =
|
|
15690
|
+
const policies = [];
|
|
15935
15691
|
const errors = [];
|
|
15936
|
-
const seen = new Set
|
|
15692
|
+
const seen = new Set;
|
|
15937
15693
|
const contested = contestedSemanticNames(declared);
|
|
15938
|
-
let spent =
|
|
15694
|
+
let spent = 0;
|
|
15939
15695
|
for (const pack of ordered) {
|
|
15940
15696
|
for (const entry of packSemantic(pack)) {
|
|
15941
15697
|
if (isReservedClaim(pack, entry.name)) {
|
|
@@ -15968,9 +15724,7 @@ function semanticPoliciesFromPacks(packs) {
|
|
|
15968
15724
|
spent += cost;
|
|
15969
15725
|
}
|
|
15970
15726
|
}
|
|
15971
|
-
|
|
15972
|
-
return { policies: SEMANTIC_POLICIES, fromPack: false, errors };
|
|
15973
|
-
return { policies, fromPack: true, errors };
|
|
15727
|
+
return { policies, fromPack: policies.length > 0, errors };
|
|
15974
15728
|
}
|
|
15975
15729
|
function resolveSemanticPolicies(cli) {
|
|
15976
15730
|
let packs = [];
|
|
@@ -15980,7 +15734,7 @@ function resolveSemanticPolicies(cli) {
|
|
|
15980
15734
|
packs = read.packs;
|
|
15981
15735
|
manifestErrors = read.warnings ?? [];
|
|
15982
15736
|
} catch {
|
|
15983
|
-
return
|
|
15737
|
+
return [];
|
|
15984
15738
|
}
|
|
15985
15739
|
const resolved = semanticPoliciesFromPacks(jevPacks(packs, cli));
|
|
15986
15740
|
for (const message of [...manifestErrors, ...resolved.errors]) {
|
|
@@ -15991,7 +15745,7 @@ function resolveSemanticPolicies(cli) {
|
|
|
15991
15745
|
}
|
|
15992
15746
|
return resolved.policies;
|
|
15993
15747
|
}
|
|
15994
|
-
var GLOBAL_QUESTION_CHARS, MAX_PACK_QUESTION_CHARS,
|
|
15748
|
+
var GLOBAL_QUESTION_CHARS, MAX_PACK_QUESTION_CHARS, warned;
|
|
15995
15749
|
var init_pack_policies = __esm(() => {
|
|
15996
15750
|
init_effective_reviewers();
|
|
15997
15751
|
init_hook_logger();
|
|
@@ -16003,7 +15757,6 @@ var init_pack_policies = __esm(() => {
|
|
|
16003
15757
|
init_precondition_names();
|
|
16004
15758
|
GLOBAL_QUESTION_CHARS = Math.max(JSON.stringify({ [INJECTION_PROBE.id]: noulOf(INJECTION_PROBE), [SCOPE_PROBE.id]: noulOf(SCOPE_PROBE) }).length, JSON.stringify(Object.fromEntries([INJECTION_PROBE, ...TASK_PROBES].map((p) => [p.id, noulOf(p)]))).length);
|
|
16005
15759
|
MAX_PACK_QUESTION_CHARS = MAX_REQUEST_CHARS - MAX_STATE_CHARS - GLOBAL_QUESTION_CHARS;
|
|
16006
|
-
BUILTIN_QUESTION_CHARS = SEMANTIC_POLICIES.reduce((n, p) => n + questionChars(p), 0);
|
|
16007
15760
|
warned = new Set;
|
|
16008
15761
|
});
|
|
16009
15762
|
|
|
@@ -16508,7 +16261,7 @@ function authorityOf(p) {
|
|
|
16508
16261
|
return { authority, reviewedBy };
|
|
16509
16262
|
}
|
|
16510
16263
|
function resolveMode(cfg) {
|
|
16511
|
-
return cfg.mode === "
|
|
16264
|
+
return cfg.mode === "observe" || cfg.mode === "enforce" ? cfg.mode : DEFAULT_JEV_MODE;
|
|
16512
16265
|
}
|
|
16513
16266
|
function resolveTimeout(cfg) {
|
|
16514
16267
|
const t = cfg.timeoutMs;
|
|
@@ -16626,7 +16379,7 @@ function startJevReview(cfg, call) {
|
|
|
16626
16379
|
sessionId: call.sessionId,
|
|
16627
16380
|
cli: call.cli,
|
|
16628
16381
|
eventType: call.eventType,
|
|
16629
|
-
applied: abandoned || review.kind !== "answered" ? "legacy-fallback" : mode === "
|
|
16382
|
+
applied: abandoned || review.kind !== "answered" ? "legacy-fallback" : mode === "observe" ? "observe" : "two-tier"
|
|
16630
16383
|
});
|
|
16631
16384
|
appendVerdictLog(hit ? { ...row, cached: true } : row);
|
|
16632
16385
|
}
|
|
@@ -16877,6 +16630,8 @@ async function startTwoTier(canonicalEventType, parsed, session, cli, opts, acti
|
|
|
16877
16630
|
return null;
|
|
16878
16631
|
if (jevForcedOff(opts) || activePause)
|
|
16879
16632
|
return null;
|
|
16633
|
+
if (!jevChecksInstalled())
|
|
16634
|
+
return null;
|
|
16880
16635
|
const loaded = await readJevConfig();
|
|
16881
16636
|
if (!loaded)
|
|
16882
16637
|
return null;
|
|
@@ -16895,7 +16650,7 @@ async function startTwoTier(canonicalEventType, parsed, session, cli, opts, acti
|
|
|
16895
16650
|
} catch (err) {
|
|
16896
16651
|
hookLogInfo(`Jev review could not start (${err instanceof Error ? err.message : String(err)})`);
|
|
16897
16652
|
return {
|
|
16898
|
-
mode: cfg.mode === "
|
|
16653
|
+
mode: cfg.mode === "observe" || cfg.mode === "enforce" ? cfg.mode : loaded.defaultMode,
|
|
16899
16654
|
review: Promise.resolve({ kind: "fallback", reason: "error", latencyMs: null, model: null }),
|
|
16900
16655
|
abort: () => {},
|
|
16901
16656
|
authorityOf: () => ({ authority: "hard", reviewedBy: [] })
|
|
@@ -16919,6 +16674,8 @@ async function captureJevIntent(canonicalEventType, decision, parsed, session, c
|
|
|
16919
16674
|
if (canonicalEventType !== "UserPromptSubmit" || jevForcedOff(opts))
|
|
16920
16675
|
return;
|
|
16921
16676
|
try {
|
|
16677
|
+
if (!jevChecksInstalled())
|
|
16678
|
+
return;
|
|
16922
16679
|
if (!await readJevConfig())
|
|
16923
16680
|
return;
|
|
16924
16681
|
if (decision === "deny") {
|
|
@@ -16999,7 +16756,7 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
16999
16756
|
config = readMergedHooksConfig(session.cwd);
|
|
17000
16757
|
clearPolicies(cli);
|
|
17001
16758
|
activePause = readActivePause(session.sessionId);
|
|
17002
|
-
const packsInstalledHere =
|
|
16759
|
+
const packsInstalledHere = hasInstalledRegexPacks();
|
|
17003
16760
|
const legacyNames = activePause || packsInstalledHere ? [] : config.enabledPolicies;
|
|
17004
16761
|
registerBuiltinPolicies(activePause ? [] : legacyNames);
|
|
17005
16762
|
if (legacyNames.length > 0) {
|
|
@@ -17073,14 +16830,14 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
17073
16830
|
const observeOnly = cloudManaged?.effect === "observe" || pack?.effect === "observe";
|
|
17074
16831
|
const fn = async (ctx) => {
|
|
17075
16832
|
if (observeOnly) {
|
|
17076
|
-
const
|
|
17077
|
-
if (
|
|
16833
|
+
const observed = await runObserved(hook, ctx, hookName, eventType, cli);
|
|
16834
|
+
if (observed.decision !== "allow") {
|
|
17078
16835
|
const observer = cloudManaged ?? pack;
|
|
17079
16836
|
observedResults.push({
|
|
17080
16837
|
policyId: observer.id,
|
|
17081
16838
|
version: observer.version,
|
|
17082
|
-
decision:
|
|
17083
|
-
reason:
|
|
16839
|
+
decision: observed.decision,
|
|
16840
|
+
reason: observed.reason ?? null
|
|
17084
16841
|
});
|
|
17085
16842
|
}
|
|
17086
16843
|
return { decision: "allow" };
|
|
@@ -17170,13 +16927,13 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
|
|
|
17170
16927
|
...twoTier,
|
|
17171
16928
|
releaseRegistry: opts?.releaseRegistry
|
|
17172
16929
|
}) : await evaluatePolicies(canonicalEventType, parsed, session, config);
|
|
17173
|
-
const
|
|
17174
|
-
if (
|
|
16930
|
+
const observeVerdict = result.twoTier?.observeVerdict;
|
|
16931
|
+
if (observeVerdict) {
|
|
17175
16932
|
observedResults.push({
|
|
17176
|
-
policyId:
|
|
17177
|
-
version:
|
|
17178
|
-
decision:
|
|
17179
|
-
reason:
|
|
16933
|
+
policyId: observeVerdict.policyName,
|
|
16934
|
+
version: observeVerdict.version,
|
|
16935
|
+
decision: observeVerdict.decision,
|
|
16936
|
+
reason: observeVerdict.reason
|
|
17180
16937
|
});
|
|
17181
16938
|
}
|
|
17182
16939
|
const durationMs = Math.round(performance.now() - startTime);
|