failproofai 1.0.4 → 1.0.5-beta.1
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 +6 -6
- 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/build-manifest.json +3 -3
- 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 +3 -3
- 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 +4 -4
- 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 +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 +3 -3
- 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 +3 -3
- 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 +3 -3
- 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 +3 -3
- 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 +2 -2
- 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 +3 -3
- 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 +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
- 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 +3 -3
- 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]__0-9ti7z._.js → [externals]__1_bftcl._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0u3wrvq._.js → [externals]__1msfs-h._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0faqz9x._.js +22 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1p-qi2t._.js → [root-of-the-server]__112b4go._.js} +22 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1l71g3y._.js +22 -0
- 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/{_1-akg6w._.js → _1c3k-8x._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +3 -0
- 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]__00y9ww2._.js +22 -0
- 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]__09flfsx._.js → [root-of-the-server]__05g1k7_._.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]__0sz_5na._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kaotix._.js +22 -0
- 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]__1u6sn69._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_02yoeej._.js → _0iv28v5._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1if9ixi._.js → _14c6q09._.js} +21 -2
- package/.next/standalone/.next/server/chunks/ssr/{_04rbnaq._.js → _17noq1a._.js} +2 -2
- 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 +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 +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_codex-projects_ts_0pqlw37._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- 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/{3tjji451hrgc0.js → 074xcclgow3yh.js} +1 -1
- package/.next/standalone/.next/static/chunks/{31u94yukw9fz-.js → 0qytob94jbzs6.js} +1 -1
- package/.next/standalone/.next/static/chunks/25cn9xpv4r1ow.js +1 -0
- package/.next/standalone/.next/static/chunks/{1j6o9min_6qa9.js → 26tf_lpco5n-v.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0-tyrpwuc3lla.js → 2b014kj28p0vz.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2lwipmv5etc8m.js → 3_fnbc-v5r-9e.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1c6aptmytnu6q.js → 3kltzmx9k_t6x.js} +1 -1
- package/.next/standalone/.next/static/chunks/{27cl8h-c2w7cd.js → 3pmi7qhtmniku.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2u8vh73tpw28_.js → 3q1rji38vuvr4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ca7octhod0g2.js → 3qxl_gibq3bas.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3p9_8x5ezce-9.js → 3vjaz4hld9oo8.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2zafgs90r_leq.js → 41ejnuj2d9g7_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-3p73obe0xmrxm.js → turbopack-1xgsp63yhrdu5.js} +1 -1
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +7 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/_version.py +1 -1
- package/.next/standalone/lib/download-session.ts +58 -13
- package/.next/standalone/lib/openclaw-db.ts +271 -0
- package/.next/standalone/lib/openclaw-projects.ts +54 -18
- package/.next/standalone/lib/openclaw-sessions.ts +75 -17
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/sdk/python/CHANGELOG.md +38 -8
- package/.next/standalone/sdk/python/failproofai_sdk/_events.py +20 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_redact.py +233 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +4 -1
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +5 -5
- package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +2 -2
- package/.next/standalone/sdk/python/tests/test_redaction.py +140 -0
- package/.next/standalone/sdk/python/tests/test_sdk.py +87 -0
- package/.next/standalone/sdk/python/tests/test_server_contract.py +60 -0
- package/.next/standalone/sdk/python/tests/test_site_docs.py +2 -2
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +466 -255
- package/dist/worker.mjs +301 -90
- package/lib/download-session.ts +58 -13
- package/lib/openclaw-db.ts +271 -0
- package/lib/openclaw-projects.ts +54 -18
- package/lib/openclaw-sessions.ts +75 -17
- package/openclaw-plugin/index.js +10 -6
- package/openclaw-plugin/instruct-retry-gate.js +89 -0
- package/package.json +9 -9
- package/src/hooks/policy-evaluator.ts +22 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00-s7h8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__190won8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1sc84bb._.js +0 -3
- package/.next/standalone/.next/static/chunks/2pd16tn5qmx13.js +0 -1
- /package/.next/standalone/.next/static/{dg8VQ7Ejlo0Qsh_x92oIt → Qexks15mjEoPt6cN3D2jb}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{dg8VQ7Ejlo0Qsh_x92oIt → Qexks15mjEoPt6cN3D2jb}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{dg8VQ7Ejlo0Qsh_x92oIt → Qexks15mjEoPt6cN3D2jb}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from failproofai_sdk._redact import redact_json_line, redaction_enabled, scrub_string
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.parametrize(
|
|
9
|
+
("raw", "marker"),
|
|
10
|
+
[
|
|
11
|
+
("sk-ant-api03-AbCdEfGhIjKlMnOpQrStUv", "anthropic-key"),
|
|
12
|
+
("sk-proj-abcdefghijklmnopqrstuvwxyz", "openai-key"),
|
|
13
|
+
("ghp_abcdefghijklmnopqrstuvwxyz0123", "github-token"),
|
|
14
|
+
("AKIAIOSFODNN7EXAMPLE0000", "aws-access-key-id"),
|
|
15
|
+
(
|
|
16
|
+
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.sIgNaTuRe0123456789ab",
|
|
17
|
+
"jwt",
|
|
18
|
+
),
|
|
19
|
+
("Authorization: Bearer tok-🔑🔑🔑-SECRETTAIL", "bearer-token"),
|
|
20
|
+
("Authorization: Bearer 🔑🔑", "bearer-token"),
|
|
21
|
+
("API_TOKEN=пароль-очень-ENDOFSECRET", "secret-assignment"),
|
|
22
|
+
("API_TOKEN=密码密码密码", "secret-assignment"),
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_minimal_redaction_matches_daemon_secret_shapes(raw, marker):
|
|
26
|
+
scrubbed, count = scrub_string(raw)
|
|
27
|
+
assert count == 1
|
|
28
|
+
assert scrubbed == f"[redacted:{marker}]" or scrubbed.endswith(f"[redacted:{marker}]")
|
|
29
|
+
assert "SECRETTAIL" not in scrubbed
|
|
30
|
+
assert "ENDOFSECRET" not in scrubbed
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.parametrize(
|
|
34
|
+
"value",
|
|
35
|
+
[
|
|
36
|
+
"this is a risk-averse approach",
|
|
37
|
+
"AWS_REGION=us-east-1",
|
|
38
|
+
"key=someLongIdentifier",
|
|
39
|
+
"api_key=$OPENAI_API_KEY",
|
|
40
|
+
"--token=<your-token-here>",
|
|
41
|
+
],
|
|
42
|
+
)
|
|
43
|
+
def test_minimal_redaction_leaves_known_false_positives_alone(value):
|
|
44
|
+
assert scrub_string(value) == (value, 0)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_redaction_preserves_json_structure_and_is_deterministic():
|
|
48
|
+
encoded = json.dumps(
|
|
49
|
+
{
|
|
50
|
+
"type": "tool_use",
|
|
51
|
+
"input": {"command": 'API_KEY="abcdefghijklmnop" && echo done'},
|
|
52
|
+
"nested": [{"output": "ghp_abcdefghijklmnopqrstuvwxyz0123"}],
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
first = redact_json_line(encoded)
|
|
56
|
+
second = redact_json_line(encoded)
|
|
57
|
+
assert first == second
|
|
58
|
+
event = json.loads(first)
|
|
59
|
+
assert event["type"] == "tool_use"
|
|
60
|
+
assert event["input"]["command"] == 'API_KEY="[redacted:secret-assignment]" && echo done'
|
|
61
|
+
assert event["nested"][0]["output"] == "[redacted:github-token]"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.parametrize(
|
|
65
|
+
"field",
|
|
66
|
+
["password", "client_secret", "api_key", "access_token", "apiKey", "accessToken"],
|
|
67
|
+
)
|
|
68
|
+
def test_secret_named_fields_redact_opaque_values(field):
|
|
69
|
+
encoded = json.dumps({"nested": {field: "abcdefghijklmnop"}})
|
|
70
|
+
event = json.loads(redact_json_line(encoded))
|
|
71
|
+
assert event["nested"][field] == "[redacted:secret-assignment]"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.parametrize(
|
|
75
|
+
"field",
|
|
76
|
+
["password", "client_secret", "api_key", "access_token", "apiKey", "accessToken"],
|
|
77
|
+
)
|
|
78
|
+
def test_secret_named_arrays_redact_opaque_elements(field):
|
|
79
|
+
encoded = json.dumps(
|
|
80
|
+
{"nested": {field: ["abcdefghijklmnop", ["qrstuvwxyzabcdef"], "short", 7, None]}}
|
|
81
|
+
)
|
|
82
|
+
event = json.loads(redact_json_line(encoded))
|
|
83
|
+
assert event["nested"][field] == [
|
|
84
|
+
"[redacted:secret-assignment]",
|
|
85
|
+
["[redacted:secret-assignment]"],
|
|
86
|
+
"short",
|
|
87
|
+
7,
|
|
88
|
+
None,
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_arrays_under_ordinary_fields_are_left_alone():
|
|
93
|
+
encoded = json.dumps({"messages": ["an ordinary sentence of text", ["another long value"]]})
|
|
94
|
+
assert redact_json_line(encoded) == encoded
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_credential_shaped_dictionary_keys_are_redacted_without_colliding():
|
|
98
|
+
first = "API_KEY=abcdefghijklmnop"
|
|
99
|
+
second = "API_KEY=qrstuvwxyzabcdef"
|
|
100
|
+
event = json.loads(redact_json_line(json.dumps({"nested": {first: 1, second: 2}})))
|
|
101
|
+
keys = list(event["nested"])
|
|
102
|
+
assert first not in keys
|
|
103
|
+
assert second not in keys
|
|
104
|
+
assert len(keys) == 2
|
|
105
|
+
assert sorted(event["nested"].values()) == [1, 2]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.parametrize(
|
|
109
|
+
("raw", "expected"),
|
|
110
|
+
[
|
|
111
|
+
(
|
|
112
|
+
"PASSWORD='abcdefghijkL\"mnopQRST'",
|
|
113
|
+
"PASSWORD='[redacted:secret-assignment]'",
|
|
114
|
+
),
|
|
115
|
+
(
|
|
116
|
+
'PASSWORD="abcdefghijkL\'mnopQRST"',
|
|
117
|
+
'PASSWORD="[redacted:secret-assignment]"',
|
|
118
|
+
),
|
|
119
|
+
],
|
|
120
|
+
)
|
|
121
|
+
def test_quoted_assignment_stops_only_at_its_matching_quote(raw, expected):
|
|
122
|
+
assert scrub_string(raw) == (expected, 1)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_plain_string_scan_does_not_copy_every_remaining_suffix():
|
|
126
|
+
class NoTailSlices(str):
|
|
127
|
+
def __getitem__(self, item):
|
|
128
|
+
if isinstance(item, slice) and item.stop is None and (item.start or 0) > 0:
|
|
129
|
+
raise AssertionError(f"copied the remaining tail at {item.start}")
|
|
130
|
+
return super().__getitem__(item)
|
|
131
|
+
|
|
132
|
+
value = NoTailSlices("ordinary payload text " * 100)
|
|
133
|
+
assert scrub_string(value) == (value, 0)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.mark.parametrize("config", [None, [], {"collector": None}, {"collector": "minimal"}])
|
|
137
|
+
def test_malformed_redaction_config_fails_closed(tmp_path, config):
|
|
138
|
+
(tmp_path / "config.json").write_text(json.dumps(config), encoding="utf-8")
|
|
139
|
+
|
|
140
|
+
assert redaction_enabled(tmp_path / "custom-agents") is True
|
|
@@ -622,6 +622,93 @@ def test_writer_multiple_events_in_one_file(tmp_path):
|
|
|
622
622
|
resolver.set_base_dir(original)
|
|
623
623
|
|
|
624
624
|
|
|
625
|
+
def test_writer_redacts_credentials_before_the_spool_reaches_disk(tmp_path):
|
|
626
|
+
import failproofai_sdk._resolver as resolver
|
|
627
|
+
original = resolver._base_dir
|
|
628
|
+
try:
|
|
629
|
+
resolver.set_base_dir(tmp_path)
|
|
630
|
+
writer = EventWriter(flush_interval=60)
|
|
631
|
+
writer.submit(
|
|
632
|
+
{
|
|
633
|
+
"timestamp": "t",
|
|
634
|
+
"session_id": "s1",
|
|
635
|
+
"agent_id": "a1",
|
|
636
|
+
"type": "tool_use",
|
|
637
|
+
"input": {
|
|
638
|
+
"command": "API_KEY=abcdefghijklmnop",
|
|
639
|
+
"password": "qrstuvwxyzabcdef",
|
|
640
|
+
"API_KEY=secretvalue123456": True,
|
|
641
|
+
},
|
|
642
|
+
}
|
|
643
|
+
)
|
|
644
|
+
writer.flush_now()
|
|
645
|
+
|
|
646
|
+
raw = next((tmp_path / "events").glob("*.jsonl")).read_text()
|
|
647
|
+
assert "abcdefghijklmnop" not in raw
|
|
648
|
+
assert "qrstuvwxyzabcdef" not in raw
|
|
649
|
+
assert "API_KEY=secretvalue123456" not in raw
|
|
650
|
+
assert "API_KEY=[redacted:secret-assignment]" in raw
|
|
651
|
+
finally:
|
|
652
|
+
resolver.set_base_dir(original)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def test_writer_redacts_opaque_secrets_in_secret_named_arrays(tmp_path):
|
|
656
|
+
import failproofai_sdk._resolver as resolver
|
|
657
|
+
original = resolver._base_dir
|
|
658
|
+
try:
|
|
659
|
+
resolver.set_base_dir(tmp_path)
|
|
660
|
+
writer = EventWriter(flush_interval=60)
|
|
661
|
+
writer.submit(
|
|
662
|
+
{
|
|
663
|
+
"timestamp": "t",
|
|
664
|
+
"session_id": "s1",
|
|
665
|
+
"agent_id": "a1",
|
|
666
|
+
"type": "tool_use",
|
|
667
|
+
"input": {
|
|
668
|
+
"password": ["ordinarysecretvalue1"],
|
|
669
|
+
"client_secret": ["ordinarysecretvalue2"],
|
|
670
|
+
"api_key": ["ordinarysecretvalue3"],
|
|
671
|
+
"access_token": [["ordinarysecretvalue4"]],
|
|
672
|
+
},
|
|
673
|
+
}
|
|
674
|
+
)
|
|
675
|
+
writer.flush_now()
|
|
676
|
+
|
|
677
|
+
raw = next((tmp_path / "events").glob("*.jsonl")).read_text()
|
|
678
|
+
assert "ordinarysecretvalue" not in raw
|
|
679
|
+
assert raw.count("[redacted:secret-assignment]") == 4
|
|
680
|
+
finally:
|
|
681
|
+
resolver.set_base_dir(original)
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def test_writer_honours_collector_redact_off(tmp_path):
|
|
685
|
+
import failproofai_sdk._resolver as resolver
|
|
686
|
+
original = resolver._base_dir
|
|
687
|
+
try:
|
|
688
|
+
base_dir = tmp_path / "custom-agents"
|
|
689
|
+
(tmp_path / "config.json").write_text(
|
|
690
|
+
json.dumps({"collector": {"redact": "off"}}), encoding="utf-8"
|
|
691
|
+
)
|
|
692
|
+
resolver.set_base_dir(base_dir)
|
|
693
|
+
writer = EventWriter(flush_interval=60)
|
|
694
|
+
writer.submit(
|
|
695
|
+
{
|
|
696
|
+
"timestamp": "t",
|
|
697
|
+
"session_id": "s1",
|
|
698
|
+
"agent_id": "a1",
|
|
699
|
+
"type": "tool_use",
|
|
700
|
+
"input": {"command": "API_KEY=abcdefghijklmnop"},
|
|
701
|
+
}
|
|
702
|
+
)
|
|
703
|
+
writer.flush_now()
|
|
704
|
+
|
|
705
|
+
raw = next((base_dir / "events").glob("*.jsonl")).read_text()
|
|
706
|
+
assert "API_KEY=abcdefghijklmnop" in raw
|
|
707
|
+
assert "[redacted:" not in raw
|
|
708
|
+
finally:
|
|
709
|
+
resolver.set_base_dir(original)
|
|
710
|
+
|
|
711
|
+
|
|
625
712
|
def test_writer_coerces_unserializable_payload_values(tmp_path):
|
|
626
713
|
import failproofai_sdk._resolver as resolver
|
|
627
714
|
original = resolver._base_dir
|
|
@@ -427,6 +427,66 @@ def test_the_promoted_numeric_set_matches_the_one_ingest_lifts():
|
|
|
427
427
|
assert _events._PROMOTED_NUMERIC == PROMOTED_NUMERIC
|
|
428
428
|
|
|
429
429
|
|
|
430
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
431
|
+
# A promoted column the caller does not have costs a warning, not the event
|
|
432
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
@pytest.mark.parametrize("name", sorted(_events._PROMOTED_STRING))
|
|
436
|
+
def test_a_promoted_string_passed_as_none_is_omitted_not_refused(name):
|
|
437
|
+
"""None means "I have no value", and that must not cost the whole event.
|
|
438
|
+
|
|
439
|
+
`agent_end(error_type=None)` is the shape every successful run produces: the
|
|
440
|
+
field is populated only on a failing outcome, so the ordinary success path
|
|
441
|
+
passes None. Refusing it raised inside the caller's emit helper, and a helper
|
|
442
|
+
that swallows telemetry errors — which every one of them does, because
|
|
443
|
+
telemetry must not break a run — turned that into a silently missing event.
|
|
444
|
+
"""
|
|
445
|
+
recorder = _Recorder()
|
|
446
|
+
EventNamespace(recorder).agent_start(session_id="s", agent_id="a", **{name: None})
|
|
447
|
+
assert name not in recorder.entries[0]
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@pytest.mark.parametrize("name", sorted(_events._PROMOTED_NUMERIC))
|
|
451
|
+
def test_a_promoted_numeric_passed_as_none_in_fields_is_omitted_too(name):
|
|
452
|
+
"""The named-parameter path already dropped None; `**fields` did not.
|
|
453
|
+
|
|
454
|
+
`_build` omits None only from a dataclass's own `specifics` — `extra` is
|
|
455
|
+
merged verbatim — so the same value went to the wire as an explicit JSON
|
|
456
|
+
null depending only on which door it came through.
|
|
457
|
+
"""
|
|
458
|
+
recorder = _Recorder()
|
|
459
|
+
EventNamespace(recorder).agent_start(session_id="s", agent_id="a", **{name: None})
|
|
460
|
+
assert name not in recorder.entries[0]
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def test_a_dropped_promoted_column_is_warned_about(caplog):
|
|
464
|
+
"""Silently dropping it would hide a real mistake; refusing it costs the event."""
|
|
465
|
+
recorder = _Recorder()
|
|
466
|
+
with caplog.at_level("WARNING", logger="failproofai_sdk._events"):
|
|
467
|
+
EventNamespace(recorder).agent_end(session_id="s", agent_id="a", error_type=None)
|
|
468
|
+
assert "error_type" in caplog.text
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def test_agent_end_on_a_successful_run_is_still_emitted():
|
|
472
|
+
"""The regression this all exists for, in the shape the caller actually sends."""
|
|
473
|
+
recorder = _Recorder()
|
|
474
|
+
EventNamespace(recorder).agent_end(
|
|
475
|
+
session_id="s", agent_id="a", outcome="success", summary=None, error_type=None
|
|
476
|
+
)
|
|
477
|
+
assert [e["type"] for e in recorder.entries] == ["agent_end"]
|
|
478
|
+
assert recorder.entries[0]["outcome"] == "success"
|
|
479
|
+
assert "error_type" not in recorder.entries[0]
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@pytest.mark.parametrize("name", sorted(_events._PROMOTED_STRING))
|
|
483
|
+
def test_a_promoted_string_with_a_real_value_still_goes_through(name):
|
|
484
|
+
"""Dropping None must not also drop the values that matter."""
|
|
485
|
+
recorder = _Recorder()
|
|
486
|
+
EventNamespace(recorder).agent_start(session_id="s", agent_id="a", **{name: "real"})
|
|
487
|
+
assert recorder.entries[0][name] == "real"
|
|
488
|
+
|
|
489
|
+
|
|
430
490
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
431
491
|
# The MEASURED duration is bound by the same u32 range a caller is held to
|
|
432
492
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -315,8 +315,8 @@ def test_no_cross_adapter_page_presents_one_adapters_option_as_universal():
|
|
|
315
315
|
and CrewAI has none — and `instrument()` drops options an adapter does not
|
|
316
316
|
read, so `instrument("crewai", capture_content=False)` raised nothing and
|
|
317
317
|
changed nothing. A reader on regulated data shipped believing prompts and
|
|
318
|
-
completions had stopped being recorded
|
|
319
|
-
|
|
318
|
+
completions had stopped being recorded. Minimal credential redaction is not
|
|
319
|
+
a substitute: arbitrary regulated content need not resemble a secret.
|
|
320
320
|
|
|
321
321
|
The four per-framework pages are checked elsewhere; these are the pages that
|
|
322
322
|
speak about all of them at once and so must name the difference.
|
|
@@ -9,7 +9,7 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
|
|
|
9
9
|
const hostname = process.env.HOSTNAME || '0.0.0.0'
|
|
10
10
|
|
|
11
11
|
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
|
|
12
|
-
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.
|
|
12
|
+
const nextConfig = {"env":{"NEXT_PUBLIC_APP_VERSION":"1.0.5-beta.1"},"typescript":{"ignoreBuildErrors":false},"typedRoutes":false,"distDir":"./.next","cleanDistDir":true,"assetPrefix":"","cacheMaxMemorySize":52428800,"configOrigin":"next.config.ts","useFileSystemPublicRoutes":true,"generateEtags":true,"pageExtensions":["tsx","ts","jsx","js"],"instrumentationClientInject":[],"poweredByHeader":true,"compress":true,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":14400,"formats":["image/webp"],"maximumRedirects":3,"maximumResponseBody":50000000,"dangerouslyAllowLocalIP":false,"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"attachment","localPatterns":[{"pathname":"**","search":""}],"remotePatterns":[],"qualities":[75],"unoptimized":true,"customCacheHandler":false},"devIndicators":{"position":"bottom-left"},"onDemandEntries":{"maxInactiveAge":60000,"pagesBufferLength":5},"basePath":"","sassOptions":{},"trailingSlash":false,"i18n":null,"productionBrowserSourceMaps":false,"excludeDefaultMomentLocales":true,"reactProductionProfiling":false,"reactStrictMode":null,"reactMaxHeadersLength":6000,"httpAgentOptions":{"keepAlive":true},"logging":{"serverFunctions":true,"browserToTerminal":"warn"},"compiler":{},"expireTime":31536000,"staticPageGenerationTimeout":60,"output":"standalone","modularizeImports":{"@mui/icons-material":{"transform":"@mui/icons-material/{{member}}"},"lodash":{"transform":"lodash/{{member}}"}},"outputFileTracingRoot":"/home/runner/work/failproofai/failproofai","enablePrerenderSourceMaps":true,"cacheComponents":false,"cacheLife":{"default":{"stale":300,"revalidate":900,"expire":4294967294},"seconds":{"stale":30,"revalidate":1,"expire":60},"minutes":{"stale":300,"revalidate":60,"expire":3600},"hours":{"stale":300,"revalidate":3600,"expire":86400},"days":{"stale":300,"revalidate":86400,"expire":604800},"weeks":{"stale":300,"revalidate":604800,"expire":2592000},"max":{"stale":300,"revalidate":2592000,"expire":31536000}},"cacheHandlers":{},"experimental":{"appNewScrollHandler":true,"coldCacheBadge":false,"devValidationWorker":true,"useSkewCookie":false,"cssChunking":true,"multiZoneDraftMode":false,"appNavFailHandling":false,"prerenderEarlyExit":true,"serverMinification":true,"linkNoTouchStart":false,"caseSensitiveRoutes":false,"cachedNavigations":false,"dynamicOnHover":false,"useOffline":false,"varyParams":true,"optimisticRouting":true,"instrumentationClientRouterTransitionEvents":false,"prefetchInlining":{"maxSize":2048,"maxBundleSize":10240},"preloadEntriesOnStart":true,"clientRouterFilter":true,"clientRouterFilterRedirects":false,"fetchCacheKeyPrefix":"","proxyPrefetch":"flexible","optimisticClientCache":true,"manualClientBasePath":false,"cpus":3,"memoryBasedWorkersCount":false,"imgOptConcurrency":null,"imgOptOperationCache":null,"imgOptTimeoutInSeconds":7,"imgOptMaxInputPixels":268402689,"imgOptSequentialRead":null,"isrFlushToDisk":true,"workerThreads":false,"optimizeCss":false,"nextScriptWorkers":false,"scrollRestoration":false,"externalDir":false,"devMemoryThresholdRestart":true,"disableOptimizedLoading":false,"gzipSize":true,"craCompat":false,"esmExternals":true,"fullySpecified":false,"swcTraceProfiling":false,"forceSwcTransforms":false,"requestInsights":false,"largePageDataBytes":128000,"typedEnv":false,"parallelServerCompiles":false,"parallelServerBuildTraces":false,"ppr":false,"authInterrupts":false,"webpackMemoryOptimizations":false,"optimizeServerReact":true,"strictRouteTypes":false,"useTypeScriptCli":true,"removeUncaughtErrorAndRejectionListeners":false,"validateRSCRequestHeaders":true,"staleTimes":{"dynamic":0,"static":300},"reactDebugChannel":true,"serverComponentsHmrCache":true,"serverComponentsHmrCancellation":false,"staticGenerationMaxConcurrency":8,"staticGenerationMinPagesPerWorker":25,"transitionIndicator":false,"gestureTransition":false,"inlineCss":false,"useCache":false,"globalNotFound":false,"browserDebugInfoInTerminal":"warn","lockDistDir":true,"proxyClientMaxBodySize":10485760,"hideLogsAfterAbort":false,"mcpServer":true,"turbopackFileSystemCacheForDev":true,"turbopackFileSystemCacheForBuild":true,"turbopackInferModuleSideEffects":true,"turbopackPluginRuntimeStrategy":"childProcesses","turbopackMemoryEvictionMode":"auto","optimizePackageImports":["lucide-react","date-fns","lodash-es","ramda","antd","react-bootstrap","ahooks","@ant-design/icons","@headlessui/react","@headlessui-float/react","@heroicons/react/20/solid","@heroicons/react/24/solid","@heroicons/react/24/outline","@visx/visx","@tremor/react","rxjs","@mui/material","@mui/icons-material","recharts","react-use","effect","@effect/schema","@effect/platform","@effect/platform-node","@effect/platform-browser","@effect/platform-bun","@effect/sql","@effect/sql-mssql","@effect/sql-mysql2","@effect/sql-pg","@effect/sql-sqlite-node","@effect/sql-sqlite-bun","@effect/sql-sqlite-wasm","@effect/sql-sqlite-react-native","@effect/rpc","@effect/rpc-http","@effect/typeclass","@effect/experimental","@effect/opentelemetry","@material-ui/core","@material-ui/icons","@tabler/icons-react","mui-core","react-icons/ai","react-icons/bi","react-icons/bs","react-icons/cg","react-icons/ci","react-icons/di","react-icons/fa","react-icons/fa6","react-icons/fc","react-icons/fi","react-icons/gi","react-icons/go","react-icons/gr","react-icons/hi","react-icons/hi2","react-icons/im","react-icons/io","react-icons/io5","react-icons/lia","react-icons/lib","react-icons/lu","react-icons/md","react-icons/pi","react-icons/ri","react-icons/rx","react-icons/si","react-icons/sl","react-icons/tb","react-icons/tfi","react-icons/ti","react-icons/vsc","react-icons/wi"],"useCacheTimeout":54,"instantInsights":{"validationLevel":"warning"},"trustHostHeader":false,"isExperimentalCompile":false},"htmlLimitedBots":"[\\w-]+-Google|Google-[\\w-]+|Chrome-Lighthouse|Slurp|DuckDuckBot|baiduspider|yandex|sogou|bitlybot|tumblr|vkShare|quora link preview|redditbot|ia_archiver|Bingbot|BingPreview|applebot|facebookexternalhit|facebookcatalog|Twitterbot|LinkedInBot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|Yeti|googleweblight","bundlePagesRouterDependencies":false,"configFileName":"next.config.ts","outputFileTracingExcludes":{"*":["node_modules/@img/**","node_modules/sharp/**"]},"turbopack":{"root":"/home/runner/work/failproofai/failproofai"},"repoRoot":"/home/runner/work/failproofai/failproofai","distDirRoot":".next"}
|
|
13
13
|
|
|
14
14
|
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
|
|
15
15
|
|