failproofai 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +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 +8 -7
- 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 +9 -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 -10
- 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 +8 -7
- 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 +6 -6
- 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 +7 -6
- 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 +52 -8
- package/.next/standalone/.next/server/app/settings/page.js +12 -7
- 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]__1msfs-h._.js → [externals]__19_pzeq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__1_bftcl._.js → [externals]__20kzpkf._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
- 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/_1ek68ln._.js +16 -16
- package/.next/standalone/.next/server/chunks/{_1c3k-8x._.js → _1q5i8mb._.js} +2 -2
- package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
- package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
- package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01bmjsj._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__02npjtd._.js → [root-of-the-server]__056wjo4._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__059yza8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06pflha._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0eip4_k._.js +22 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0p-5p8u._.js → [root-of-the-server]__0n0xg95._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qcb0mg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__013jr2b._.js → [root-of-the-server]__0rwtwpm._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0da85px._.js → [root-of-the-server]__11mayhe._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12e7nhs._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13t1zkw._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dinjii._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cxe_2_._.js → [root-of-the-server]__1mf3zp6._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ftmoxc._.js → [root-of-the-server]__1pprgri._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1p2otjt._.js → [root-of-the-server]__1q4p5b8._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qiz0e4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_166t73i._.js → _0-vcssj._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0uyu3jf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_13kfn90._.js +23 -0
- package/.next/standalone/.next/server/chunks/ssr/{_1es2j7i._.js → _1gb0ifp._.js} +5 -5
- package/.next/standalone/.next/server/chunks/ssr/{_1_qswah._.js → _1q46vxx._.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 +13 -13
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +3 -0
- 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_02tf1h4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_next_dist_0drixxt._.js → node_modules_next_dist_0w6mzq5._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_18_d8l1._.js +151 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +4 -2
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.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/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
- 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 +67 -23
- package/.next/standalone/.next/static/chunks/06dnzbolj00mc.js +6 -0
- package/.next/standalone/.next/static/chunks/0bhidk90e-07f.css +2 -0
- package/.next/standalone/.next/static/chunks/{2mdh397ghgnvv.js → 0m-9d6yn9hx4j.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0__8a7m868fvf.js → 0wbjy0zo-m7is.js} +1 -1
- package/.next/standalone/.next/static/chunks/16f3fa-lx38hk.js +1 -0
- package/.next/standalone/.next/static/chunks/{0o6qlkgubtoex.js → 21uv-uusw329x.js} +1 -1
- package/.next/standalone/.next/static/chunks/2g2tki08kdhie.js +1 -0
- package/.next/standalone/.next/static/chunks/2qdpj67x6ifk_.js +69 -0
- package/.next/standalone/.next/static/chunks/2qv4hshejedtx.css +1 -0
- package/.next/standalone/.next/static/chunks/3-nbtkhg9y-1j.js +1 -0
- package/.next/standalone/.next/static/chunks/355km0ihuqo1p.js +1 -0
- package/.next/standalone/.next/static/chunks/{13i7-9is-vhys.js → 3c3qbmosdjl6w.js} +1 -1
- package/.next/standalone/PROBE-FOLLOWUP.md +186 -0
- package/.next/standalone/app/actions/get-jev-config.ts +604 -0
- package/.next/standalone/app/actions/pack-actions.ts +12 -0
- package/.next/standalone/app/actions/update-jev-config.ts +569 -0
- package/.next/standalone/app/components/jev-notices.tsx +96 -0
- package/.next/standalone/app/policies/hooks-client.tsx +14 -3
- package/.next/standalone/app/settings/jev-panel.tsx +630 -0
- package/.next/standalone/app/settings/page.tsx +20 -1
- package/.next/standalone/app/settings/settings-client.tsx +27 -1
- package/.next/standalone/app/settings/settings.css +79 -0
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
- package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
- package/.next/standalone/package.json +10 -10
- package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- 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/sdk/typescript/scripts/release.mjs +30 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +3 -2
- package/bin/failproofai.mjs +148 -4
- package/dist/cli.mjs +18599 -9478
- package/dist/index.js +19 -1
- package/dist/worker.mjs +10500 -3773
- package/package.json +10 -10
- package/pi-extension/index.ts +11 -0
- package/scripts/build-policy-pack.mjs +53 -2
- package/src/audit/features.ts +3 -2
- package/src/hooks/builtin-policies.ts +195 -8
- package/src/hooks/cloud-connection.ts +190 -1
- package/src/hooks/cloud-enrollment-cli.ts +57 -8
- package/src/hooks/cloud-introspect.ts +6 -0
- package/src/hooks/cloud-managed-policies.ts +22 -0
- package/src/hooks/configure-wizard.ts +26 -6
- package/src/hooks/custom-hooks-loader.ts +98 -10
- package/src/hooks/custom-hooks-registry.ts +45 -1
- package/src/hooks/effective-reviewers.ts +243 -0
- package/src/hooks/first-run-gate.ts +5 -0
- package/src/hooks/flush-cli.ts +35 -8
- package/src/hooks/fp-config.ts +266 -1
- package/src/hooks/fp-home.ts +23 -0
- package/src/hooks/fp-reset.ts +22 -4
- package/src/hooks/handler.ts +303 -7
- package/src/hooks/hook-activity-store.ts +118 -6
- package/src/hooks/hook-telemetry.ts +41 -0
- package/src/hooks/jev-activity.ts +385 -0
- package/src/hooks/jev-cli.ts +2172 -0
- package/src/hooks/jev-cloud-connection.ts +392 -0
- package/src/hooks/loader-utils.ts +6 -0
- package/src/hooks/manager.ts +45 -7
- package/src/hooks/pack-cli.ts +568 -37
- package/src/hooks/pack-failclosed.ts +3 -0
- package/src/hooks/pack-manifest.ts +489 -7
- package/src/hooks/pack-store.ts +194 -11
- package/src/hooks/policy-authority.ts +386 -0
- package/src/hooks/policy-catalog.ts +206 -0
- package/src/hooks/policy-evaluator.ts +963 -796
- package/src/hooks/policy-registry.ts +27 -1
- package/src/hooks/policy-reviewability.ts +258 -0
- package/src/hooks/policy-types.ts +128 -0
- package/src/hooks/semantic/combine.ts +581 -0
- package/src/hooks/semantic/compile.ts +176 -0
- package/src/hooks/semantic/decide.ts +513 -0
- package/src/hooks/semantic/envelope.ts +1255 -0
- package/src/hooks/semantic/evaluator.ts +556 -0
- package/src/hooks/semantic/facts.ts +387 -0
- package/src/hooks/semantic/intent.ts +1190 -0
- package/src/hooks/semantic/jev-client.ts +1155 -0
- package/src/hooks/semantic/jev-config.ts +1142 -0
- package/src/hooks/semantic/jev-review.ts +389 -0
- package/src/hooks/semantic/jev-stats.ts +289 -0
- package/src/hooks/semantic/jev-throttle.ts +427 -0
- package/src/hooks/semantic/pack-policies.ts +294 -0
- package/src/hooks/semantic/policies.ts +597 -0
- package/src/hooks/semantic/precondition-names.ts +60 -0
- package/src/hooks/semantic/preconditions.ts +58 -0
- package/src/hooks/semantic/redact.ts +2937 -0
- package/src/hooks/semantic/session-root.ts +116 -0
- package/src/hooks/semantic/types.ts +162 -0
- package/src/hooks/semver-precedence.ts +128 -0
- package/src/hooks/tui.ts +4 -0
- package/src/hooks/types.ts +1 -1
- package/src/hooks/worker-server.ts +121 -27
- package/src/index.ts +6 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1_r2rbg._.js +0 -24
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bd3mje._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cg-bgc._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0cpu_mj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u3w0ll._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17d_ffl._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19d9tgz._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1jiwfsj._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1phc187._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/_0h_douw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1-i_gzc._.js +0 -23
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
- package/.next/standalone/.next/static/chunks/094xgi4owxaqf.js +0 -1
- package/.next/standalone/.next/static/chunks/1rz20_pz828f3.js +0 -6
- package/.next/standalone/.next/static/chunks/2k9f4tyv04809.css +0 -1
- package/.next/standalone/.next/static/chunks/2klitrtzpaoe0.js +0 -1
- package/.next/standalone/.next/static/chunks/2rshywgeqsyzk.css +0 -2
- package/.next/standalone/.next/static/chunks/3pzx4chkhko9k.js +0 -1
- package/.next/standalone/.next/static/chunks/3rh5o7e16irrm.js +0 -69
- package/.next/standalone/.next/static/chunks/43ufqrz8qo3h-.js +0 -1
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{PgeWCHmyVbjRznv2VO7KF → 5yKM8NoTOpUI0MoQl8eo5}/_ssgManifest.js +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Observability and enforcement for AI agent harnesses.
|
|
3
|
+
"version": "1.0.8",
|
|
4
|
+
"description": "Observability and enforcement for AI agent harnesses. 40 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.
|
|
126
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.
|
|
127
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.
|
|
128
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.
|
|
125
|
+
"@failproofai/failproofaid-linux-x64": "1.0.8",
|
|
126
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.8",
|
|
127
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.8",
|
|
128
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.8"
|
|
129
129
|
},
|
|
130
130
|
"failproofaidBinaries": {
|
|
131
|
-
"linux-x64": "
|
|
132
|
-
"linux-arm64": "
|
|
133
|
-
"darwin-x64": "
|
|
134
|
-
"darwin-arm64": "
|
|
131
|
+
"linux-x64": "35b641f60e1724513fbb760db382123dad2b5fcdd67dda6373bda80651bd36bb",
|
|
132
|
+
"linux-arm64": "a058288f5d08f99612f053793e5c7f59264d3da97f3dbe769e401b0f8536702d",
|
|
133
|
+
"darwin-x64": "b4f3dbe4aea44e77cc10839647c0dc03a1b8b797dfb27e1d89a6a96554ef27b8",
|
|
134
|
+
"darwin-arm64": "fd42f28bf92001d39117b488a215578b966f07d316214744aca64a1888f2aed2"
|
|
135
135
|
}
|
|
136
136
|
}
|
package/pi-extension/index.ts
CHANGED
|
@@ -404,6 +404,17 @@ 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,
|
|
407
418
|
});
|
|
408
419
|
if (decision.block) {
|
|
409
420
|
console.error(`[failproofai] prompt blocked: ${decision.reason ?? "blocked by policy"}`);
|
|
@@ -31,6 +31,24 @@
|
|
|
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`.
|
|
34
52
|
*/
|
|
35
53
|
import { execFileSync } from "node:child_process";
|
|
36
54
|
import { createHash } from "node:crypto";
|
|
@@ -40,6 +58,7 @@ import { fileURLToPath } from "node:url";
|
|
|
40
58
|
// The one place the pack's id is decided. Imported rather than restated — see
|
|
41
59
|
// PACK_ID below for what restating it cost.
|
|
42
60
|
import { CORE_SOURCE } from "../src/hooks/pack-store.ts";
|
|
61
|
+
import { manifestAuthority } from "../src/hooks/policy-authority.ts";
|
|
43
62
|
|
|
44
63
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
45
64
|
// `--out <dir>` lets the conformance test generate into a temp directory rather
|
|
@@ -95,6 +114,23 @@ for (const policy of BUILTIN_POLICIES) {
|
|
|
95
114
|
defaultEnabled: policy.defaultEnabled === true,
|
|
96
115
|
match: policy.match,
|
|
97
116
|
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 } : {}),
|
|
98
134
|
});
|
|
99
135
|
}
|
|
100
136
|
`;
|
|
@@ -120,7 +156,20 @@ try {
|
|
|
120
156
|
// validates these with the loader's own rules, so a shape the loader would
|
|
121
157
|
// refuse fails this build rather than shipping.
|
|
122
158
|
const { POLICY_CATALOG } = await import(join(ROOT, "src/hooks/policy-catalog.ts"));
|
|
123
|
-
const policies = POLICY_CATALOG.filter((p) => !p.alwaysOn).map((p) =>
|
|
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
|
+
});
|
|
124
173
|
|
|
125
174
|
const manifest = JSON.stringify(
|
|
126
175
|
{ id: PACK_ID, version, effect: "enforce", policies },
|
|
@@ -138,8 +187,10 @@ writeFileSync(
|
|
|
138
187
|
);
|
|
139
188
|
|
|
140
189
|
const omitted = POLICY_CATALOG.filter((p) => p.alwaysOn).map((p) => p.name);
|
|
190
|
+
const reviewable = policies.filter((p) => p.authority === "reviewable").length;
|
|
141
191
|
console.log(
|
|
142
|
-
`[policy-pack] ${PACK_ID}@${version} — ${policies.length} policies
|
|
192
|
+
`[policy-pack] ${PACK_ID}@${version} — ${policies.length} policies ` +
|
|
193
|
+
`(${reviewable} reviewable by Jev, ${policies.length - reviewable} hard), ` +
|
|
143
194
|
`${(entryBytes.length / 1024).toFixed(1)} KB entry` +
|
|
144
195
|
(omitted.length ? `; omitted alwaysOn: ${omitted.join(", ")}` : ""),
|
|
145
196
|
);
|
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 40 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 (21) ───────────
|
|
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,6 +88,7 @@ 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 },
|
|
91
92
|
"warn-all-files-staged": { archetype: "cowboy", weight: 0.6 },
|
|
92
93
|
"block-kubectl": { archetype: "cowboy", weight: 1.5 },
|
|
93
94
|
"block-terraform": { archetype: "cowboy", weight: 1.5 },
|
|
@@ -186,6 +186,15 @@ const PUBLISH_CMD_RE = /(?:npm\s+publish|bun\s+publish|pnpm\s+publish|yarn\s+npm
|
|
|
186
186
|
const ENV_PRINTENV_RE = /(?:^|\s|;|&&|\|\|)(?:env|printenv)(?:\s|$|;|&&|\|)/;
|
|
187
187
|
const ECHO_ENV_RE = /echo\s+.*\$\{?[A-Za-z_]/;
|
|
188
188
|
const EXPORT_RE = /(?:^|\s|;|&&|\|\|)export\s+\w+/;
|
|
189
|
+
// The other whole-environment dumps: bare `set` / `export` / `export -p` in
|
|
190
|
+
// command position (`set -e`, `export FOO=bar` are not), `declare|typeset` with
|
|
191
|
+
// -x or -p (not `declare -a`), `compgen -v|-e`, and /proc/<pid>/environ.
|
|
192
|
+
const ENV_DUMP_RE =
|
|
193
|
+
/(?:^|[;&|({\n`'"]|\$\()\s*(?:set|export(?:\s+-p)?)\s*(?:$|[|;&>)\n])|\b(?:declare|typeset)\s+-[A-Za-z]*[xp]|\bcompgen\s+-[A-Za-z]*[ev]|\/proc\/(?:self|thread-self|\d+|\$\$|\$BASHPID)\/environ/;
|
|
194
|
+
// A script dumping the environment OBJECT, not one variable of it. Two tests,
|
|
195
|
+
// both linear: an interpreter in the command, and the object used whole.
|
|
196
|
+
const SCRIPT_INTERP_RE = /\b(?:python[\d.]*|node|bun|deno)\b/;
|
|
197
|
+
const ENV_OBJECT_RE = /\bos\.environ(?!\s*\[|\.(?:get|setdefault|pop|update)\b)|\bprocess\.env(?![.\[\w])/;
|
|
189
198
|
const PS_ENV_VAR_RE = /\$env:[A-Za-z_]/i;
|
|
190
199
|
const PS_CHILDITEM_ENV_RE = /(?:Get-ChildItem|dir|gci|ls)\s+Env:/i;
|
|
191
200
|
const DOTNET_GETENV_RE = /\[Environment\]::GetEnvironment/i;
|
|
@@ -623,8 +632,15 @@ const GIT_DESTRUCTIVE_SUBCOMMANDS = new Set([
|
|
|
623
632
|
*/
|
|
624
633
|
const GIT_FLAGS_WITH_OPERANDS = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--exec-path"]);
|
|
625
634
|
|
|
626
|
-
/**
|
|
627
|
-
|
|
635
|
+
/**
|
|
636
|
+
* Where the `git` subcommand sits in `args`, with git's own global options
|
|
637
|
+
* walked off first, or `-1` when there is no subcommand at all.
|
|
638
|
+
*
|
|
639
|
+
* The index rather than the value, because a caller that wants to read the
|
|
640
|
+
* SUBCOMMAND's flags (`warnGitClean`) needs to know where they start, and
|
|
641
|
+
* `args.indexOf("clean")` would land on a `-C clean` operand instead.
|
|
642
|
+
*/
|
|
643
|
+
function gitSubcommandIndex(args: string[]): number {
|
|
628
644
|
for (let i = 0; i < args.length; i++) {
|
|
629
645
|
const arg = args[i];
|
|
630
646
|
if (GIT_FLAGS_WITH_OPERANDS.has(arg)) {
|
|
@@ -632,9 +648,15 @@ function gitSubcommand(args: string[]): string | undefined {
|
|
|
632
648
|
continue;
|
|
633
649
|
}
|
|
634
650
|
if (arg.startsWith("-")) continue;
|
|
635
|
-
return
|
|
651
|
+
return i;
|
|
636
652
|
}
|
|
637
|
-
return
|
|
653
|
+
return -1;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** The `git` subcommand, with git's own global options walked off first. */
|
|
657
|
+
function gitSubcommand(args: string[]): string | undefined {
|
|
658
|
+
const i = gitSubcommandIndex(args);
|
|
659
|
+
return i < 0 ? undefined : args[i];
|
|
638
660
|
}
|
|
639
661
|
|
|
640
662
|
/**
|
|
@@ -1379,6 +1401,48 @@ const GIT_AMEND_RE = /\bgit\s+commit\b.*--amend\b/;
|
|
|
1379
1401
|
// warnGitStashDrop
|
|
1380
1402
|
const GIT_STASH_DROP_RE = /\bgit\s+stash\s+(?:drop|clear)\b/;
|
|
1381
1403
|
|
|
1404
|
+
// warnGitClean
|
|
1405
|
+
/**
|
|
1406
|
+
* `git`, however the command line spells it. Same shape as {@link RM_CMD_RE}:
|
|
1407
|
+
* an absolute path to the binary does not change what it does.
|
|
1408
|
+
*/
|
|
1409
|
+
const GIT_CMD_RE = /^(?:\/\S*\/)?git$/;
|
|
1410
|
+
|
|
1411
|
+
/** A short-flag cluster: `-f`, `-fdx`, `-xdf`. */
|
|
1412
|
+
const SHORT_FLAG_CLUSTER_RE = /^-[A-Za-z]+$/;
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* `clean.requireForce=false`, the config that makes `git clean` delete with no
|
|
1416
|
+
* `-f` at all — and settable per invocation as `git -c
|
|
1417
|
+
* clean.requireForce=false clean -xd`. Reading the subcommand's flags alone
|
|
1418
|
+
* would call that "no force, no warning" on the one spelling that needs none.
|
|
1419
|
+
*/
|
|
1420
|
+
const GIT_CLEAN_FORCE_WAIVED_RE = /^clean\.requireforce=(?:false|0|no|off)$/i;
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* The default `destructiveFlags`, and why the condition is these three letters
|
|
1424
|
+
* rather than "any `git clean`".
|
|
1425
|
+
*
|
|
1426
|
+
* `git clean` deletes nothing without `-f` / `--force` (or the config above),
|
|
1427
|
+
* and `-n` / `--dry-run` only prints — so force is the precondition and
|
|
1428
|
+
* dry-run is the exemption. What force ALONE removes is untracked files in the
|
|
1429
|
+
* current directory: the narrowest spelling, and the one people mean when they
|
|
1430
|
+
* tidy up. These are the letters that widen the blast radius past that:
|
|
1431
|
+
*
|
|
1432
|
+
* - `-x` also removes IGNORED files — `.env`, local config, editor state,
|
|
1433
|
+
* scratch work. That is the data that genuinely cannot be rebuilt by running
|
|
1434
|
+
* a command, and it is what makes `git clean -fdx` the severe spelling.
|
|
1435
|
+
* - `-X` removes ONLY ignored files: the same loss, tracked tree untouched.
|
|
1436
|
+
* - `-d` also removes untracked DIRECTORIES — whole new subtrees git has never
|
|
1437
|
+
* had a copy of.
|
|
1438
|
+
*
|
|
1439
|
+
* A bare `git clean -f` is deliberately absent. It has the smallest radius and
|
|
1440
|
+
* by far the most traffic, and a guard that fires on the ordinary spelling is
|
|
1441
|
+
* one people switch off — which would cost them the `-fdx` warning too. Add
|
|
1442
|
+
* `"f"` to `destructiveFlags` to opt into it.
|
|
1443
|
+
*/
|
|
1444
|
+
const GIT_CLEAN_DESTRUCTIVE_FLAGS = ["d", "x", "X"];
|
|
1445
|
+
|
|
1382
1446
|
// warnAllFilesStaged
|
|
1383
1447
|
const GIT_ADD_ALL_RE = /\bgit\s+add\s+(?:-A\b|--all\b|\.(?:\s|$|;|&&|\|\|))/;
|
|
1384
1448
|
|
|
@@ -1710,6 +1774,9 @@ function protectEnvVars(ctx: PolicyContext): PolicyResult {
|
|
|
1710
1774
|
if (EXPORT_RE.test(cmd)) {
|
|
1711
1775
|
return deny("Command exports environment variable");
|
|
1712
1776
|
}
|
|
1777
|
+
if (ENV_DUMP_RE.test(cmd) || (SCRIPT_INTERP_RE.test(cmd) && ENV_OBJECT_RE.test(cmd))) {
|
|
1778
|
+
return deny("Command dumps environment variables");
|
|
1779
|
+
}
|
|
1713
1780
|
// PowerShell: $env:VAR
|
|
1714
1781
|
if (PS_ENV_VAR_RE.test(cmd)) {
|
|
1715
1782
|
return deny("Command reads environment variable via PowerShell");
|
|
@@ -2215,17 +2282,35 @@ const READ_LIKE_CMDS =
|
|
|
2215
2282
|
* separator characters that appear in compound argv tokens (':' for Docker
|
|
2216
2283
|
* volume mounts and PATH-like lists, '=' for env var assignments) so that a
|
|
2217
2284
|
* suffix like '/dashboard.mdx' in 'docs/STAR/dashboard.mdx' or '/docs' in
|
|
2218
|
-
* '-v HOST_DIR:/docs' is not misread as a standalone absolute path.
|
|
2285
|
+
* '-v HOST_DIR:/docs' is not misread as a standalone absolute path. '/' is in
|
|
2286
|
+
* that list too, so a match cannot START on the second slash of a protocol
|
|
2287
|
+
* separator: 'http://localhost:3000/x' used to yield '/localhost:3000/x'.
|
|
2288
|
+
* A match that starts on the FIRST slash is untouched, so '//etc/passwd' —
|
|
2289
|
+
* a real, working spelling of /etc/passwd — is still extracted and still
|
|
2290
|
+
* resolves to /etc/passwd.
|
|
2291
|
+
*
|
|
2292
|
+
* A run of slashes with nothing else ('//', '///') that opens a line after
|
|
2293
|
+
* the first is dropped: that is a line-comment marker in a heredoc body, not a
|
|
2294
|
+
* directory. It used to resolve to the filesystem root, which denied a heredoc
|
|
2295
|
+
* writing a TypeScript file into the project. Anywhere else a slash-run is
|
|
2296
|
+
* the root, as a lone '/' is: `cd // && cat etc/shadow` reads /etc/shadow.
|
|
2219
2297
|
*/
|
|
2220
2298
|
function extractAbsolutePaths(command: string): string[] {
|
|
2221
2299
|
const paths: string[] = [];
|
|
2222
|
-
const pathRe = /(?<![a-zA-Z0-9_
|
|
2300
|
+
const pathRe = /(?<![a-zA-Z0-9_.\-~\\*?:=/])(?:~\/[^\s;|&"'()\[\]{}]*|~(?=\s|$|[;|&"'()\[\]{}])|\/[^\s;|&"'()\[\]{}]*)/g;
|
|
2223
2301
|
|
|
2224
2302
|
function addPaths(s: string): void {
|
|
2225
2303
|
pathRe.lastIndex = 0;
|
|
2226
2304
|
let m: RegExpExecArray | null;
|
|
2227
2305
|
while ((m = pathRe.exec(s)) !== null) {
|
|
2228
2306
|
let p = m[0];
|
|
2307
|
+
// A `//` that opens a line after the first (a heredoc body) is a comment
|
|
2308
|
+
// marker. Anywhere else a slash-run is the root: `cd // && cat etc/shadow`,
|
|
2309
|
+
// and `cd \<newline>//`, which bash joins into one line.
|
|
2310
|
+
if (/^\/{2,}$/.test(p)) {
|
|
2311
|
+
const nl = s.lastIndexOf("\n", m.index - 1);
|
|
2312
|
+
if (nl !== -1 && s[nl - 1] !== "\\" && /^[ \t]*$/.test(s.slice(nl + 1, m.index))) continue;
|
|
2313
|
+
}
|
|
2229
2314
|
if (p === "~") p = homedir();
|
|
2230
2315
|
else if (p.startsWith("~/")) p = join(homedir(), p.slice(2));
|
|
2231
2316
|
paths.push(p);
|
|
@@ -2516,6 +2601,101 @@ function warnGitStashDrop(ctx: PolicyContext): PolicyResult {
|
|
|
2516
2601
|
return allow();
|
|
2517
2602
|
}
|
|
2518
2603
|
|
|
2604
|
+
/** What one `git clean` argument list asks for. */
|
|
2605
|
+
interface GitCleanFlags {
|
|
2606
|
+
force: boolean;
|
|
2607
|
+
dryRun: boolean;
|
|
2608
|
+
/** Short-flag letters, with `--force` counted as `f` so widening works either way. */
|
|
2609
|
+
letters: Set<string>;
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
/**
|
|
2613
|
+
* Read `git clean`'s own options: clusters (`-fdx`) and the two long forms
|
|
2614
|
+
* that matter (`--force`, `--dry-run`).
|
|
2615
|
+
*
|
|
2616
|
+
* Everything after `--` is a pathspec, not a flag — `git clean -n -- -fdx`
|
|
2617
|
+
* asks about a file literally called `-fdx` and deletes nothing.
|
|
2618
|
+
*/
|
|
2619
|
+
function readGitCleanFlags(args: string[]): GitCleanFlags {
|
|
2620
|
+
const letters = new Set<string>();
|
|
2621
|
+
let force = false;
|
|
2622
|
+
let dryRun = false;
|
|
2623
|
+
let sawEndOfOptions = false;
|
|
2624
|
+
for (const token of args) {
|
|
2625
|
+
if (token === "--") {
|
|
2626
|
+
sawEndOfOptions = true;
|
|
2627
|
+
continue;
|
|
2628
|
+
}
|
|
2629
|
+
if (sawEndOfOptions) continue;
|
|
2630
|
+
if (token === "--force") {
|
|
2631
|
+
force = true;
|
|
2632
|
+
continue;
|
|
2633
|
+
}
|
|
2634
|
+
if (token === "--dry-run") {
|
|
2635
|
+
dryRun = true;
|
|
2636
|
+
continue;
|
|
2637
|
+
}
|
|
2638
|
+
if (token.startsWith("--")) continue;
|
|
2639
|
+
if (!SHORT_FLAG_CLUSTER_RE.test(token)) continue;
|
|
2640
|
+
for (const letter of token.slice(1)) {
|
|
2641
|
+
if (letter === "f") force = true;
|
|
2642
|
+
if (letter === "n") dryRun = true;
|
|
2643
|
+
letters.add(letter);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
if (force) letters.add("f");
|
|
2647
|
+
return { force, dryRun, letters };
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
/** True when git's own `-c key=value` options waive `clean.requireForce`. */
|
|
2651
|
+
function gitCleanForceWaived(globalOptions: string[]): boolean {
|
|
2652
|
+
for (let i = 0; i < globalOptions.length; i++) {
|
|
2653
|
+
const token = globalOptions[i];
|
|
2654
|
+
const value =
|
|
2655
|
+
token === "-c" ? globalOptions[i + 1] : token.startsWith("-c") ? token.slice(2) : undefined;
|
|
2656
|
+
if (value !== undefined && GIT_CLEAN_FORCE_WAIVED_RE.test(value)) return true;
|
|
2657
|
+
}
|
|
2658
|
+
return false;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/**
|
|
2662
|
+
* The gap this closes: `git clean -fdx` was caught by NEITHER tier — no regex
|
|
2663
|
+
* builtin matched the string at all, and the semantic check that covers
|
|
2664
|
+
* unrecoverable deletion cannot fire on it. See the note on the catalog entry
|
|
2665
|
+
* for the measurement and for why this policy is `hard`.
|
|
2666
|
+
*/
|
|
2667
|
+
function warnGitClean(ctx: PolicyContext): PolicyResult {
|
|
2668
|
+
if (ctx.toolName !== "Bash") return allow();
|
|
2669
|
+
const destructiveFlags = ((ctx.params?.destructiveFlags ?? GIT_CLEAN_DESTRUCTIVE_FLAGS) as string[]);
|
|
2670
|
+
if (destructiveFlags.length === 0) return allow();
|
|
2671
|
+
|
|
2672
|
+
for (const segment of shellSegments(getCommand(ctx))) {
|
|
2673
|
+
const tokens = parseArgvTokens(segment);
|
|
2674
|
+
// `findIndex`, not `tokens[0]`, so an env prefix (`GIT_DIR=x git clean …`)
|
|
2675
|
+
// and an absolute binary path both still resolve to the git invocation.
|
|
2676
|
+
const gitIdx = tokens.findIndex((t) => GIT_CMD_RE.test(t));
|
|
2677
|
+
if (gitIdx < 0) continue;
|
|
2678
|
+
const args = tokens.slice(gitIdx + 1);
|
|
2679
|
+
const subIdx = gitSubcommandIndex(args);
|
|
2680
|
+
if (subIdx < 0 || args[subIdx] !== "clean") continue;
|
|
2681
|
+
|
|
2682
|
+
const flags = readGitCleanFlags(args.slice(subIdx + 1));
|
|
2683
|
+
// `-n` / `--dry-run` prints what would go and removes nothing, whatever
|
|
2684
|
+
// else is on the line — the safe inspection form, never a warning.
|
|
2685
|
+
if (flags.dryRun) continue;
|
|
2686
|
+
if (!flags.force && !gitCleanForceWaived(args.slice(0, subIdx))) continue;
|
|
2687
|
+
if (!destructiveFlags.some((f) => flags.letters.has(f))) continue;
|
|
2688
|
+
|
|
2689
|
+
return instruct(
|
|
2690
|
+
"STOP: This command deletes untracked files from the working tree (git clean). Git has no copy of " +
|
|
2691
|
+
"untracked or ignored files, so nothing it removes can be recovered: -d takes whole untracked " +
|
|
2692
|
+
"directories, and -x / -X also take .gitignore'd files such as .env, local config and scratch work. " +
|
|
2693
|
+
"Re-run it with --dry-run and confirm the exact paths with the user before executing.",
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
return allow();
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2519
2699
|
function warnAllFilesStaged(ctx: PolicyContext): PolicyResult {
|
|
2520
2700
|
if (ctx.toolName !== "Bash") return allow();
|
|
2521
2701
|
const cmd = getCommand(ctx);
|
|
@@ -3078,7 +3258,7 @@ function requireCiGreenBeforeStop(ctx: PolicyContext): PolicyResult {
|
|
|
3078
3258
|
* Each value is the identical hoisted function object, never a wrapper. Two
|
|
3079
3259
|
* things depend on that and neither fails loudly: `audit/cache.ts` hashes
|
|
3080
3260
|
* `fn.toString()` into the audit cache's `engineVersion`, so wrapping every
|
|
3081
|
-
* entry would collapse
|
|
3261
|
+
* entry would collapse 40 distinct hashes into one and freeze the key — stale
|
|
3082
3262
|
* audit results would then be served for the full 30-day TTL with no symptom;
|
|
3083
3263
|
* and `gitBranchCache` is module-scoped, so a per-call factory would silently
|
|
3084
3264
|
* reset it on every hook event.
|
|
@@ -3109,6 +3289,7 @@ const POLICY_IMPLEMENTATIONS: Record<string, PolicyFunction> = {
|
|
|
3109
3289
|
"block-work-on-main": blockWorkOnMain,
|
|
3110
3290
|
"warn-git-amend": warnGitAmend,
|
|
3111
3291
|
"warn-git-stash-drop": warnGitStashDrop,
|
|
3292
|
+
"warn-git-clean": warnGitClean,
|
|
3112
3293
|
"warn-all-files-staged": warnAllFilesStaged,
|
|
3113
3294
|
"warn-destructive-sql": warnDestructiveSql,
|
|
3114
3295
|
"warn-schema-alteration": warnSchemaAlteration,
|
|
@@ -3178,7 +3359,13 @@ export function registerBuiltinPolicies(enabledNames: string[]): void {
|
|
|
3178
3359
|
// A guard against the agent disabling failproofai that any of those can
|
|
3179
3360
|
// switch off is not a guard.
|
|
3180
3361
|
if (policy.alwaysOn || enabledSet.has(normalizePolicyName(policy.name))) {
|
|
3181
|
-
|
|
3362
|
+
// `alwaysOn` travels with the declaration so the self-protection guard
|
|
3363
|
+
// resolves to hard even if its catalog entry ever said otherwise.
|
|
3364
|
+
registerPolicy(policy.name, policy.description, policy.fn, policy.match, 0, policy.params, {
|
|
3365
|
+
authority: policy.authority,
|
|
3366
|
+
reviewedBy: policy.reviewedBy,
|
|
3367
|
+
alwaysOn: policy.alwaysOn,
|
|
3368
|
+
});
|
|
3182
3369
|
}
|
|
3183
3370
|
}
|
|
3184
3371
|
}
|