Agents in this build are text-only researchers. Image, audio, video,
and binary content has no role in the pipeline and only widens the
attack surface (server-side image fetches, exfiltration via rendered
img tags, etc). The cleanest answer is to never load it in the first
place rather than maintain a domain allowlist that nobody can keep
up to date.
- web_fetch_tool now uses httpx.AsyncClient.stream and inspects the
Content-Type header BEFORE the body is read into memory. Only
text/*, application/json, application/xml, application/xhtml+xml,
application/ld+json, application/atom+xml, application/rss+xml are
accepted; everything else (image/*, audio/*, video/*, octet-stream,
pdf, font, missing header, ...) is refused with a wrap_untrusted
error reply. The body bytes never enter the process for refused
responses. Read budget is bounded to ~4x max_chars regardless.
- image_search_tool removed from deerflow.community.searx.tools
(both the deer-flow runtime tree and the factory overlay). The
function is gone, not stubbed — any tool.use referencing it will
raise AttributeError at tool-loading time.
- config.yaml: image_search tool entry removed; the example
allowed_tools list updated to drop image_search.
- HARDENING.md: new section 2.8 explains the policy and the frontend
caveat (the LLM can still emit  markdown which the user's
browser would render — that requires a separate frontend patch
that is not yet implemented). Section 3.4 adds a verification
snippet for the policy. The web_fetch entry in section 2.2 is
updated to mention the streaming Content-Type gate.
Both source trees stay in sync.
Quick orientation: layout, hard rules (native tools stay disabled,
sanitize+wrap, no secrets, two trees in sync, firewall is part of the
threat model, deer-flow is vendored), where things run on data-nuc,
commit style, a one-page verification block, and the common NixOS /
docker / pip footguns to avoid.
- RUN.md: start/stop/inspect/smoke-test commands for the hardened
DeerFlow stack on data-nuc, including the docker compose -f overlay
invocation and a copy-paste smoke test that verifies allow + block
destinations from inside the container.
- scripts/deerflow-firewall.sh: status now uses iptables -nvL so the
input-interface column is included, and the awk filter shows the
header plus all rules matching br-deerflow. The previous version
used -nL which omits the interface column entirely, so the grep
found nothing even when the rules were correctly installed.
Adds the host-level egress firewall recommended by the upstream
DeerFlow team's "run in a VLAN" guidance, adapted to a Fritzbox-only
home network where LAN VLANs are not available.
- docker/docker-compose.override.yaml: pins the upstream deer-flow
Docker network to a stable Linux bridge name br-deerflow so the
firewall can address it without guessing Docker's auto-generated
br-<hash>. Used as a -f overlay on top of the upstream compose file.
- scripts/deerflow-firewall.sh: idempotent up/down/status wrapper that
installs DOCKER-USER iptables rules. Allowlist for 10.67.67.1 (Searx)
and 10.67.67.2 (XTTS/Whisper/Ollama-local), hard block for
192.168.3.0/24 (home LAN), 10.0.0.0/8, 172.16.0.0/12. Stateful return
rule keeps inbound LAN access to published ports working.
- scripts/deerflow-firewall.nix: NixOS module snippet defining a
systemd unit ordered After=docker.service so the rules survive
dockerd restarts and follow its lifecycle. Copy into
configuration.nix and nixos-rebuild switch.
- HARDENING.md: new section 2.7 "Network isolation (egress firewall)"
with allow/block tables, bring-up steps, and smoke-test commands.
Guarantees: rules match on -i br-deerflow, so if the bridge does not
exist, the rules are no-ops and do not affect any other container
(paperclip, telebrowser, openclaw-gateway, ...). Stopping the
container leaves the rules in place but inert; stopping the systemd
unit removes them.
Vendored deer-flow upstream (bytedance/deer-flow) plus prompt-injection
hardening:
- New deerflow.security package: content_delimiter, html_cleaner,
sanitizer (8 layers — invisible chars, control chars, symbols, NFC,
PUA, tag chars, horizontal whitespace collapse with newline/tab
preservation, length cap)
- New deerflow.community.searx package: web_search, web_fetch,
image_search backed by a private SearX instance, every external
string sanitized and wrapped in <<<EXTERNAL_UNTRUSTED_CONTENT>>>
delimiters
- All native community web providers (ddg_search, tavily, exa,
firecrawl, jina_ai, infoquest, image_search) replaced with hard-fail
stubs that raise NativeWebToolDisabledError at import time, so a
misconfigured tool.use path fails loud rather than silently falling
back to unsanitized output
- Native client back-doors (jina_client.py, infoquest_client.py)
stubbed too
- Native-tool tests quarantined under tests/_disabled_native/
(collect_ignore_glob via local conftest.py)
- Sanitizer Layer 7 fix: only collapse horizontal whitespace, preserve
newlines and tabs so list/table structure survives
- Hardened runtime config.yaml references only the searx-backed tools
- Factory overlay (backend/) kept in sync with deer-flow tree as a
reference / source
See HARDENING.md for the full audit trail and verification steps.