Add command policy audit trail

This commit is contained in:
2026-05-20 23:03:59 +08:00
parent 45b70d2800
commit 61cdd8bbaf
7 changed files with 364 additions and 12 deletions
+54
View File
@@ -323,3 +323,57 @@ def test_password_stream_runs_sudo_with_password_and_streams_answer(tmp_path, mo
assert "event: content_delta" in body
assert "sudo command completed" in body
assert "secret" not in body
def test_command_audit_endpoint_exposes_redacted_shell_events(tmp_path, monkeypatch):
monkeypatch.setenv("DUCK_DB_PATH", str(tmp_path / "duck.sqlite3"))
async def fake_chat(self, role, messages, temperature=None, max_output_tokens=None, response_format=None):
assert role == "action"
actions = []
if not any("tool_observations" in message["content"] for message in messages):
actions = [
{
"tool": "shell_exec_safe",
"args": {"command": "apt list --upgradable"},
"reason": "Check available updates",
}
]
return ModelResponse(
role=role,
model="local-main",
content=json.dumps(
{
"kind": "action_directive",
"intent": "check updates",
"risk_level": "low",
"actions": actions,
}
),
reasoning_content=None,
raw={},
latency_ms=1.0,
)
async def fake_stream_chat(self, role, messages):
yield {"type": "content_delta", "delta": "updates checked"}
monkeypatch.setattr("duck_core.model_client.ModelClient.chat", fake_chat)
monkeypatch.setattr("duck_core.model_client.ModelClient.stream_chat", fake_stream_chat)
client = TestClient(create_app())
with client.stream(
"POST",
"/v1/chat/stream",
json={"message": "check updates", "workspace": str(tmp_path), "debug": True},
) as response:
_ = "".join(response.iter_text())
audit = client.get("/v1/audit/commands").json()
assert response.status_code == 200
assert audit[0]["event_type"] == "command_audit"
assert audit[0]["payload"]["command"] == "apt list --upgradable"
assert audit[0]["payload"]["risk_level"] == "low"
assert audit[0]["payload"]["action_type"] == "package_check"
assert audit[0]["payload"]["approved"] is False
assert "password" not in json.dumps(audit).lower()
+20
View File
@@ -3,6 +3,7 @@ import pytest
from duck_core.tools.file_read import FileReadTool
from duck_core.tools.file_write import FileWriteTool
from duck_core.tools.gateway import ToolGateway
from duck_core.tools.command_policy import CommandPolicy
from duck_core.tools.shell_exec_safe import ShellExecSafeTool
@@ -139,3 +140,22 @@ async def test_shell_tool_allows_read_only_apt_update_check(monkeypatch, tmp_pat
assert result.ok is True
assert "bootlogd" in result.output
assert result.metadata["command"] == "apt list --upgradable"
def test_command_policy_classifies_common_system_commands():
readonly = CommandPolicy.classify("apt list --upgradable")
sudo_update = CommandPolicy.classify("sudo apt update")
install = CommandPolicy.classify("sudo apt install vim")
destructive = CommandPolicy.classify("rm -rf .")
assert readonly.risk_level == "low"
assert readonly.action_type == "package_check"
assert readonly.requires_approval is False
assert sudo_update.risk_level == "high"
assert sudo_update.action_type == "package_cache_update"
assert sudo_update.requires_password is True
assert install.risk_level == "high"
assert install.action_type == "package_install"
assert install.requires_password is True
assert destructive.risk_level == "critical"
assert destructive.blocked is True