* feat: full image support across all channels
End-to-end image handling: upload, generation, analysis, editing, and
rendering across web gateway, HTTP webhook, WASM (Telegram/Slack), and
REPL channels. Builds on the attachment infrastructure from #596 and
draws inspiration from PR #641's image pipeline approach — credit to
that PR's author for the sentinel JSON pattern and base64-in-JSON
upload design.
Key changes:
- Image upload in web UI (file picker, paste, preview strip)
- Image generation tool (FLUX/DALL-E via /v1/images/generations)
- Image edit tool (multipart /v1/images/edits with fallback)
- Image analysis tool (vision model for workspace images)
- Model detection utilities (image_models.rs, vision_models.rs)
- Sentinel JSON detection in dispatcher for generated image rendering
- StatusUpdate::ImageGenerated → SSE/WS/REPL/WASM broadcast
- HTTP webhook attachment support (base64, 5MB/file, 10MB total)
- WASM channel image download (Telegram via file API, Slack via host HTTP)
- Tool registration wiring in app.rs
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: address PR #725 review comments (16 issues)
- SecretString for API keys in all image tools (image_gen, image_edit, image_analyze)
- Binary image read via tokio::fs::read instead of DB-backed workspace.read()
- Replace Arc<Workspace> with Option<PathBuf> base_dir (workspace has no filesystem API)
- ApprovalRequirement::UnlessAutoApproved for cost-sensitive image tools
- Scope sentinel detection to image_generate/image_edit tool names only
- Skip ToolResult preview broadcast for image sentinels (avoids multi-MB base64 in SSE)
- Extract shared media_type_from_path() to builtin/mod.rs
- Rename fallback_chat_edit → fallback_generate with tracing::warn
- Increase gateway body limit from 1MB to 10MB for image uploads
- Increase webhook body limit to 15MB (base64 overhead)
- Log warning on invalid base64 in images_to_attachments
- Client-side image size limits (5MB/file, 5 images max) in app.js
- aria-label on attach button for accessibility
- Update body_too_large test for new 10MB limit
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: add Slack file size check before download (PR review item #15)
Skip downloading files larger than 20 MB in the Slack WASM channel to
avoid excessive memory use and slow downloads in the WASM runtime.
Logs a warning when a file is skipped. Also bumps channel versions
for Slack and Telegram (prior branch changes).
[skip-regression-check]
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: cargo fmt
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(security): add path validation and approval requirement to image tools
Add sandbox path validation via validate_path() to both ImageAnalyzeTool
and ImageEditTool to prevent path traversal attacks that could exfiltrate
arbitrary files through external vision/edit APIs. Also fix
ImageAnalyzeTool::requires_approval to return UnlessAutoApproved,
consistent with ImageEditTool and ImageGenerateTool.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: post-download size guards and empty data_url sentinel check
- Slack: add post-download size check on actual bytes when metadata
size_bytes is absent, preventing bypass of the 20MB limit
- Telegram: add 20MB download size limit (matching Slack) enforced
in download_telegram_file() after receiving response bytes
- Dispatcher: skip broadcasting ImageGenerated SSE event when
data_url is empty from unwrap_or_default(), log warning instead
Closes correctness issues #3, #4, #5 from PR #725 review.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix: use mime_guess for media type detection, add alt attrs and media_type validation
- Replace hardcoded media type mapping with mime_guess crate (already in deps)
- Add alt attributes to img elements in web UI for accessibility
- Validate media_type starts with "image/" in images_to_attachments()
- Update bmp test assertion to match mime_guess behavior
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Zaki <[email protected]>
* docs(security): add network security reference for all listeners
Catalogs every network-facing surface (web gateway, webhook server,
orchestrator API, OAuth callback, sandbox proxy) with auth mechanisms,
bind addresses, egress controls, known findings, and a review checklist
for PRs that touch network-facing code.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(security): address three network security findings
- Use constant-time comparison (ct_eq) for webhook secret validation,
matching the pattern in web gateway and orchestrator auth
- Add X-Content-Type-Options and X-Frame-Options security headers to
the web gateway via SetResponseHeaderLayer
- Warn at startup when HTTP webhook server binds to 0.0.0.0
- Update NETWORK_SECURITY.md to mark findings 1, 4, 5 as resolved
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* fix(security): address PR #201 review findings
- Reorder web gateway layers so security headers (X-Content-Type-Options,
X-Frame-Options) are outermost and apply to all responses including
DefaultBodyLimit 413 rejections
- Move 0.0.0.0 warning to final bind address resolution so it fires for
WASM-only webhook servers that fall back to the default address
- Add webhook handler auth tests: correct secret -> 200, wrong secret
-> 401, missing secret -> 401
- Rewrite NETWORK_SECURITY.md: replace brittle line-number references
with function/struct name anchors, add threat model section, document
graceful shutdown per listener, fill content gaps (health endpoint
responses, content-type validation, CSRF analysis, WS auth flow, MCP
trust boundary, orchestrator rate limiting), change findings F-4/F-5
from "Resolved" to "Mitigated" with caveats
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* style: fix rustfmt and clippy warnings from main merge
Fix formatting in llm/mod.rs and llm/rig_adapter.rs introduced by
PR #132, and collapse nested if in rig_adapter.rs per clippy.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Replace the dual-server architecture (HttpChannel + WasmChannelServer both
competing for port 8080) with a single WebhookServer that composes route
fragments from all sources. Channels define routes but never spawn servers.
- Add WebhookServer struct that collects Router fragments and binds one listener
- Extract routes() from HttpChannel, remove server-spawning from start/shutdown
- Delete WasmChannelServer (keep WasmChannelRouter and route builder)
- Rewire main.rs to compose all webhook routes into one server
Co-Authored-By: Claude Opus 4.6 <[email protected]>