Skip to main content

Module attachments

Module attachments 

Source
Expand description

T-32 attachment policy layer.

The agent receives a message body containing 📎 attachment: <path> and asks the broker to read the file. This module owns the decision: accept (return bytes) or reject (return reason). It is transport-agnostic — the MCP read_attachment tool is one caller; a future REST surface or CLI debug helper would call the same check_and_read entry point.

Three independent guards layered before any read:

  1. Path-traversal: canonicalize the operator-supplied path and confirm it is a descendant of one of allowed_roots.
  2. Size: stat the file and reject if it exceeds max_size_bytes before any bytes are read.
  3. Scanner (optional): hand the canonical path to an external command with a timeout; non-zero exit or timeout → reject.

Bytes are returned as-is on accept. No envelope wrapping, no “treat as data” framing — those are prompt-injection mitigations and live in the hook layer per owner ratify.

enabled = false short-circuits with RejectReason::Disabled — no filesystem cost when the operator has flipped the flag.

Structs§

AcceptedAttachment
T-32b: outcome of a successful read — bytes plus metadata the caller surfaces to the agent (and writes to the audit log).
AuditEntry
T-32b audit log entry. Written as a single JSON line per attempt so the file is tail -f-friendly + parseable with jq.
RealScanner
Production scanner: spawns the operator-configured command with the resolved path as a single argument, waits up to timeout, captures stderr for the reject detail. The wait uses std::thread::spawn + mpsc::recv_timeout so team-core stays sync (no tokio dep — owner-ratify variant 4).

Enums§

RejectReason
Reasons the broker can refuse to read an attachment. The agent surfaces the variant + a short string back to the operator via the originating-channel notification path; the operator never sees raw filesystem errors verbatim.
ScanOutcome

Traits§

Scanner
External-scanner abstraction. Implementations spawn the operator’s configured command, wait up to timeout, and return the outcome. Trait-object shape keeps the read path testable without spawning real processes — the test seam is the Mock impl in #[cfg(test)].

Functions§

append_audit
Append a single JSON-line audit entry. No-op when audit_log_path is None. Errors creating the parent dir or opening the file are surfaced to the caller — production paths log-and-continue so a misconfigured audit dir doesn’t block real reads.
check_and_read
Attempt to read the file the operator pointed at, applying every configured guard. The scanner is plumbed through as a trait object so callers (production: real Command; tests: mock) share the same control flow.
check_and_read_with_metadata
Wrapper that runs check_and_read and packages the result with the metadata team-mcp’s read_attachment tool returns to the agent. Centralises hashing so the staging-tempfile name (content- addressed) and the audit log entry stay in sync.
is_within_any_root
Pure check: is resolved a descendant of (or equal to) any of roots? Both sides are expected canonical, so byte-equality starts_with is enough — no .. slipping through.
now_rfc3339
Helper: format an RFC3339 UTC timestamp suitable for audit entries. Pulled out so tests can pin the format independently of the call site.
resolve_allowed_roots
Resolve $HOME and other allow-list roots to canonical paths. Performed at check-time so a snapshot taken on machine A still resolves correctly when restored on machine B (different $HOME). Roots that fail to canonicalize are dropped — an operator with a stale path entry doesn’t break the whole policy.
stage_to_tempfile
Write accepted.bytes to the staging dir under a content- addressed name. Idempotent: if the file already exists with the expected size, we skip the write (operator may have read the same attachment recently). Returns the staged path so the agent can read_file() it directly.
staging_dir
T-32b staging directory under the compose root. Tempfiles live here, named by the content blake3 hash so identical content dedups to a single file across sessions and across agents.
sweep_expired
T-32b: drop tempfiles whose mtime is older than now - ttl. Called on team-mcp startup as a best-effort cleanup. Returns the number of files reaped so callers can log a single summary line. Errors traversing individual entries are logged-and-skipped at the call site (kept out of this function so unit tests stay trace-free).