Files
f9f6bc01fd
CI / test (push) Canceled after 0s
Feature/delta team/epic r04 (#7)
## Summary

epic r04 - begin refactor

## Change Type

- [x] Cowork feature
- [ ] Bug fix
- [ ] Core AI contribution
- [ ] Test / hardening
- [ ] Performance
- [ ] Documentation

## Related Work

Cowork Task:

Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets

Core AI Issue:

Core Task:

Related PR:

## Scope

What is intentionally included?

What is intentionally NOT included?

## Validation

- [ ] Unit tests
- [ ] Integration tests
- [ ] Manual verification
- [ ] Regression check

Commands / evidence:

## Security Impact

Permission / credential / network / customer data impact:

## Compatibility

- [ ] No breaking change
- [ ] Breaking change documented

## Reviewer Notes

Anything Cowork reviewers should pay attention to.

---------

Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com>
Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com>
Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com>
Co-authored-by: Vu Dam Tuan <vudt15@fpt.com>
Co-authored-by: Hiep Ha Van <hiephv3@fpt.com>
Co-authored-by: Lam Hoang Van <lamhv7@fpt.com>
Reviewed-on: #7
Co-authored-by: Duy Le Huu <duylh19@fpt.com>
2026-08-31 05:15:13 +00:00

113 lines
3.8 KiB
Python

"""Audit logger — records every sandbox execution attempt.
Logs: allow, deny, execution events with timestamp, user, project, workspace,
prompt category, risk score, action type, backend selected, command hash,
working directory scope, network blocked, result status, return code, denial reason.
Does NOT log secrets or raw sensitive content.
"""
from __future__ import annotations
import hashlib
import json
import logging
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional
logger = logging.getLogger("cowork_local.security.audit")
@dataclass
class AuditEntry:
"""Một dòng nhật ký kiểm toán, đủ trường để dựng lại bối cảnh sau này.
Cố ý KHÔNG lưu nguyên văn lệnh: chỉ giữ ``command_hash`` (SHA-256, 16
ký tự đầu) để đối chiếu hai lần chạy có giống nhau không, mà không đưa
nội dung nhạy cảm vào file log.
"""
timestamp: str = ""
user: str = ""
project: str = ""
workspace: str = ""
prompt_category: str = ""
risk_score: int = 0
action_type: str = ""
backend_selected: str = ""
command_hash: str = ""
working_directory_scope: str = ""
network_blocked: bool = False
result_status: str = "" # allowed, denied, executed, error
return_code: int = 0
denial_reason: str = ""
approval_status: str = "" # auto, approved, rejected
def __post_init__(self):
"""Điền mốc thời gian và mã băm lệnh nếu bên gọi chưa đặt.
Mã băm thay cho nội dung lệnh gốc: nhật ký cần truy được về sau nhưng không
nên chứa nguyên văn thứ đã chạy.
"""
if not self.timestamp:
self.timestamp = datetime.now(timezone.utc).isoformat()
if not self.command_hash and self.action_type:
self.command_hash = hashlib.sha256(
self.action_type.encode()
).hexdigest()[:16]
def _audit_dir() -> Path:
"""Thư mục chứa nhật ký kiểm toán. Import muộn để tránh vòng import với ``config``."""
from ..config import CONFIG_DIR
return CONFIG_DIR / "audit"
def record(
action_type: str,
result_status: str,
*,
user: str = "",
project: str = "",
workspace: str = "",
prompt_category: str = "",
risk_score: int = 0,
backend_selected: str = "",
command: str = "",
working_directory: str = "",
network_blocked: bool = False,
return_code: int = 0,
denial_reason: str = "",
approval_status: str = "",
) -> AuditEntry:
"""Create and persist an audit log entry."""
cmd_hash = hashlib.sha256(command.encode()).hexdigest()[:16] if command else ""
entry = AuditEntry(
user=user,
project=project,
workspace=workspace,
prompt_category=prompt_category,
risk_score=risk_score,
action_type=action_type,
backend_selected=backend_selected,
command_hash=cmd_hash,
working_directory_scope=working_directory,
network_blocked=network_blocked,
result_status=result_status,
return_code=return_code,
denial_reason=denial_reason,
approval_status=approval_status,
)
# Write to JSONL file
audit_dir = _audit_dir()
audit_dir.mkdir(parents=True, exist_ok=True)
log_file = audit_dir / "sandbox_audit.jsonl"
with open(log_file, "a", encoding="utf-8") as f:
f.write(json.dumps(asdict(entry)) + "\n")
logger.info(
"AUDIT: action=%s status=%s user=%s risk=%d backend=%s",
action_type, result_status, user, risk_score, backend_selected,
)
return entry