@goplus/agentguard 1.1.28-beta.1 → 1.1.28
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/README.md +3 -2
- package/dist/action/detectors/exec.d.ts.map +1 -1
- package/dist/action/detectors/exec.js +265 -4
- package/dist/action/detectors/exec.js.map +1 -1
- package/dist/action/detectors/network.d.ts.map +1 -1
- package/dist/action/detectors/network.js +63 -0
- package/dist/action/detectors/network.js.map +1 -1
- package/dist/adapters/openclaw-plugin.d.ts.map +1 -1
- package/dist/adapters/openclaw-plugin.js +2 -0
- package/dist/adapters/openclaw-plugin.js.map +1 -1
- package/dist/cli.js +14 -1
- package/dist/cli.js.map +1 -1
- package/dist/installers.d.ts +1 -0
- package/dist/installers.d.ts.map +1 -1
- package/dist/installers.js +144 -6
- package/dist/installers.js.map +1 -1
- package/dist/runtime/evaluator.d.ts +4 -1
- package/dist/runtime/evaluator.d.ts.map +1 -1
- package/dist/runtime/evaluator.js +59 -10
- package/dist/runtime/evaluator.js.map +1 -1
- package/dist/runtime/protect.d.ts +1 -0
- package/dist/runtime/protect.d.ts.map +1 -1
- package/dist/runtime/protect.js +3 -1
- package/dist/runtime/protect.js.map +1 -1
- package/dist/runtime/types.d.ts +1 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/tests/action.test.js +159 -1
- package/dist/tests/action.test.js.map +1 -1
- package/dist/tests/cli-init.test.js +8 -3
- package/dist/tests/cli-init.test.js.map +1 -1
- package/dist/tests/feed-cron.test.js +1 -1
- package/dist/tests/feed-cron.test.js.map +1 -1
- package/dist/tests/installer.test.js +47 -7
- package/dist/tests/installer.test.js.map +1 -1
- package/dist/tests/integration.test.js +26 -0
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/mcpb-manifest.test.d.ts +2 -0
- package/dist/tests/mcpb-manifest.test.d.ts.map +1 -0
- package/dist/tests/mcpb-manifest.test.js +83 -0
- package/dist/tests/mcpb-manifest.test.js.map +1 -0
- package/dist/tests/runtime-cloud.test.js +111 -0
- package/dist/tests/runtime-cloud.test.js.map +1 -1
- package/dist/utils/system-paths.d.ts +14 -0
- package/dist/utils/system-paths.d.ts.map +1 -0
- package/dist/utils/system-paths.js +172 -0
- package/dist/utils/system-paths.js.map +1 -0
- package/docs/SECURITY-POLICY.md +22 -0
- package/docs/hermes.md +46 -15
- package/docs/mcpb-build.md +49 -0
- package/package.json +5 -2
- package/plugins/hermes/README.md +78 -0
- package/plugins/hermes/__init__.py +13 -0
- package/plugins/hermes/bridge.py +305 -0
- package/plugins/hermes/plugin.py +116 -0
- package/plugins/hermes/plugin.yaml +19 -0
- package/plugins/hermes/tests/conftest.py +8 -0
- package/plugins/hermes/tests/helpers.py +70 -0
- package/plugins/hermes/tests/test_allow.py +30 -0
- package/plugins/hermes/tests/test_ask.py +18 -0
- package/plugins/hermes/tests/test_block.py +38 -0
- package/plugins/hermes/tests/test_command.py +29 -0
- package/plugins/hermes/tests/test_failmodes.py +57 -0
- package/plugins/hermes/tests/test_post.py +19 -0
- package/plugins/hermes/tests/test_resolution.py +35 -0
- package/plugins/hermes/tests/test_validation.py +43 -0
- package/skills/agentguard/action-policies.md +22 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Engine failures fail closed (pre) unless fail-open is requested."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from helpers import make_protect_runner, register_with
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_timeout_fails_closed_for_mapped_tool(monkeypatch):
|
|
11
|
+
monkeypatch.delenv("AGENTGUARD_HERMES_FAIL_OPEN", raising=False)
|
|
12
|
+
runner = make_protect_runner(raises=subprocess.TimeoutExpired(cmd="agentguard", timeout=10))
|
|
13
|
+
ctx, _ = register_with(runner)
|
|
14
|
+
result = ctx.hooks["pre_tool_call"]("terminal", {"command": "git status"})
|
|
15
|
+
assert isinstance(result, dict)
|
|
16
|
+
assert result["action"] == "block"
|
|
17
|
+
assert "fail-closed" in result["message"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_os_error_fails_closed(monkeypatch):
|
|
21
|
+
monkeypatch.delenv("AGENTGUARD_HERMES_FAIL_OPEN", raising=False)
|
|
22
|
+
runner = make_protect_runner(raises=OSError("boom"))
|
|
23
|
+
ctx, _ = register_with(runner)
|
|
24
|
+
result = ctx.hooks["pre_tool_call"]("write_file", {"path": "/etc/hosts"})
|
|
25
|
+
assert isinstance(result, dict)
|
|
26
|
+
assert result["action"] == "block"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_fail_open_env_allows_on_engine_error(monkeypatch):
|
|
30
|
+
monkeypatch.setenv("AGENTGUARD_HERMES_FAIL_OPEN", "1")
|
|
31
|
+
runner = make_protect_runner(raises=OSError("boom"))
|
|
32
|
+
ctx, _ = register_with(runner)
|
|
33
|
+
result = ctx.hooks["pre_tool_call"]("terminal", {"command": "git status"})
|
|
34
|
+
assert result is None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_post_phase_never_blocks_on_engine_error(monkeypatch):
|
|
38
|
+
monkeypatch.delenv("AGENTGUARD_HERMES_FAIL_OPEN", raising=False)
|
|
39
|
+
runner = make_protect_runner(raises=subprocess.TimeoutExpired(cmd="agentguard", timeout=10))
|
|
40
|
+
ctx, _ = register_with(runner)
|
|
41
|
+
result = ctx.hooks["post_tool_call"]("terminal", {"command": "rm -rf /"})
|
|
42
|
+
assert result is None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_malformed_output_with_block_exit_code_blocks():
|
|
46
|
+
runner = make_protect_runner(stdout="not-json", returncode=2)
|
|
47
|
+
ctx, _ = register_with(runner)
|
|
48
|
+
result = ctx.hooks["pre_tool_call"]("terminal", {"command": "rm -rf /"})
|
|
49
|
+
assert isinstance(result, dict)
|
|
50
|
+
assert result["action"] == "block"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_malformed_output_with_ok_exit_code_allows():
|
|
54
|
+
runner = make_protect_runner(stdout="not-json", returncode=0)
|
|
55
|
+
ctx, _ = register_with(runner)
|
|
56
|
+
result = ctx.hooks["pre_tool_call"]("terminal", {"command": "echo hi"})
|
|
57
|
+
assert result is None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""post_tool_call is audit-only and never blocks."""
|
|
2
|
+
|
|
3
|
+
from helpers import make_protect_runner, register_with
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_post_tool_call_never_blocks_even_on_block_decision():
|
|
7
|
+
ctx, _ = register_with(make_protect_runner(decision="block"))
|
|
8
|
+
result = ctx.hooks["post_tool_call"]("terminal", {"command": "rm -rf /"})
|
|
9
|
+
assert result is None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_bridge_post_phase_returns_none():
|
|
13
|
+
_, guard = register_with(make_protect_runner(decision="block"))
|
|
14
|
+
decision = guard.evaluate(
|
|
15
|
+
event="post_tool_call",
|
|
16
|
+
tool_name="terminal",
|
|
17
|
+
args={"command": "rm -rf /"},
|
|
18
|
+
)
|
|
19
|
+
assert decision is None
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Engine resolution: npx is opt-in; explicit binary is preferred."""
|
|
2
|
+
|
|
3
|
+
import bridge
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _only_npx(name):
|
|
7
|
+
return "/usr/bin/npx" if name == "npx" else None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_npx_not_used_without_optin(monkeypatch, tmp_path):
|
|
11
|
+
monkeypatch.delenv("AGENTGUARD_BIN", raising=False)
|
|
12
|
+
monkeypatch.delenv("AGENTGUARD_HERMES_HOOK", raising=False)
|
|
13
|
+
monkeypatch.delenv("AGENTGUARD_HERMES_ALLOW_NPX", raising=False)
|
|
14
|
+
monkeypatch.setattr(bridge.shutil, "which", _only_npx)
|
|
15
|
+
monkeypatch.setattr(bridge.Path, "home", classmethod(lambda cls: tmp_path))
|
|
16
|
+
argv, mode = bridge.AgentGuardBridge._resolve_invocation()
|
|
17
|
+
assert argv is None and mode is None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_npx_used_with_optin(monkeypatch, tmp_path):
|
|
21
|
+
monkeypatch.delenv("AGENTGUARD_BIN", raising=False)
|
|
22
|
+
monkeypatch.delenv("AGENTGUARD_HERMES_HOOK", raising=False)
|
|
23
|
+
monkeypatch.setenv("AGENTGUARD_HERMES_ALLOW_NPX", "1")
|
|
24
|
+
monkeypatch.setattr(bridge.shutil, "which", _only_npx)
|
|
25
|
+
monkeypatch.setattr(bridge.Path, "home", classmethod(lambda cls: tmp_path))
|
|
26
|
+
argv, mode = bridge.AgentGuardBridge._resolve_invocation()
|
|
27
|
+
assert mode == "protect" and argv[0] == "npx"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_agentguard_bin_is_preferred(monkeypatch, tmp_path):
|
|
31
|
+
monkeypatch.setenv("AGENTGUARD_BIN", "/opt/agentguard")
|
|
32
|
+
monkeypatch.delenv("AGENTGUARD_HERMES_HOOK", raising=False)
|
|
33
|
+
monkeypatch.setattr(bridge.Path, "home", classmethod(lambda cls: tmp_path))
|
|
34
|
+
argv, mode = bridge.AgentGuardBridge._resolve_invocation()
|
|
35
|
+
assert mode == "protect" and argv == ["/opt/agentguard", "protect"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Malformed mapped-tool payloads fail closed before the engine is invoked."""
|
|
2
|
+
|
|
3
|
+
from helpers import make_protect_runner, register_with
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_terminal_without_command_blocks_without_engine():
|
|
7
|
+
calls = []
|
|
8
|
+
ctx, _ = register_with(make_protect_runner(decision="allow", calls=calls))
|
|
9
|
+
result = ctx.hooks["pre_tool_call"]("terminal", {})
|
|
10
|
+
assert isinstance(result, dict) and result["action"] == "block"
|
|
11
|
+
assert "missing" in result["message"]
|
|
12
|
+
assert calls == [] # engine never invoked on a malformed payload
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_write_file_without_path_blocks():
|
|
16
|
+
ctx, _ = register_with(make_protect_runner(decision="allow"))
|
|
17
|
+
result = ctx.hooks["pre_tool_call"]("write_file", {"content": "x"})
|
|
18
|
+
assert isinstance(result, dict) and result["action"] == "block"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_web_extract_without_url_blocks():
|
|
22
|
+
ctx, _ = register_with(make_protect_runner(decision="allow"))
|
|
23
|
+
result = ctx.hooks["pre_tool_call"]("web_extract", {})
|
|
24
|
+
assert isinstance(result, dict) and result["action"] == "block"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_malformed_blocks_even_under_fail_open(monkeypatch):
|
|
28
|
+
monkeypatch.setenv("AGENTGUARD_HERMES_FAIL_OPEN", "1")
|
|
29
|
+
ctx, _ = register_with(make_protect_runner(decision="allow"))
|
|
30
|
+
result = ctx.hooks["pre_tool_call"]("terminal", {})
|
|
31
|
+
assert isinstance(result, dict) and result["action"] == "block"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_post_phase_does_not_validate():
|
|
35
|
+
ctx, _ = register_with(make_protect_runner(decision="block"))
|
|
36
|
+
result = ctx.hooks["post_tool_call"]("terminal", {})
|
|
37
|
+
assert result is None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_wellformed_payload_passes_validation():
|
|
41
|
+
ctx, _ = register_with(make_protect_runner(decision="allow"))
|
|
42
|
+
result = ctx.hooks["pre_tool_call"]("terminal", {"command": "ls"})
|
|
43
|
+
assert result is None
|
|
@@ -51,6 +51,28 @@ Scan request body for sensitive data. Priority determines risk level:
|
|
|
51
51
|
6. POST/PUT to untrusted domain -> escalate medium to high
|
|
52
52
|
7. Domain in allowlist -> ALLOW (low)
|
|
53
53
|
|
|
54
|
+
### Social Account Actions
|
|
55
|
+
|
|
56
|
+
Mutating requests to X/Twitter or TweetClaw social account endpoints receive the
|
|
57
|
+
`SOCIAL_ACCOUNT_ACTION` risk tag and escalate to high risk. Balanced mode prompts
|
|
58
|
+
the operator before execution because these requests can post tweets, post tweet
|
|
59
|
+
replies, send direct messages, upload media, create monitors, register webhooks,
|
|
60
|
+
or run giveaway draws.
|
|
61
|
+
|
|
62
|
+
| Example | Risk |
|
|
63
|
+
|---------|------|
|
|
64
|
+
| `POST https://api.twitter.com/2/tweets` | high |
|
|
65
|
+
| `POST https://xquik.com/api/v1/x/tweets` | high |
|
|
66
|
+
| `POST https://xquik.com/api/v1/x/dm/12345` | high |
|
|
67
|
+
| `POST https://xquik.com/api/v1/x/media` | high |
|
|
68
|
+
| `POST https://xquik.com/api/v1/monitors` | high |
|
|
69
|
+
| `POST https://xquik.com/api/v1/webhooks` | high |
|
|
70
|
+
| `POST https://xquik.com/api/v1/draws` | high |
|
|
71
|
+
|
|
72
|
+
Read-only TweetClaw requests such as tweet search, user lookup, or follower
|
|
73
|
+
export remain low risk unless they hit another rule such as secret scanning,
|
|
74
|
+
high-risk TLD handling, or webhook exfiltration.
|
|
75
|
+
|
|
54
76
|
## Command Execution Detector
|
|
55
77
|
|
|
56
78
|
### Dangerous Commands (always DENY, critical)
|