Files
DATA 6de0bf9f5b Initial commit: hardened DeerFlow factory
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.
2026-04-12 14:23:57 +02:00

59 lines
1.6 KiB
Python

#!/usr/bin/env python3
"""Cross-platform config bootstrap script for DeerFlow."""
from __future__ import annotations
import shutil
import sys
from pathlib import Path
def copy_if_missing(src: Path, dst: Path) -> None:
if dst.exists():
return
if not src.exists():
raise FileNotFoundError(f"Missing template file: {src}")
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(src, dst)
def main() -> int:
project_root = Path(__file__).resolve().parent.parent
existing_config = [
project_root / "config.yaml",
project_root / "config.yml",
project_root / "configure.yml",
]
if any(path.exists() for path in existing_config):
print(
"Error: configuration file already exists "
"(config.yaml/config.yml/configure.yml). Aborting."
)
return 1
try:
copy_if_missing(project_root / "config.example.yaml", project_root / "config.yaml")
copy_if_missing(project_root / ".env.example", project_root / ".env")
copy_if_missing(
project_root / "frontend" / ".env.example",
project_root / "frontend" / ".env",
)
except (FileNotFoundError, OSError) as exc:
print("Error while generating configuration files:")
print(f" {exc}")
if isinstance(exc, PermissionError):
print(
"Hint: Check file permissions and ensure the files are not "
"read-only or locked by another process."
)
return 1
print("✓ Configuration files generated")
return 0
if __name__ == "__main__":
sys.exit(main())