failproofai 1.0.7-beta.2 → 1.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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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 +7 -8
- 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 +5 -7
- 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 +6 -6
- 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 +11 -13
- 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 +7 -8
- 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 +7 -7
- 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 +6 -7
- 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 -41
- package/.next/standalone/.next/server/app/settings/page.js +9 -12
- 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/{[externals]__1j-zsg5._.js → [externals]__1_bftcl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__19_pzeq._.js → [externals]__1msfs-h._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1bf34x4._.js → [root-of-the-server]__1_r2rbg._.js} +7 -5
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +21 -21
- 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/{_1q5i8mb._.js → _1c3k-8x._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__0bd3mje._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__0cxe_2_._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1w_5l7t._.js → _0h_douw._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-i_gzc._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _166t73i._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1q46vxx._.js → _1_qswah._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifp._.js → _1es2j7i._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +3 -0
- 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 +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +10 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.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 -56
- package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +1 -0
- package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 0__8a7m868fvf.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 0o6qlkgubtoex.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 13i7-9is-vhys.js} +1 -1
- package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +6 -0
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +1 -0
- package/.next/standalone/.next/static/chunks/{2_pltstd8-xgs.js → 2klitrtzpaoe0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 2mdh397ghgnvv.js} +1 -1
- package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +2 -0
- package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +1 -0
- package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +69 -0
- package/.next/standalone/.next/static/chunks/{1qd741hzlmjbo.js → 43ufqrz8qo3h-.js} +1 -1
- package/.next/standalone/SECURITY.md +53 -0
- package/.next/standalone/app/actions/pack-actions.ts +0 -12
- package/.next/standalone/app/policies/hooks-client.tsx +0 -9
- package/.next/standalone/app/settings/page.tsx +1 -20
- package/.next/standalone/app/settings/settings-client.tsx +1 -27
- package/.next/standalone/app/settings/settings.css +0 -79
- package/.next/standalone/package.json +10 -10
- package/.next/standalone/sdk/typescript/CHANGELOG.md +15 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +10 -30
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +3 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -16
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +3 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +13 -132
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -142
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +4 -0
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +1396 -1016
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +9 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +2 -2
- package/bin/failproofai.mjs +2 -115
- package/dist/cli.mjs +6543 -13872
- package/dist/index.js +1 -19
- package/dist/worker.mjs +2022 -8055
- package/package.json +10 -10
- package/pi-extension/index.ts +0 -11
- package/scripts/build-policy-pack.mjs +2 -53
- package/src/audit/features.ts +2 -3
- package/src/hooks/builtin-policies.ts +8 -177
- package/src/hooks/cloud-enrollment-cli.ts +1 -1
- package/src/hooks/cloud-managed-policies.ts +0 -22
- package/src/hooks/custom-hooks-loader.ts +7 -45
- package/src/hooks/custom-hooks-registry.ts +1 -45
- package/src/hooks/first-run-gate.ts +0 -5
- package/src/hooks/fp-home.ts +0 -23
- package/src/hooks/handler.ts +6 -265
- package/src/hooks/hook-activity-store.ts +1 -105
- package/src/hooks/hook-telemetry.ts +0 -41
- package/src/hooks/loader-utils.ts +0 -6
- package/src/hooks/manager.ts +1 -1
- package/src/hooks/pack-cli.ts +19 -412
- package/src/hooks/pack-manifest.ts +7 -479
- package/src/hooks/pack-store.ts +11 -157
- package/src/hooks/policy-catalog.ts +0 -204
- package/src/hooks/policy-evaluator.ts +796 -940
- package/src/hooks/policy-registry.ts +0 -25
- package/src/hooks/policy-types.ts +0 -126
- package/src/hooks/types.ts +1 -1
- package/src/hooks/worker-server.ts +26 -119
- package/src/index.ts +0 -6
- package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
- package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qxnccm._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0soxz2z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +0 -151
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
- package/.next/standalone/.next/static/chunks/043j99m8ykg__.css +0 -2
- package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +0 -1
- package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
- package/.next/standalone/.next/static/chunks/3otmypm6j_xfo.js +0 -6
- package/.next/standalone/.next/static/chunks/3yxro_r2_o9ad.js +0 -69
- package/.next/standalone/PROBE-FOLLOWUP.md +0 -186
- package/.next/standalone/app/actions/get-jev-config.ts +0 -424
- package/.next/standalone/app/actions/update-jev-config.ts +0 -420
- package/.next/standalone/app/components/jev-notices.tsx +0 -96
- package/.next/standalone/app/settings/jev-panel.tsx +0 -473
- package/src/hooks/effective-reviewers.ts +0 -79
- package/src/hooks/jev-activity.ts +0 -385
- package/src/hooks/jev-cli.ts +0 -1249
- package/src/hooks/policy-authority.ts +0 -333
- package/src/hooks/policy-reviewability.ts +0 -229
- package/src/hooks/semantic/combine.ts +0 -541
- package/src/hooks/semantic/compile.ts +0 -176
- package/src/hooks/semantic/decide.ts +0 -392
- package/src/hooks/semantic/envelope.ts +0 -1296
- package/src/hooks/semantic/evaluator.ts +0 -547
- package/src/hooks/semantic/facts.ts +0 -292
- package/src/hooks/semantic/intent.ts +0 -1190
- package/src/hooks/semantic/jev-client.ts +0 -643
- package/src/hooks/semantic/jev-config.ts +0 -594
- package/src/hooks/semantic/jev-review.ts +0 -374
- package/src/hooks/semantic/jev-stats.ts +0 -289
- package/src/hooks/semantic/jev-throttle.ts +0 -421
- package/src/hooks/semantic/pack-policies.ts +0 -251
- package/src/hooks/semantic/policies.ts +0 -596
- package/src/hooks/semantic/precondition-names.ts +0 -60
- package/src/hooks/semantic/preconditions.ts +0 -58
- package/src/hooks/semantic/redact.ts +0 -2910
- package/src/hooks/semantic/types.ts +0 -145
- package/src/hooks/semver-precedence.ts +0 -128
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → PgeWCHmyVbjRznv2VO7KF}/_ssgManifest.js +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.7
|
|
4
|
-
"description": "Observability and enforcement for AI agent harnesses.
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
7
7
|
"failproofaid": "./bin/failproofaid-shim.mjs"
|
|
@@ -122,15 +122,15 @@
|
|
|
122
122
|
"browserslist": "4.28.8"
|
|
123
123
|
},
|
|
124
124
|
"optionalDependencies": {
|
|
125
|
-
"@failproofai/failproofaid-linux-x64": "1.0.7
|
|
126
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.7
|
|
127
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.7
|
|
128
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.7
|
|
125
|
+
"@failproofai/failproofaid-linux-x64": "1.0.7",
|
|
126
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.7",
|
|
127
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.7",
|
|
128
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.7"
|
|
129
129
|
},
|
|
130
130
|
"failproofaidBinaries": {
|
|
131
|
-
"linux-x64": "
|
|
132
|
-
"linux-arm64": "
|
|
133
|
-
"darwin-x64": "
|
|
134
|
-
"darwin-arm64": "
|
|
131
|
+
"linux-x64": "65acfcabaa4dda6d37d2958d4100c366a85f17c68d2250de6126e2717ca773e6",
|
|
132
|
+
"linux-arm64": "cece883f87d53034003be8f0f293aa89bd0e69306d6032e17b3eb91f230b3ce2",
|
|
133
|
+
"darwin-x64": "565aa3d560a05dd76e2eb843ddbad593d2d8df94c963bdeb1c7e2a8a3816d0c5",
|
|
134
|
+
"darwin-arm64": "2b41601b53e77a31439e92d8be9be6b9872e3d4ad7e8664ff89cd2be4ed6c44d"
|
|
135
135
|
}
|
|
136
136
|
}
|
package/pi-extension/index.ts
CHANGED
|
@@ -404,17 +404,6 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
404
404
|
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
405
405
|
cwd: resolveCwd(e.cwd),
|
|
406
406
|
hook_event_name: "UserPromptSubmit",
|
|
407
|
-
// "interactive" | "rpc" | "extension": which channel the input came
|
|
408
|
-
// through, and the only mark Pi gives about who wrote the prompt. It
|
|
409
|
-
// names the channel, not the author — `pi -p "<text>"` reports
|
|
410
|
-
// `interactive` exactly as a person typing does — so the Jev evaluator
|
|
411
|
-
// uses it only in the direction it is reliable in (`piMachineTurn`,
|
|
412
|
-
// src/hooks/semantic/intent.ts): `extension`, another extension's
|
|
413
|
-
// `sendUserMessage()` whose text can be model-written or repo-derived,
|
|
414
|
-
// is refused, while `interactive`, `rpc` and an absent value are all
|
|
415
|
-
// recorded as the human's request. Policies read it too, and a later Pi
|
|
416
|
-
// build may say more here.
|
|
417
|
-
input_source: e.source,
|
|
418
407
|
});
|
|
419
408
|
if (decision.block) {
|
|
420
409
|
console.error(`[failproofai] prompt blocked: ${decision.reason ?? "blocked by policy"}`);
|
|
@@ -31,24 +31,6 @@
|
|
|
31
31
|
* that stops an agent disabling failproofai, it ships compiled in, and
|
|
32
32
|
* `pack-manifest.ts` REFUSES any pack that declares `alwaysOn`. A pack containing
|
|
33
33
|
* it would be rejected by our own loader — correctly.
|
|
34
|
-
*
|
|
35
|
-
* **Every policy states its authority** — whether Jev may clear its verdict —
|
|
36
|
-
* as the registry would resolve it (`manifestAuthority`), not as a copy of
|
|
37
|
-
* whatever the catalog happened to spell. A machine running this pack reads
|
|
38
|
-
* authority from the MANIFEST, so a field left off here is a policy that is
|
|
39
|
-
* silently hard everywhere it is installed. The entry passes the same fields to
|
|
40
|
-
* `customPolicies.add`, so a manifest rebuilt from the entry (`failproofai
|
|
41
|
-
* publish`) says the same thing.
|
|
42
|
-
*
|
|
43
|
-
* **It emits no `semantic` array, deliberately.** A pack that declares one
|
|
44
|
-
* REPLACES this build's compiled-in semantic set wholesale
|
|
45
|
-
* (`semantic/pack-policies.ts`), and this pack is the dress rehearsal for the
|
|
46
|
-
* regex floor leaving the package — not for Jev's question set leaving it. So
|
|
47
|
-
* the 39 builtins travel and the 16 semantic policies stay compiled in, which
|
|
48
|
-
* also keeps `manifestAuthority` below judging `reviewedBy` against the right
|
|
49
|
-
* list: the builtin reviewer names, which is what a machine installing this pack
|
|
50
|
-
* will still be running. The separate `FailproofAI/jev-policies` pack is where
|
|
51
|
-
* the semantic half ships, and it declares its own `minCliVersion`.
|
|
52
34
|
*/
|
|
53
35
|
import { execFileSync } from "node:child_process";
|
|
54
36
|
import { createHash } from "node:crypto";
|
|
@@ -58,7 +40,6 @@ import { fileURLToPath } from "node:url";
|
|
|
58
40
|
// The one place the pack's id is decided. Imported rather than restated — see
|
|
59
41
|
// PACK_ID below for what restating it cost.
|
|
60
42
|
import { CORE_SOURCE } from "../src/hooks/pack-store.ts";
|
|
61
|
-
import { manifestAuthority } from "../src/hooks/policy-authority.ts";
|
|
62
43
|
|
|
63
44
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
64
45
|
// `--out <dir>` lets the conformance test generate into a temp directory rather
|
|
@@ -114,23 +95,6 @@ for (const policy of BUILTIN_POLICIES) {
|
|
|
114
95
|
defaultEnabled: policy.defaultEnabled === true,
|
|
115
96
|
match: policy.match,
|
|
116
97
|
fn: policy.fn,
|
|
117
|
-
// The parameter schema. It has to be registered here as well as written
|
|
118
|
-
// into the manifest this script builds, because there are TWO manifest
|
|
119
|
-
// producers for this pack and they have to agree: this script builds one
|
|
120
|
-
// from POLICY_CATALOG, and the publish command rebuilds one by importing
|
|
121
|
-
// the entry and reading back what it registered. A field the entry does
|
|
122
|
-
// not register is a field the second producer cannot know about.
|
|
123
|
-
//
|
|
124
|
-
// params is the one that fails silently STRICTER. Registration reads a
|
|
125
|
-
// pack policy's schema from the manifest, so an absent schema means the
|
|
126
|
-
// policy sees empty params: block-sudo's allowPatterns, and block-rm-rf's
|
|
127
|
-
// and block-read-outside-cwd's allowPaths, stop applying -- taking the
|
|
128
|
-
// user's own configured values with them. No backticks in this comment:
|
|
129
|
-
// see the note above entrySource.
|
|
130
|
-
...(policy.params !== undefined ? { params: policy.params } : {}),
|
|
131
|
-
// Whether Jev may clear it; see the note on authority at the top.
|
|
132
|
-
...(policy.authority !== undefined ? { authority: policy.authority } : {}),
|
|
133
|
-
...(policy.reviewedBy !== undefined ? { reviewedBy: policy.reviewedBy } : {}),
|
|
134
98
|
});
|
|
135
99
|
}
|
|
136
100
|
`;
|
|
@@ -156,20 +120,7 @@ try {
|
|
|
156
120
|
// validates these with the loader's own rules, so a shape the loader would
|
|
157
121
|
// refuse fails this build rather than shipping.
|
|
158
122
|
const { POLICY_CATALOG } = await import(join(ROOT, "src/hooks/policy-catalog.ts"));
|
|
159
|
-
const policies = POLICY_CATALOG.filter((p) => !p.alwaysOn).map((p) => {
|
|
160
|
-
// Throws on a declaration the registry would quietly downgrade: in the
|
|
161
|
-
// catalog that is a typo or a renamed semantic policy, and publishing it
|
|
162
|
-
// would turn a reviewed decision into an unannounced hard one. The fields
|
|
163
|
-
// come from the same call, so the check cannot be skipped without the
|
|
164
|
-
// manifest losing its authority too.
|
|
165
|
-
const resolved = manifestAuthority(p);
|
|
166
|
-
// Spread first so every field keeps its catalog position; only the two
|
|
167
|
-
// authority fields are replaced by their resolved values.
|
|
168
|
-
const entry = { ...p, authority: resolved.authority };
|
|
169
|
-
if (resolved.reviewedBy) entry.reviewedBy = resolved.reviewedBy;
|
|
170
|
-
else delete entry.reviewedBy;
|
|
171
|
-
return entry;
|
|
172
|
-
});
|
|
123
|
+
const policies = POLICY_CATALOG.filter((p) => !p.alwaysOn).map((p) => ({ ...p }));
|
|
173
124
|
|
|
174
125
|
const manifest = JSON.stringify(
|
|
175
126
|
{ id: PACK_ID, version, effect: "enforce", policies },
|
|
@@ -187,10 +138,8 @@ writeFileSync(
|
|
|
187
138
|
);
|
|
188
139
|
|
|
189
140
|
const omitted = POLICY_CATALOG.filter((p) => p.alwaysOn).map((p) => p.name);
|
|
190
|
-
const reviewable = policies.filter((p) => p.authority === "reviewable").length;
|
|
191
141
|
console.log(
|
|
192
|
-
`[policy-pack] ${PACK_ID}@${version} — ${policies.length} policies ` +
|
|
193
|
-
`(${reviewable} reviewable by Jev, ${policies.length - reviewable} hard), ` +
|
|
142
|
+
`[policy-pack] ${PACK_ID}@${version} — ${policies.length} policies, ` +
|
|
194
143
|
`${(entryBytes.length / 1024).toFixed(1)} KB entry` +
|
|
195
144
|
(omitted.length ? `; omitted alwaysOn: ${omitted.join(", ")}` : ""),
|
|
196
145
|
);
|
package/src/audit/features.ts
CHANGED
|
@@ -69,13 +69,13 @@ export const ARCHITECT_CAUTION_SIGNALS = new Set(["reread-after-edit", "redundan
|
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Mapping from policy/detector short-name → which archetype its hits feed,
|
|
72
|
-
* and how heavily (intensity within the cluster). Every one of the
|
|
72
|
+
* and how heavily (intensity within the cluster). Every one of the 39 builtin
|
|
73
73
|
* policies and 8 audit-only detectors maps exactly once — no overlaps, full
|
|
74
74
|
* coverage. Weights express *severity within* a persona; cross-persona
|
|
75
75
|
* fairness is handled later by lift normalisation, not by these numbers.
|
|
76
76
|
*/
|
|
77
77
|
export const SIGNAL_MAP: Record<string, { archetype: ArchetypeKey; weight: number }> = {
|
|
78
|
-
// ── cowboy ── destructive / forceful / bypasses guardrails (
|
|
78
|
+
// ── cowboy ── destructive / forceful / bypasses guardrails (20) ───────────
|
|
79
79
|
"block-rm-rf": { archetype: "cowboy", weight: 2.0 },
|
|
80
80
|
"block-failproofai-commands":{ archetype: "cowboy", weight: 2.0 },
|
|
81
81
|
"block-sudo": { archetype: "cowboy", weight: 1.5 },
|
|
@@ -88,7 +88,6 @@ export const SIGNAL_MAP: Record<string, { archetype: ArchetypeKey; weight: numbe
|
|
|
88
88
|
"git-commit-no-verify": { archetype: "cowboy", weight: 1.5 },
|
|
89
89
|
"warn-git-amend": { archetype: "cowboy", weight: 0.8 },
|
|
90
90
|
"warn-git-stash-drop": { archetype: "cowboy", weight: 1.0 },
|
|
91
|
-
"warn-git-clean": { archetype: "cowboy", weight: 1.0 },
|
|
92
91
|
"warn-all-files-staged": { archetype: "cowboy", weight: 0.6 },
|
|
93
92
|
"block-kubectl": { archetype: "cowboy", weight: 1.5 },
|
|
94
93
|
"block-terraform": { archetype: "cowboy", weight: 1.5 },
|
|
@@ -623,15 +623,8 @@ const GIT_DESTRUCTIVE_SUBCOMMANDS = new Set([
|
|
|
623
623
|
*/
|
|
624
624
|
const GIT_FLAGS_WITH_OPERANDS = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--exec-path"]);
|
|
625
625
|
|
|
626
|
-
/**
|
|
627
|
-
|
|
628
|
-
* walked off first, or `-1` when there is no subcommand at all.
|
|
629
|
-
*
|
|
630
|
-
* The index rather than the value, because a caller that wants to read the
|
|
631
|
-
* SUBCOMMAND's flags (`warnGitClean`) needs to know where they start, and
|
|
632
|
-
* `args.indexOf("clean")` would land on a `-C clean` operand instead.
|
|
633
|
-
*/
|
|
634
|
-
function gitSubcommandIndex(args: string[]): number {
|
|
626
|
+
/** The `git` subcommand, with git's own global options walked off first. */
|
|
627
|
+
function gitSubcommand(args: string[]): string | undefined {
|
|
635
628
|
for (let i = 0; i < args.length; i++) {
|
|
636
629
|
const arg = args[i];
|
|
637
630
|
if (GIT_FLAGS_WITH_OPERANDS.has(arg)) {
|
|
@@ -639,15 +632,9 @@ function gitSubcommandIndex(args: string[]): number {
|
|
|
639
632
|
continue;
|
|
640
633
|
}
|
|
641
634
|
if (arg.startsWith("-")) continue;
|
|
642
|
-
return
|
|
635
|
+
return arg;
|
|
643
636
|
}
|
|
644
|
-
return
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
/** The `git` subcommand, with git's own global options walked off first. */
|
|
648
|
-
function gitSubcommand(args: string[]): string | undefined {
|
|
649
|
-
const i = gitSubcommandIndex(args);
|
|
650
|
-
return i < 0 ? undefined : args[i];
|
|
637
|
+
return undefined;
|
|
651
638
|
}
|
|
652
639
|
|
|
653
640
|
/**
|
|
@@ -1392,48 +1379,6 @@ const GIT_AMEND_RE = /\bgit\s+commit\b.*--amend\b/;
|
|
|
1392
1379
|
// warnGitStashDrop
|
|
1393
1380
|
const GIT_STASH_DROP_RE = /\bgit\s+stash\s+(?:drop|clear)\b/;
|
|
1394
1381
|
|
|
1395
|
-
// warnGitClean
|
|
1396
|
-
/**
|
|
1397
|
-
* `git`, however the command line spells it. Same shape as {@link RM_CMD_RE}:
|
|
1398
|
-
* an absolute path to the binary does not change what it does.
|
|
1399
|
-
*/
|
|
1400
|
-
const GIT_CMD_RE = /^(?:\/\S*\/)?git$/;
|
|
1401
|
-
|
|
1402
|
-
/** A short-flag cluster: `-f`, `-fdx`, `-xdf`. */
|
|
1403
|
-
const SHORT_FLAG_CLUSTER_RE = /^-[A-Za-z]+$/;
|
|
1404
|
-
|
|
1405
|
-
/**
|
|
1406
|
-
* `clean.requireForce=false`, the config that makes `git clean` delete with no
|
|
1407
|
-
* `-f` at all — and settable per invocation as `git -c
|
|
1408
|
-
* clean.requireForce=false clean -xd`. Reading the subcommand's flags alone
|
|
1409
|
-
* would call that "no force, no warning" on the one spelling that needs none.
|
|
1410
|
-
*/
|
|
1411
|
-
const GIT_CLEAN_FORCE_WAIVED_RE = /^clean\.requireforce=(?:false|0|no|off)$/i;
|
|
1412
|
-
|
|
1413
|
-
/**
|
|
1414
|
-
* The default `destructiveFlags`, and why the condition is these three letters
|
|
1415
|
-
* rather than "any `git clean`".
|
|
1416
|
-
*
|
|
1417
|
-
* `git clean` deletes nothing without `-f` / `--force` (or the config above),
|
|
1418
|
-
* and `-n` / `--dry-run` only prints — so force is the precondition and
|
|
1419
|
-
* dry-run is the exemption. What force ALONE removes is untracked files in the
|
|
1420
|
-
* current directory: the narrowest spelling, and the one people mean when they
|
|
1421
|
-
* tidy up. These are the letters that widen the blast radius past that:
|
|
1422
|
-
*
|
|
1423
|
-
* - `-x` also removes IGNORED files — `.env`, local config, editor state,
|
|
1424
|
-
* scratch work. That is the data that genuinely cannot be rebuilt by running
|
|
1425
|
-
* a command, and it is what makes `git clean -fdx` the severe spelling.
|
|
1426
|
-
* - `-X` removes ONLY ignored files: the same loss, tracked tree untouched.
|
|
1427
|
-
* - `-d` also removes untracked DIRECTORIES — whole new subtrees git has never
|
|
1428
|
-
* had a copy of.
|
|
1429
|
-
*
|
|
1430
|
-
* A bare `git clean -f` is deliberately absent. It has the smallest radius and
|
|
1431
|
-
* by far the most traffic, and a guard that fires on the ordinary spelling is
|
|
1432
|
-
* one people switch off — which would cost them the `-fdx` warning too. Add
|
|
1433
|
-
* `"f"` to `destructiveFlags` to opt into it.
|
|
1434
|
-
*/
|
|
1435
|
-
const GIT_CLEAN_DESTRUCTIVE_FLAGS = ["d", "x", "X"];
|
|
1436
|
-
|
|
1437
1382
|
// warnAllFilesStaged
|
|
1438
1383
|
const GIT_ADD_ALL_RE = /\bgit\s+add\s+(?:-A\b|--all\b|\.(?:\s|$|;|&&|\|\|))/;
|
|
1439
1384
|
|
|
@@ -2270,29 +2215,17 @@ const READ_LIKE_CMDS =
|
|
|
2270
2215
|
* separator characters that appear in compound argv tokens (':' for Docker
|
|
2271
2216
|
* volume mounts and PATH-like lists, '=' for env var assignments) so that a
|
|
2272
2217
|
* suffix like '/dashboard.mdx' in 'docs/STAR/dashboard.mdx' or '/docs' in
|
|
2273
|
-
* '-v HOST_DIR:/docs' is not misread as a standalone absolute path.
|
|
2274
|
-
* that list too, so a match cannot START on the second slash of a protocol
|
|
2275
|
-
* separator: 'http://localhost:3000/x' used to yield '/localhost:3000/x'.
|
|
2276
|
-
* A match that starts on the FIRST slash is untouched, so '//etc/passwd' —
|
|
2277
|
-
* a real, working spelling of /etc/passwd — is still extracted and still
|
|
2278
|
-
* resolves to /etc/passwd.
|
|
2279
|
-
*
|
|
2280
|
-
* Runs of slashes with nothing else ('//', '///') are dropped: those are a
|
|
2281
|
-
* line-comment marker, not a directory. They used to resolve to the filesystem
|
|
2282
|
-
* root, which denied any read-like command carrying a '// …' comment — a
|
|
2283
|
-
* heredoc writing a TypeScript file into the project, say. A lone '/' is the
|
|
2284
|
-
* root and is kept.
|
|
2218
|
+
* '-v HOST_DIR:/docs' is not misread as a standalone absolute path.
|
|
2285
2219
|
*/
|
|
2286
2220
|
function extractAbsolutePaths(command: string): string[] {
|
|
2287
2221
|
const paths: string[] = [];
|
|
2288
|
-
const pathRe = /(?<![a-zA-Z0-9_
|
|
2222
|
+
const pathRe = /(?<![a-zA-Z0-9_.\-~\\*?:=])(?:~\/[^\s;|&"'()\[\]{}]*|~(?=\s|$|[;|&"'()\[\]{}])|\/[^\s;|&"'()\[\]{}]*)/g;
|
|
2289
2223
|
|
|
2290
2224
|
function addPaths(s: string): void {
|
|
2291
2225
|
pathRe.lastIndex = 0;
|
|
2292
2226
|
let m: RegExpExecArray | null;
|
|
2293
2227
|
while ((m = pathRe.exec(s)) !== null) {
|
|
2294
2228
|
let p = m[0];
|
|
2295
|
-
if (/^\/{2,}$/.test(p)) continue; // a `//` comment marker, not the root
|
|
2296
2229
|
if (p === "~") p = homedir();
|
|
2297
2230
|
else if (p.startsWith("~/")) p = join(homedir(), p.slice(2));
|
|
2298
2231
|
paths.push(p);
|
|
@@ -2583,101 +2516,6 @@ function warnGitStashDrop(ctx: PolicyContext): PolicyResult {
|
|
|
2583
2516
|
return allow();
|
|
2584
2517
|
}
|
|
2585
2518
|
|
|
2586
|
-
/** What one `git clean` argument list asks for. */
|
|
2587
|
-
interface GitCleanFlags {
|
|
2588
|
-
force: boolean;
|
|
2589
|
-
dryRun: boolean;
|
|
2590
|
-
/** Short-flag letters, with `--force` counted as `f` so widening works either way. */
|
|
2591
|
-
letters: Set<string>;
|
|
2592
|
-
}
|
|
2593
|
-
|
|
2594
|
-
/**
|
|
2595
|
-
* Read `git clean`'s own options: clusters (`-fdx`) and the two long forms
|
|
2596
|
-
* that matter (`--force`, `--dry-run`).
|
|
2597
|
-
*
|
|
2598
|
-
* Everything after `--` is a pathspec, not a flag — `git clean -n -- -fdx`
|
|
2599
|
-
* asks about a file literally called `-fdx` and deletes nothing.
|
|
2600
|
-
*/
|
|
2601
|
-
function readGitCleanFlags(args: string[]): GitCleanFlags {
|
|
2602
|
-
const letters = new Set<string>();
|
|
2603
|
-
let force = false;
|
|
2604
|
-
let dryRun = false;
|
|
2605
|
-
let sawEndOfOptions = false;
|
|
2606
|
-
for (const token of args) {
|
|
2607
|
-
if (token === "--") {
|
|
2608
|
-
sawEndOfOptions = true;
|
|
2609
|
-
continue;
|
|
2610
|
-
}
|
|
2611
|
-
if (sawEndOfOptions) continue;
|
|
2612
|
-
if (token === "--force") {
|
|
2613
|
-
force = true;
|
|
2614
|
-
continue;
|
|
2615
|
-
}
|
|
2616
|
-
if (token === "--dry-run") {
|
|
2617
|
-
dryRun = true;
|
|
2618
|
-
continue;
|
|
2619
|
-
}
|
|
2620
|
-
if (token.startsWith("--")) continue;
|
|
2621
|
-
if (!SHORT_FLAG_CLUSTER_RE.test(token)) continue;
|
|
2622
|
-
for (const letter of token.slice(1)) {
|
|
2623
|
-
if (letter === "f") force = true;
|
|
2624
|
-
if (letter === "n") dryRun = true;
|
|
2625
|
-
letters.add(letter);
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
if (force) letters.add("f");
|
|
2629
|
-
return { force, dryRun, letters };
|
|
2630
|
-
}
|
|
2631
|
-
|
|
2632
|
-
/** True when git's own `-c key=value` options waive `clean.requireForce`. */
|
|
2633
|
-
function gitCleanForceWaived(globalOptions: string[]): boolean {
|
|
2634
|
-
for (let i = 0; i < globalOptions.length; i++) {
|
|
2635
|
-
const token = globalOptions[i];
|
|
2636
|
-
const value =
|
|
2637
|
-
token === "-c" ? globalOptions[i + 1] : token.startsWith("-c") ? token.slice(2) : undefined;
|
|
2638
|
-
if (value !== undefined && GIT_CLEAN_FORCE_WAIVED_RE.test(value)) return true;
|
|
2639
|
-
}
|
|
2640
|
-
return false;
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
/**
|
|
2644
|
-
* The gap this closes: `git clean -fdx` was caught by NEITHER tier — no regex
|
|
2645
|
-
* builtin matched the string at all, and the semantic check that covers
|
|
2646
|
-
* unrecoverable deletion cannot fire on it. See the note on the catalog entry
|
|
2647
|
-
* for the measurement and for why this policy is `hard`.
|
|
2648
|
-
*/
|
|
2649
|
-
function warnGitClean(ctx: PolicyContext): PolicyResult {
|
|
2650
|
-
if (ctx.toolName !== "Bash") return allow();
|
|
2651
|
-
const destructiveFlags = ((ctx.params?.destructiveFlags ?? GIT_CLEAN_DESTRUCTIVE_FLAGS) as string[]);
|
|
2652
|
-
if (destructiveFlags.length === 0) return allow();
|
|
2653
|
-
|
|
2654
|
-
for (const segment of shellSegments(getCommand(ctx))) {
|
|
2655
|
-
const tokens = parseArgvTokens(segment);
|
|
2656
|
-
// `findIndex`, not `tokens[0]`, so an env prefix (`GIT_DIR=x git clean …`)
|
|
2657
|
-
// and an absolute binary path both still resolve to the git invocation.
|
|
2658
|
-
const gitIdx = tokens.findIndex((t) => GIT_CMD_RE.test(t));
|
|
2659
|
-
if (gitIdx < 0) continue;
|
|
2660
|
-
const args = tokens.slice(gitIdx + 1);
|
|
2661
|
-
const subIdx = gitSubcommandIndex(args);
|
|
2662
|
-
if (subIdx < 0 || args[subIdx] !== "clean") continue;
|
|
2663
|
-
|
|
2664
|
-
const flags = readGitCleanFlags(args.slice(subIdx + 1));
|
|
2665
|
-
// `-n` / `--dry-run` prints what would go and removes nothing, whatever
|
|
2666
|
-
// else is on the line — the safe inspection form, never a warning.
|
|
2667
|
-
if (flags.dryRun) continue;
|
|
2668
|
-
if (!flags.force && !gitCleanForceWaived(args.slice(0, subIdx))) continue;
|
|
2669
|
-
if (!destructiveFlags.some((f) => flags.letters.has(f))) continue;
|
|
2670
|
-
|
|
2671
|
-
return instruct(
|
|
2672
|
-
"STOP: This command deletes untracked files from the working tree (git clean). Git has no copy of " +
|
|
2673
|
-
"untracked or ignored files, so nothing it removes can be recovered: -d takes whole untracked " +
|
|
2674
|
-
"directories, and -x / -X also take .gitignore'd files such as .env, local config and scratch work. " +
|
|
2675
|
-
"Re-run it with --dry-run and confirm the exact paths with the user before executing.",
|
|
2676
|
-
);
|
|
2677
|
-
}
|
|
2678
|
-
return allow();
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
2519
|
function warnAllFilesStaged(ctx: PolicyContext): PolicyResult {
|
|
2682
2520
|
if (ctx.toolName !== "Bash") return allow();
|
|
2683
2521
|
const cmd = getCommand(ctx);
|
|
@@ -3240,7 +3078,7 @@ function requireCiGreenBeforeStop(ctx: PolicyContext): PolicyResult {
|
|
|
3240
3078
|
* Each value is the identical hoisted function object, never a wrapper. Two
|
|
3241
3079
|
* things depend on that and neither fails loudly: `audit/cache.ts` hashes
|
|
3242
3080
|
* `fn.toString()` into the audit cache's `engineVersion`, so wrapping every
|
|
3243
|
-
* entry would collapse
|
|
3081
|
+
* entry would collapse 39 distinct hashes into one and freeze the key — stale
|
|
3244
3082
|
* audit results would then be served for the full 30-day TTL with no symptom;
|
|
3245
3083
|
* and `gitBranchCache` is module-scoped, so a per-call factory would silently
|
|
3246
3084
|
* reset it on every hook event.
|
|
@@ -3271,7 +3109,6 @@ const POLICY_IMPLEMENTATIONS: Record<string, PolicyFunction> = {
|
|
|
3271
3109
|
"block-work-on-main": blockWorkOnMain,
|
|
3272
3110
|
"warn-git-amend": warnGitAmend,
|
|
3273
3111
|
"warn-git-stash-drop": warnGitStashDrop,
|
|
3274
|
-
"warn-git-clean": warnGitClean,
|
|
3275
3112
|
"warn-all-files-staged": warnAllFilesStaged,
|
|
3276
3113
|
"warn-destructive-sql": warnDestructiveSql,
|
|
3277
3114
|
"warn-schema-alteration": warnSchemaAlteration,
|
|
@@ -3341,13 +3178,7 @@ export function registerBuiltinPolicies(enabledNames: string[]): void {
|
|
|
3341
3178
|
// A guard against the agent disabling failproofai that any of those can
|
|
3342
3179
|
// switch off is not a guard.
|
|
3343
3180
|
if (policy.alwaysOn || enabledSet.has(normalizePolicyName(policy.name))) {
|
|
3344
|
-
|
|
3345
|
-
// resolves to hard even if its catalog entry ever said otherwise.
|
|
3346
|
-
registerPolicy(policy.name, policy.description, policy.fn, policy.match, 0, policy.params, {
|
|
3347
|
-
authority: policy.authority,
|
|
3348
|
-
reviewedBy: policy.reviewedBy,
|
|
3349
|
-
alwaysOn: policy.alwaysOn,
|
|
3350
|
-
});
|
|
3181
|
+
registerPolicy(policy.name, policy.description, policy.fn, policy.match, 0, policy.params);
|
|
3351
3182
|
}
|
|
3352
3183
|
}
|
|
3353
3184
|
}
|
|
@@ -377,7 +377,7 @@ export function versionStatusLines(): string[] {
|
|
|
377
377
|
const daemon = recorded?.daemon ?? "not installed";
|
|
378
378
|
return [
|
|
379
379
|
`CLI ${cliVersion} · daemon ${daemon}${skew ? " (STALE)" : ""} · layout ${recorded?.layout ?? "-"}`,
|
|
380
|
-
...(skew ? [" Run `failproofai
|
|
380
|
+
...(skew ? [" Run `failproofai config` to update the daemon."] : []),
|
|
381
381
|
];
|
|
382
382
|
}
|
|
383
383
|
|
|
@@ -8,8 +8,6 @@ import { createHash } from "node:crypto";
|
|
|
8
8
|
import { existsSync, readFileSync, realpathSync, rmSync } from "node:fs";
|
|
9
9
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
10
10
|
import { cloudPoliciesDir } from "./fp-home";
|
|
11
|
-
import { authorityFieldsOf } from "./policy-authority";
|
|
12
|
-
import type { PolicyAuthority } from "./policy-types";
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* Active-manifest schema versions this reader accepts.
|
|
@@ -46,19 +44,6 @@ export interface CloudManagedPolicyArtifact {
|
|
|
46
44
|
sha256: string;
|
|
47
45
|
path: string;
|
|
48
46
|
deployment: number;
|
|
49
|
-
/**
|
|
50
|
-
* Whether Jev may clear this policy's verdict once it is configured, as the
|
|
51
|
-
* ASSIGNMENT declares it — code inside the artifact cannot grant itself this.
|
|
52
|
-
* Absent means `hard`, which is what keeps Jev from weakening central
|
|
53
|
-
* enforcement by default: a cloud policy is only ever reviewable because
|
|
54
|
-
* whoever deployed it said so. Applies to every hook the artifact registers.
|
|
55
|
-
*
|
|
56
|
-
* Read and validated here; the daemon has to carry it into `active.json`
|
|
57
|
-
* (`ActivePolicy` in `cloud_policies.rs`) before a deployment can set it.
|
|
58
|
-
*/
|
|
59
|
-
authority?: PolicyAuthority;
|
|
60
|
-
/** The semantic policies that must all be asked and none answer `deny`; see `authority`. */
|
|
61
|
-
reviewedBy?: string[];
|
|
62
47
|
}
|
|
63
48
|
|
|
64
49
|
interface ActiveManifest {
|
|
@@ -70,8 +55,6 @@ interface ActiveManifest {
|
|
|
70
55
|
effect?: string;
|
|
71
56
|
sha256: string;
|
|
72
57
|
path: string;
|
|
73
|
-
authority?: unknown;
|
|
74
|
-
reviewedBy?: unknown;
|
|
75
58
|
}>;
|
|
76
59
|
}
|
|
77
60
|
|
|
@@ -223,11 +206,6 @@ export function readActiveCloudManagedPolicies(): CloudManagedPolicyArtifact[] {
|
|
|
223
206
|
sha256: policy.sha256,
|
|
224
207
|
path,
|
|
225
208
|
deployment: manifest.deployment,
|
|
226
|
-
// Unlike `effect`, a malformed authority is dropped rather than refused:
|
|
227
|
-
// dropping it makes this policy `hard`, the default that keeps enforcing,
|
|
228
|
-
// while refusing would take the whole deployment down over an optional
|
|
229
|
-
// field. Absent stays absent.
|
|
230
|
-
...authorityFieldsOf(policy as unknown as Record<string, unknown>),
|
|
231
209
|
};
|
|
232
210
|
});
|
|
233
211
|
}
|
|
@@ -33,7 +33,6 @@ import type { CustomHook, PolicyCatalogEntry } from "./policy-types";
|
|
|
33
33
|
import type { CloudManagedPolicyArtifact } from "./cloud-managed-policies";
|
|
34
34
|
import type { ResolvedPack } from "./pack-manifest";
|
|
35
35
|
import { customPoliciesDir, shimsDir } from "./fp-home";
|
|
36
|
-
import { warnAuthority, withMergedAuthority } from "./policy-authority";
|
|
37
36
|
|
|
38
37
|
const LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__";
|
|
39
38
|
|
|
@@ -449,19 +448,9 @@ export async function loadAllCustomHooks(
|
|
|
449
448
|
`cloud-managed policies ${existing.id} and ${policy.id} have identical source, so they share ` +
|
|
450
449
|
`one artifact and load as one policy; enforcing it if either asks to enforce`,
|
|
451
450
|
);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
// declaration let the order of active.json decide, so a team's reviewable
|
|
455
|
-
// assignment could clear what an org-wide hard one enforces.
|
|
456
|
-
const winner = existing.effect !== "enforce" && policy.effect === "enforce" ? policy : existing;
|
|
457
|
-
const { merged, overruled } = withMergedAuthority(winner, [existing, policy]);
|
|
458
|
-
if (overruled) {
|
|
459
|
-
warnAuthority(
|
|
460
|
-
`cloud-managed policies ${existing.id} and ${policy.id} share one artifact and do not all declare it ` +
|
|
461
|
-
`reviewable, so it stays hard`,
|
|
462
|
-
);
|
|
451
|
+
if (existing.effect !== "enforce" && policy.effect === "enforce") {
|
|
452
|
+
cloudManagedByPath.set(key, policy);
|
|
463
453
|
}
|
|
464
|
-
cloudManagedByPath.set(key, merged);
|
|
465
454
|
}
|
|
466
455
|
|
|
467
456
|
// Installed packs, keyed by artifact path — the same content-addressing, and
|
|
@@ -493,34 +482,14 @@ export async function loadAllCustomHooks(
|
|
|
493
482
|
*
|
|
494
483
|
* Winner precedence on a name both declare, because the merged record carries
|
|
495
484
|
* the winner's id and version and there is no merging two different defaults.
|
|
496
|
-
*
|
|
497
|
-
* Except AUTHORITY, which resolves toward hard like the effect resolves toward
|
|
498
|
-
* enforce (`withMergedAuthority`): a name is reviewable only if every pack
|
|
499
|
-
* behind the artifact declares it reviewable. A pack whose manifest does not
|
|
500
|
-
* list the name at all counts as hard — its copy of the artifact registers
|
|
501
|
-
* the policy all the same, undeclared, and an undeclared pack policy is hard.
|
|
502
|
-
* Winner precedence here would let one pack's manifest make another pack's
|
|
503
|
-
* policy reviewable, which is the one thing a manifest may never do.
|
|
504
485
|
*/
|
|
505
486
|
const unionCatalog = (
|
|
506
487
|
winner: PolicyCatalogEntry[],
|
|
507
488
|
other: PolicyCatalogEntry[],
|
|
508
|
-
):
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const r = withMergedAuthority(entry, [entry, peer ?? {}]);
|
|
513
|
-
if (r.overruled) overruled.push(entry.name);
|
|
514
|
-
return r.merged;
|
|
515
|
-
};
|
|
516
|
-
return {
|
|
517
|
-
policies: [
|
|
518
|
-
...winner.map((w) => harden(w, other.find((p) => p.name === w.name))),
|
|
519
|
-
...other.filter((p) => !winner.some((w) => w.name === p.name)).map((p) => harden(p, undefined)),
|
|
520
|
-
],
|
|
521
|
-
overruled,
|
|
522
|
-
};
|
|
523
|
-
};
|
|
489
|
+
): PolicyCatalogEntry[] => [
|
|
490
|
+
...winner,
|
|
491
|
+
...other.filter((p) => !winner.some((w) => w.name === p.name)),
|
|
492
|
+
];
|
|
524
493
|
|
|
525
494
|
const packByPath = new Map<string, ResolvedPack>();
|
|
526
495
|
/**
|
|
@@ -566,19 +535,12 @@ export async function loadAllCustomHooks(
|
|
|
566
535
|
// being intersected away.
|
|
567
536
|
const winner = existing.effect !== "enforce" && pack.effect === "enforce" ? pack : existing;
|
|
568
537
|
const other = winner === existing ? pack : existing;
|
|
569
|
-
const catalog = unionCatalog(winner.policies, other.policies);
|
|
570
538
|
packByPath.set(key, {
|
|
571
539
|
...winner,
|
|
572
|
-
policies:
|
|
540
|
+
policies: unionCatalog(winner.policies, other.policies),
|
|
573
541
|
enabled: unionSelection(winner.enabled, other.enabled),
|
|
574
542
|
clis: unionSelection(winner.clis, other.clis),
|
|
575
543
|
});
|
|
576
|
-
if (catalog.overruled.length > 0) {
|
|
577
|
-
warnAuthority(
|
|
578
|
-
`packs ${existing.id} and ${pack.id} share one artifact and do not all declare ` +
|
|
579
|
-
`${catalog.overruled.join(", ")} reviewable, so ${catalog.overruled.length === 1 ? "it stays" : "they stay"} hard`,
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
544
|
}
|
|
583
545
|
|
|
584
546
|
// 1. Explicit custom policy paths. Accept a string for callers/configs using
|
|
@@ -4,14 +4,9 @@
|
|
|
4
4
|
* from 'failproofai') and the hook handler (which reads from this file) share the
|
|
5
5
|
* same in-process state when running within the same Node.js process.
|
|
6
6
|
*/
|
|
7
|
-
import type { CustomHook
|
|
7
|
+
import type { CustomHook } from "./policy-types";
|
|
8
8
|
|
|
9
9
|
const REGISTRY_KEY = "__failproofai_custom_hooks__";
|
|
10
|
-
/**
|
|
11
|
-
* Beside the hook registry, on the same key namespace and cleared by the same
|
|
12
|
-
* call — the two are one registration pass and a build step reads both.
|
|
13
|
-
*/
|
|
14
|
-
const SEMANTIC_KEY = "__failproofai_semantic_policies__";
|
|
15
10
|
|
|
16
11
|
function getRegistry(): CustomHook[] {
|
|
17
12
|
const g = globalThis as Record<string, unknown>;
|
|
@@ -19,56 +14,17 @@ function getRegistry(): CustomHook[] {
|
|
|
19
14
|
return g[REGISTRY_KEY] as CustomHook[];
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
function getSemanticRegistry(): SemanticPolicyDeclaration[] {
|
|
23
|
-
const g = globalThis as Record<string, unknown>;
|
|
24
|
-
if (!Array.isArray(g[SEMANTIC_KEY])) g[SEMANTIC_KEY] = [];
|
|
25
|
-
return g[SEMANTIC_KEY] as SemanticPolicyDeclaration[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
17
|
export const customPolicies = {
|
|
29
18
|
add(hook: CustomHook): void {
|
|
30
19
|
getRegistry().push(hook);
|
|
31
20
|
},
|
|
32
21
|
};
|
|
33
22
|
|
|
34
|
-
/**
|
|
35
|
-
* Register a semantic (Jev) policy — a question set for the classifier, read by
|
|
36
|
-
* `failproofai publish` and emitted as the pack manifest's `semantic` array.
|
|
37
|
-
*
|
|
38
|
-
* A SEPARATE namespace from `customPolicies`, deliberately, rather than a
|
|
39
|
-
* `customPolicies.addSemantic`. A semantic policy has no `fn` and no `match`,
|
|
40
|
-
* and nothing about it ever executes on a user's machine: what installs is the
|
|
41
|
-
* declaration in the manifest, and the artifact's call here matters only at
|
|
42
|
-
* build time. Putting it behind the object whose entries return
|
|
43
|
-
* `allow()`/`deny()` invites exactly the shape confusion the manifest parser
|
|
44
|
-
* would then have to catch — a function on a semantic entry, a probe list on a
|
|
45
|
-
* regex one — and both mistakes would be silent until somebody read a manifest.
|
|
46
|
-
*
|
|
47
|
-
* Nothing is validated here. `parsePackSemanticPolicy` in `pack-manifest.ts`
|
|
48
|
-
* owns every rule, and it is applied at BUILD time (where the author can still
|
|
49
|
-
* fix it) and again at load time (where the bytes may have come from anywhere).
|
|
50
|
-
* Validating a third time in the setter would be a third copy of the rules.
|
|
51
|
-
*/
|
|
52
|
-
export const semanticPolicies = {
|
|
53
|
-
add(policy: SemanticPolicyDeclaration): void {
|
|
54
|
-
getSemanticRegistry().push(policy);
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
|
|
58
23
|
export function getCustomHooks(): CustomHook[] {
|
|
59
24
|
return getRegistry();
|
|
60
25
|
}
|
|
61
26
|
|
|
62
|
-
export function getSemanticRegistrations(): SemanticPolicyDeclaration[] {
|
|
63
|
-
return getSemanticRegistry();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
27
|
export function clearCustomHooks(): void {
|
|
67
28
|
const g = globalThis as Record<string, unknown>;
|
|
68
29
|
g[REGISTRY_KEY] = [];
|
|
69
|
-
// Both maps, on one call. A loader that cleared only the hooks would carry
|
|
70
|
-
// one entry file's semantic policies into the next file's build — and since
|
|
71
|
-
// the semantic half is read after the load rather than during it, the extras
|
|
72
|
-
// would be published under the wrong pack with nothing to notice.
|
|
73
|
-
g[SEMANTIC_KEY] = [];
|
|
74
30
|
}
|
|
@@ -35,11 +35,6 @@ export const FIRST_RUN_EXEMPT_SUBCOMMANDS: readonly string[] = [
|
|
|
35
35
|
// Same reason: a backfill is an explicit instruction about an already-set-up
|
|
36
36
|
// machine, and interrupting it to offer setup answers a question nobody asked.
|
|
37
37
|
"backfill",
|
|
38
|
-
// `jev setup` is configuration in its own right, and `jev status` / `test` /
|
|
39
|
-
// `remove` inspect or undo it. It is also the command most likely to be
|
|
40
|
-
// scripted with a key on stdin — a wizard in front of it would read that key
|
|
41
|
-
// as the answer to its first question.
|
|
42
|
-
"jev",
|
|
43
38
|
];
|
|
44
39
|
|
|
45
40
|
export function shouldOfferFirstRun(args: readonly string[]): boolean {
|