feat(ai): phase 0 couche d'outils partagee (backend/tools) + tests
CI / lint (push) Successful in 57s
CI / security (push) Successful in 39s
CI / test (push) Failing after 41s
CI / build (push) Skipped
CI / e2e (push) Skipped
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 57s
CI / security (push) Successful in 39s
CI / test (push) Failing after 41s
CI / build (push) Skipped
CI / e2e (push) Skipped
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
- backend/tools/: ToolContext, registry @tool + schemas JSON, audit ai_tool_call - Services lecture/recherche: list_vaults, list_directory, read_file, search_fulltext, list_tags - Permissions check_vault_access + resolve_safe_path, confirmation gating (two-step) - Redaction des secrets, limites de taille, audit JSONL (args sensibles resumes) - tests/test_tools.py: 30 tests (registry, contexte, execution, confirmation, audit) - ROADMAP: item #79 phase A livree (A2 partiel)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
"""Audit logging for AI tool calls.
|
||||
|
||||
Reuses the application audit log (``data/audit.log``, JSON lines) and adds an
|
||||
``ai_tool_call`` action. Argument values that may contain sensitive payloads
|
||||
(file content, prompts) are summarized rather than stored verbatim.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from backend.audit import _write_entry
|
||||
|
||||
# Argument keys whose values may contain secrets or large payloads.
|
||||
_SENSITIVE_ARG_KEYS = {"content", "text", "body"}
|
||||
_MAX_ARG_CHARS = 200
|
||||
|
||||
|
||||
def _sanitize_arguments(arguments: dict[str, Any] | None) -> dict[str, Any]:
|
||||
"""Return a log-safe view of tool arguments."""
|
||||
safe: dict[str, Any] = {}
|
||||
for key, value in (arguments or {}).items():
|
||||
if key in _SENSITIVE_ARG_KEYS:
|
||||
safe[key] = f"<{len(str(value))} chars>"
|
||||
else:
|
||||
safe[key] = str(value)[:_MAX_ARG_CHARS]
|
||||
return safe
|
||||
|
||||
|
||||
def log_tool_call(
|
||||
*,
|
||||
username: str,
|
||||
mode: str,
|
||||
tool: str,
|
||||
arguments: dict[str, Any] | None = None,
|
||||
ok: bool = True,
|
||||
vault: str | None = None,
|
||||
ip: str | None = None,
|
||||
error: str | None = None,
|
||||
) -> None:
|
||||
"""Append an ``ai_tool_call`` entry to the audit log."""
|
||||
_write_entry({
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"action": "ai_tool_call",
|
||||
"username": username,
|
||||
"ip": ip or "unknown",
|
||||
"mode": mode,
|
||||
"tool": tool,
|
||||
"vault": vault,
|
||||
"ok": ok,
|
||||
"error": error,
|
||||
"arguments": _sanitize_arguments(arguments),
|
||||
})
|
||||
Reference in New Issue
Block a user