mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
* feat(workflows): add versioned approvals and run configuration evidence * docs: trim repeated setup guidance to respect inherited size limit * docs: leave room in inherited agent guidance * test(channels): align Buzz capability expectation with upstream registry * fix(workflows): fence file consumers and preserve review access * fix(runtime): reuse assembly evidence key at run admission
42 lines
1.9 KiB
Python
42 lines
1.9 KiB
Python
"""Bounded output capture without symlink traversal or mutable-path rereads."""
|
|
|
|
import os
|
|
import stat
|
|
|
|
from deerflow_extension_api import AgentRunError
|
|
|
|
from app.gateway.path_utils import normalize_outputs_virtual_path
|
|
from deerflow.config.paths import get_paths
|
|
|
|
MAX_BYTES = 2 * 1024 * 1024
|
|
|
|
|
|
def read_output_snapshot(user_id, thread_id, path):
|
|
if os.name != "posix" or not isinstance(path, str):
|
|
raise AgentRunError(422, "Output capture requires POSIX and an output path")
|
|
path = normalize_outputs_virtual_path(path)
|
|
paths = get_paths()
|
|
root = paths.base_dir.resolve()
|
|
relative = paths.sandbox_outputs_dir(thread_id, user_id=user_id).relative_to(paths.base_dir)
|
|
parts = (*relative.parts, *path.removeprefix("/mnt/user-data/outputs/").split("/"))
|
|
directory = os.open(root, os.O_RDONLY | os.O_DIRECTORY)
|
|
try:
|
|
for part in parts[:-1]:
|
|
next_dir = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=directory)
|
|
os.close(directory)
|
|
directory = next_dir
|
|
fd = os.open(parts[-1], os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory)
|
|
with os.fdopen(fd, "rb") as source:
|
|
before = os.fstat(source.fileno())
|
|
if not stat.S_ISREG(before.st_mode) or before.st_size > MAX_BYTES:
|
|
raise AgentRunError(422, "Artifact must be a regular file of at most 2 MiB")
|
|
content = source.read(MAX_BYTES + 1)
|
|
after = os.fstat(source.fileno())
|
|
if len(content) > MAX_BYTES or (before.st_size, before.st_mtime_ns, before.st_ctime_ns) != (after.st_size, after.st_mtime_ns, after.st_ctime_ns):
|
|
raise AgentRunError(409, "Artifact changed during capture")
|
|
return content
|
|
except OSError as exc:
|
|
raise AgentRunError(422, "Artifact unavailable or unsafe") from exc
|
|
finally:
|
|
os.close(directory)
|