failproofai 1.0.5 → 1.0.6-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/prerender-manifest.json +5 -5
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- 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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
- 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 +1 -1
- 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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
- 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]__0sds_16._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +3 -3
- 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/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__15k-dqb._.js → [root-of-the-server]__1i26dsw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0sz_5na._.js → [root-of-the-server]__1ouobn4._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1b78kcp._.js → _09hs6ms._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0guaevg._.js → _0lbwa8m._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0wso5d3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1-3unkg._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +3 -3
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +3 -3
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr._.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 +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- 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 +19 -19
- package/.next/standalone/.next/static/chunks/{3w46euc60dx39.js → 03n_qjih7-w6q.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2zafgs90r_leq.js → 06jxjnw8bfczc.js} +1 -1
- package/.next/standalone/.next/static/chunks/{252xreaecy80h.js → 0vanab2g65op_.js} +1 -1
- package/.next/standalone/.next/static/chunks/17pjz_h45j10c.js +1 -0
- package/.next/standalone/.next/static/chunks/1ekh-hj_iom84.js +1 -0
- package/.next/standalone/.next/static/chunks/{021ydrq6rhpsi.js → 1so_3s0q4c4j1.js} +1 -1
- package/.next/standalone/.next/static/chunks/1xacnwjkbekn7.js +1 -0
- package/.next/standalone/.next/static/chunks/2hg_bp8z_vm2g.js +1 -0
- package/.next/standalone/.next/static/chunks/{3bleyy40s99f2.js → 2u0kol_boahtv.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1vb33xa6j9y2q.js → 3_vesf83-0903.js} +1 -1
- package/.next/standalone/.next/static/chunks/3d3hzhcnns6ww.js +1 -0
- package/.next/standalone/.next/static/chunks/3iasus89n8mg1.js +6 -0
- package/.next/standalone/.next/static/chunks/{11s_v03ub6nss.js → 44_cj1_i_aa2z.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-3p73obe0xmrxm.js → turbopack-38nzuvp95_2rv.js} +1 -1
- package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
- package/.next/standalone/hermes-plugin/README.md +133 -0
- package/.next/standalone/hermes-plugin/__init__.py +248 -0
- package/.next/standalone/hermes-plugin/client.py +134 -0
- package/.next/standalone/hermes-plugin/ledger.py +187 -0
- package/.next/standalone/hermes-plugin/plugin.yaml +37 -0
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/app-render/create-component-styles-and-scripts.js +2 -1
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +14 -0
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/disk-lru-cache.external.js +7 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +6 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +10 -10
- package/.next/standalone/node_modules/react/cjs/react.development.js +92 -46
- package/.next/standalone/node_modules/react/cjs/react.production.js +112 -89
- package/.next/standalone/node_modules/react/package.json +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1165 -790
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1171 -791
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +1294 -862
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +1305 -868
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +1315 -896
- package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +24 -5
- package/.next/standalone/node_modules/react-dom/package.json +4 -4
- package/.next/standalone/package.json +13 -12
- package/.next/standalone/sdk/python/uv.lock +18 -18
- package/.next/standalone/server.js +1 -1
- package/README.md +29 -13
- package/bin/failproofai.mjs +3 -1
- package/dist/cli.mjs +420 -178
- package/dist/worker.mjs +17 -3
- package/hermes-plugin/README.md +133 -0
- package/hermes-plugin/__init__.py +248 -0
- package/hermes-plugin/client.py +134 -0
- package/hermes-plugin/ledger.py +187 -0
- package/hermes-plugin/plugin.yaml +37 -0
- package/package.json +13 -12
- package/scripts/translate-docs/validate-translation.ts +48 -0
- package/src/hooks/handler.ts +29 -1
- package/src/hooks/integrations.ts +264 -73
- package/src/hooks/manager.ts +17 -5
- package/src/hooks/types.ts +8 -8
- package/src/hooks/worker-server.ts +1 -0
- package/.next/standalone/.next/server/chunks/[externals]__157pf8j._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +0 -3
- package/.next/standalone/.next/static/chunks/0y8_1s45gbqmn.js +0 -6
- package/.next/standalone/.next/static/chunks/14b3ezufe5411.js +0 -1
- package/.next/standalone/.next/static/chunks/1oskk7pdo8aon.js +0 -1
- package/.next/standalone/.next/static/chunks/2xycd6mjg00fb.js +0 -1
- package/.next/standalone/.next/static/chunks/3l36d73kj18zw.js +0 -1
- package/.next/standalone/.next/static/chunks/40lpt2o2b283e.js +0 -1
- /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → -7OT2NwGPxAu-skEL_Xee}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → -7OT2NwGPxAu-skEL_Xee}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → -7OT2NwGPxAu-skEL_Xee}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Versioned Unix-socket client for failproofaid policy evaluation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import socket
|
|
8
|
+
import struct
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Mapping, Sequence
|
|
12
|
+
|
|
13
|
+
PROTOCOL_VERSION = 1
|
|
14
|
+
MAX_FRAME_BYTES = 16 * 1024 * 1024
|
|
15
|
+
MAX_REQUEST_BYTES = 1024 * 1024
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EvaluationError(RuntimeError):
|
|
19
|
+
"""The daemon could not return a trustworthy policy verdict."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class PolicyVerdict:
|
|
24
|
+
decision: str
|
|
25
|
+
policy_names: tuple[str, ...]
|
|
26
|
+
reason: str | None
|
|
27
|
+
matched_policies: tuple[str, ...]
|
|
28
|
+
duration_ms: int
|
|
29
|
+
tool_name: str | None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def daemon_socket_path() -> Path:
|
|
33
|
+
override = os.environ.get("FAILPROOFAI_DAEMON_SOCKET", "").strip()
|
|
34
|
+
if override:
|
|
35
|
+
return Path(override).expanduser()
|
|
36
|
+
root = os.environ.get("FAILPROOFAI_HOME", "").strip()
|
|
37
|
+
home = Path(root).expanduser() if root else Path.home() / ".failproofai"
|
|
38
|
+
return home / "run" / "failproofaid.sock"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _read_exact(sock: socket.socket, length: int) -> bytes:
|
|
42
|
+
chunks: list[bytes] = []
|
|
43
|
+
remaining = length
|
|
44
|
+
while remaining:
|
|
45
|
+
chunk = sock.recv(remaining)
|
|
46
|
+
if not chunk:
|
|
47
|
+
raise EvaluationError("failproofaid closed the connection before returning a verdict")
|
|
48
|
+
chunks.append(chunk)
|
|
49
|
+
remaining -= len(chunk)
|
|
50
|
+
return b"".join(chunks)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _string_list(value: object, field: str) -> tuple[str, ...]:
|
|
54
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes, bytearray)):
|
|
55
|
+
raise EvaluationError(f"failproofaid returned an invalid {field}")
|
|
56
|
+
if not all(isinstance(item, str) for item in value):
|
|
57
|
+
raise EvaluationError(f"failproofaid returned an invalid {field}")
|
|
58
|
+
return tuple(value)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def evaluate_policy(
|
|
62
|
+
*,
|
|
63
|
+
event: str,
|
|
64
|
+
payload: Mapping[str, Any],
|
|
65
|
+
cwd: str | None,
|
|
66
|
+
connect_timeout_ms: int = 250,
|
|
67
|
+
evaluation_timeout_ms: int = 12_000,
|
|
68
|
+
) -> PolicyVerdict:
|
|
69
|
+
request = {
|
|
70
|
+
"type": "policyEvaluation",
|
|
71
|
+
"protocolVersion": PROTOCOL_VERSION,
|
|
72
|
+
"integration": "hermes",
|
|
73
|
+
"event": event,
|
|
74
|
+
"payload": dict(payload),
|
|
75
|
+
"cwd": cwd,
|
|
76
|
+
}
|
|
77
|
+
try:
|
|
78
|
+
body = json.dumps(
|
|
79
|
+
request,
|
|
80
|
+
ensure_ascii=False,
|
|
81
|
+
separators=(",", ":"),
|
|
82
|
+
default=lambda value: repr(value),
|
|
83
|
+
).encode("utf-8")
|
|
84
|
+
except (TypeError, ValueError) as exc:
|
|
85
|
+
raise EvaluationError(f"could not encode policy request: {exc}") from exc
|
|
86
|
+
if len(body) > MAX_REQUEST_BYTES:
|
|
87
|
+
raise EvaluationError("policy request exceeds the 1 MiB limit")
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
|
91
|
+
sock.settimeout(max(connect_timeout_ms, 1) / 1000)
|
|
92
|
+
sock.connect(str(daemon_socket_path()))
|
|
93
|
+
sock.settimeout(max(evaluation_timeout_ms, 1) / 1000)
|
|
94
|
+
sock.sendall(struct.pack(">I", len(body)) + body)
|
|
95
|
+
declared_length = struct.unpack(">I", _read_exact(sock, 4))[0]
|
|
96
|
+
if declared_length > MAX_FRAME_BYTES:
|
|
97
|
+
raise EvaluationError("failproofaid response exceeds the 16 MiB limit")
|
|
98
|
+
response = json.loads(_read_exact(sock, declared_length).decode("utf-8"))
|
|
99
|
+
except EvaluationError:
|
|
100
|
+
raise
|
|
101
|
+
except (OSError, UnicodeError, ValueError, struct.error) as exc:
|
|
102
|
+
raise EvaluationError(f"failproofaid evaluation failed: {exc}") from exc
|
|
103
|
+
|
|
104
|
+
if not isinstance(response, dict):
|
|
105
|
+
raise EvaluationError("failproofaid returned a non-object response")
|
|
106
|
+
if response.get("protocolVersion") != PROTOCOL_VERSION:
|
|
107
|
+
raise EvaluationError("failproofaid protocol version mismatch")
|
|
108
|
+
if response.get("type") == "error":
|
|
109
|
+
message = response.get("message")
|
|
110
|
+
raise EvaluationError(str(message or "failproofaid could not evaluate the policy"))
|
|
111
|
+
if response.get("type") != "policyResult":
|
|
112
|
+
raise EvaluationError("failproofaid does not support native policy evaluation")
|
|
113
|
+
|
|
114
|
+
decision = response.get("decision")
|
|
115
|
+
if decision not in {"allow", "deny", "instruct"}:
|
|
116
|
+
raise EvaluationError("failproofaid returned an invalid policy decision")
|
|
117
|
+
reason = response.get("reason")
|
|
118
|
+
if reason is not None and not isinstance(reason, str):
|
|
119
|
+
raise EvaluationError("failproofaid returned an invalid policy reason")
|
|
120
|
+
duration_ms = response.get("durationMs", 0)
|
|
121
|
+
if not isinstance(duration_ms, int) or duration_ms < 0:
|
|
122
|
+
raise EvaluationError("failproofaid returned an invalid evaluation duration")
|
|
123
|
+
tool_name = response.get("toolName")
|
|
124
|
+
if tool_name is not None and not isinstance(tool_name, str):
|
|
125
|
+
raise EvaluationError("failproofaid returned an invalid tool name")
|
|
126
|
+
|
|
127
|
+
return PolicyVerdict(
|
|
128
|
+
decision=decision,
|
|
129
|
+
policy_names=_string_list(response.get("policyNames", []), "policyNames"),
|
|
130
|
+
reason=reason,
|
|
131
|
+
matched_policies=_string_list(response.get("matchedPolicies", []), "matchedPolicies"),
|
|
132
|
+
duration_ms=duration_ms,
|
|
133
|
+
tool_name=tool_name,
|
|
134
|
+
)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Persistent, bounded delivery state for FailproofAI instruct decisions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import sqlite3
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Sequence
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LedgerError(RuntimeError):
|
|
16
|
+
"""Instruction state could not be read or updated safely."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class InstructionAction:
|
|
21
|
+
block: bool
|
|
22
|
+
status: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class InstructionLedger:
|
|
26
|
+
def __init__(self, path: Path, *, ttl_seconds: int = 3600, max_rounds: int = 2) -> None:
|
|
27
|
+
self.path = path
|
|
28
|
+
self.ttl_seconds = max(60, int(ttl_seconds))
|
|
29
|
+
self.max_rounds = max(0, int(max_rounds))
|
|
30
|
+
self._init_lock = threading.Lock()
|
|
31
|
+
self._initialized = False
|
|
32
|
+
|
|
33
|
+
def _connect(self) -> sqlite3.Connection:
|
|
34
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
connection = sqlite3.connect(self.path, timeout=0.25, isolation_level=None)
|
|
36
|
+
connection.execute("PRAGMA busy_timeout = 250")
|
|
37
|
+
connection.execute("PRAGMA journal_mode = WAL")
|
|
38
|
+
connection.execute("PRAGMA synchronous = NORMAL")
|
|
39
|
+
return connection
|
|
40
|
+
|
|
41
|
+
def _ensure_schema(self) -> None:
|
|
42
|
+
if self._initialized:
|
|
43
|
+
return
|
|
44
|
+
with self._init_lock:
|
|
45
|
+
if self._initialized:
|
|
46
|
+
return
|
|
47
|
+
try:
|
|
48
|
+
with self._connect() as connection:
|
|
49
|
+
connection.execute(
|
|
50
|
+
"""
|
|
51
|
+
CREATE TABLE IF NOT EXISTS instruction_deliveries (
|
|
52
|
+
delivery_key TEXT PRIMARY KEY,
|
|
53
|
+
profile TEXT NOT NULL,
|
|
54
|
+
session_id TEXT NOT NULL,
|
|
55
|
+
task_id TEXT NOT NULL,
|
|
56
|
+
turn_id TEXT NOT NULL,
|
|
57
|
+
policy_fingerprint TEXT NOT NULL,
|
|
58
|
+
scope_key TEXT NOT NULL,
|
|
59
|
+
blocked_api_request_id TEXT NOT NULL,
|
|
60
|
+
acknowledged_api_request_id TEXT,
|
|
61
|
+
state TEXT NOT NULL,
|
|
62
|
+
created_at_ms INTEGER NOT NULL,
|
|
63
|
+
expires_at_ms INTEGER NOT NULL
|
|
64
|
+
)
|
|
65
|
+
"""
|
|
66
|
+
)
|
|
67
|
+
connection.execute(
|
|
68
|
+
"""
|
|
69
|
+
CREATE INDEX IF NOT EXISTS instruction_deliveries_turn
|
|
70
|
+
ON instruction_deliveries(profile, session_id, task_id, turn_id, expires_at_ms)
|
|
71
|
+
"""
|
|
72
|
+
)
|
|
73
|
+
except sqlite3.Error as exc:
|
|
74
|
+
raise LedgerError(f"could not initialize instruction state: {exc}") from exc
|
|
75
|
+
self._initialized = True
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _digest(parts: Sequence[str]) -> str:
|
|
79
|
+
encoded = json.dumps(list(parts), ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
|
80
|
+
return hashlib.sha256(encoded).hexdigest()
|
|
81
|
+
|
|
82
|
+
def decide(
|
|
83
|
+
self,
|
|
84
|
+
*,
|
|
85
|
+
profile: str,
|
|
86
|
+
session_id: str,
|
|
87
|
+
task_id: str,
|
|
88
|
+
turn_id: str,
|
|
89
|
+
api_request_id: str,
|
|
90
|
+
policy_names: Sequence[str],
|
|
91
|
+
reason: str,
|
|
92
|
+
scope_key: str,
|
|
93
|
+
now_ms: int | None = None,
|
|
94
|
+
) -> InstructionAction:
|
|
95
|
+
if not session_id or not turn_id or not api_request_id:
|
|
96
|
+
return InstructionAction(False, "missing-correlation-id")
|
|
97
|
+
|
|
98
|
+
self._ensure_schema()
|
|
99
|
+
now = int(time.time() * 1000) if now_ms is None else int(now_ms)
|
|
100
|
+
expires_at = now + self.ttl_seconds * 1000
|
|
101
|
+
task = task_id or "root"
|
|
102
|
+
policy_fingerprint = self._digest([*sorted(policy_names), reason])
|
|
103
|
+
delivery_key = self._digest(
|
|
104
|
+
[profile, session_id, task, turn_id, policy_fingerprint, scope_key]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
with self._connect() as connection:
|
|
109
|
+
connection.execute("BEGIN IMMEDIATE")
|
|
110
|
+
connection.execute(
|
|
111
|
+
"DELETE FROM instruction_deliveries WHERE expires_at_ms <= ?", (now,)
|
|
112
|
+
)
|
|
113
|
+
row = connection.execute(
|
|
114
|
+
"""
|
|
115
|
+
SELECT blocked_api_request_id, state
|
|
116
|
+
FROM instruction_deliveries
|
|
117
|
+
WHERE delivery_key = ?
|
|
118
|
+
""",
|
|
119
|
+
(delivery_key,),
|
|
120
|
+
).fetchone()
|
|
121
|
+
if row is not None:
|
|
122
|
+
blocked_request, state = row
|
|
123
|
+
if state == "issued" and blocked_request == api_request_id:
|
|
124
|
+
connection.commit()
|
|
125
|
+
return InstructionAction(True, "same-api-request")
|
|
126
|
+
if state == "issued":
|
|
127
|
+
connection.execute(
|
|
128
|
+
"""
|
|
129
|
+
UPDATE instruction_deliveries
|
|
130
|
+
SET state = 'acknowledged', acknowledged_api_request_id = ?
|
|
131
|
+
WHERE delivery_key = ?
|
|
132
|
+
""",
|
|
133
|
+
(api_request_id, delivery_key),
|
|
134
|
+
)
|
|
135
|
+
connection.commit()
|
|
136
|
+
return InstructionAction(False, "acknowledged")
|
|
137
|
+
|
|
138
|
+
rounds = connection.execute(
|
|
139
|
+
"""
|
|
140
|
+
SELECT COUNT(*)
|
|
141
|
+
FROM instruction_deliveries
|
|
142
|
+
WHERE profile = ? AND session_id = ? AND task_id = ? AND turn_id = ?
|
|
143
|
+
AND expires_at_ms > ?
|
|
144
|
+
""",
|
|
145
|
+
(profile, session_id, task, turn_id, now),
|
|
146
|
+
).fetchone()[0]
|
|
147
|
+
if rounds >= self.max_rounds:
|
|
148
|
+
connection.commit()
|
|
149
|
+
return InstructionAction(False, "turn-cap")
|
|
150
|
+
|
|
151
|
+
connection.execute(
|
|
152
|
+
"""
|
|
153
|
+
INSERT INTO instruction_deliveries (
|
|
154
|
+
delivery_key, profile, session_id, task_id, turn_id,
|
|
155
|
+
policy_fingerprint, scope_key, blocked_api_request_id,
|
|
156
|
+
acknowledged_api_request_id, state, created_at_ms, expires_at_ms
|
|
157
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, 'issued', ?, ?)
|
|
158
|
+
""",
|
|
159
|
+
(
|
|
160
|
+
delivery_key,
|
|
161
|
+
profile,
|
|
162
|
+
session_id,
|
|
163
|
+
task,
|
|
164
|
+
turn_id,
|
|
165
|
+
policy_fingerprint,
|
|
166
|
+
scope_key,
|
|
167
|
+
api_request_id,
|
|
168
|
+
now,
|
|
169
|
+
expires_at,
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
connection.commit()
|
|
173
|
+
return InstructionAction(True, "issued")
|
|
174
|
+
except (sqlite3.Error, OSError) as exc:
|
|
175
|
+
raise LedgerError(f"could not update instruction state: {exc}") from exc
|
|
176
|
+
|
|
177
|
+
def clear_session(self, session_id: str) -> None:
|
|
178
|
+
if not session_id:
|
|
179
|
+
return
|
|
180
|
+
self._ensure_schema()
|
|
181
|
+
try:
|
|
182
|
+
with self._connect() as connection:
|
|
183
|
+
connection.execute(
|
|
184
|
+
"DELETE FROM instruction_deliveries WHERE session_id = ?", (session_id,)
|
|
185
|
+
)
|
|
186
|
+
except (sqlite3.Error, OSError) as exc:
|
|
187
|
+
raise LedgerError(f"could not clear instruction state: {exc}") from exc
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: failproofai
|
|
2
|
+
version: 1.0.6-beta.0
|
|
3
|
+
description: Real-time FailproofAI policy enforcement for Hermes
|
|
4
|
+
manifest_version: 2
|
|
5
|
+
api_version: 1
|
|
6
|
+
homepage: https://github.com/FailproofAI/failproofai
|
|
7
|
+
tags: [security, policy, observability]
|
|
8
|
+
provides_hooks:
|
|
9
|
+
- pre_tool_call
|
|
10
|
+
- post_tool_call
|
|
11
|
+
- pre_llm_call
|
|
12
|
+
- on_session_start
|
|
13
|
+
- on_session_end
|
|
14
|
+
- on_session_reset
|
|
15
|
+
- on_session_finalize
|
|
16
|
+
- subagent_stop
|
|
17
|
+
config_schema:
|
|
18
|
+
failure_mode:
|
|
19
|
+
type: str
|
|
20
|
+
default: deny
|
|
21
|
+
description: Block or allow when the local FailproofAI evaluator is unavailable.
|
|
22
|
+
connect_timeout_ms:
|
|
23
|
+
type: int
|
|
24
|
+
default: 250
|
|
25
|
+
description: Maximum time to connect to the local FailproofAI daemon.
|
|
26
|
+
evaluation_timeout_ms:
|
|
27
|
+
type: int
|
|
28
|
+
default: 12000
|
|
29
|
+
description: Maximum total time allowed for one policy evaluation.
|
|
30
|
+
instruction_ttl_seconds:
|
|
31
|
+
type: int
|
|
32
|
+
default: 3600
|
|
33
|
+
description: Maximum lifetime of an instruction-delivery record.
|
|
34
|
+
max_instruction_rounds:
|
|
35
|
+
type: int
|
|
36
|
+
default: 2
|
|
37
|
+
description: Maximum distinct instruction interruptions in one Hermes turn.
|
|
@@ -139,7 +139,7 @@ var HmrTarget = /*#__PURE__*/ function(HmrTarget) {
|
|
|
139
139
|
HmrTarget["Server"] = "server";
|
|
140
140
|
return HmrTarget;
|
|
141
141
|
}({});
|
|
142
|
-
const nextVersion = "16.3.
|
|
142
|
+
const nextVersion = "16.3.5";
|
|
143
143
|
const ArchName = (0, _os.arch)();
|
|
144
144
|
const PlatformName = (0, _os.platform)();
|
|
145
145
|
function infoLog(...args) {
|