T02 · Agent Memory Poisoning
Error
- Location
- ui/server.py:134
- Finding
- Unauthenticated Localhost Workflow Poisoning<![CDATA[ ## Vulnerability Details **File Location**: `ui/server.py:134-153`, `ui/server.py:161-188`, `ui/server.py:265-275`; downstream execution path in `scripts/workflow_engine.py:194-224` **Vulnerability Type**: Persistent workflow instruction poisoning through an unauthenticated localhost API **Risk Level**: High ### Vulnerable Code #### Unauthenticated state-changing endpoint ```python def do_POST(self) -> None: parsed = urlparse(self.path) path = unquote(parsed.path) if path == "/api/workflows": return self.handle_save_workflow() if path == "/api/match": return self.handle_match_workflows() if path == "/api/capture-draft": return self.handle_capture_draft() if path == "/api/render-brief": return self.handle_render_brief() self.respond_error(HTTPStatus.NOT_FOUND, "Unknown endpoint.") ``` #### Arbitrary workflow creation, overwrite, and rename-based deletion ```python def handle_save_workflow(self) -> None: body = self.read_json_body() if body is None: return original_id = body.pop("_original_id", None) try: workflow = validate_workflow(body) destination = workflow_path(self.config.workflows_dir, workflow["id"]) except ValueError as exc: return self.respond_error(HTTPStatus.BAD_REQUEST, str(exc)) if original_id and original_id != workflow["id"]: try: old_path = workflow_path(self.config.workflows_dir, original_id) except ValueError as exc: return self.respond_error(HTTPStatus.BAD_REQUEST, str(exc)) if old_path.exists(): old_path.unlink() destination.write_text(json.dumps(workflow, indent=2) + "\n", encoding="utf-8") self.respond_json({"ok": True, "id": workflow["id"]}) ``` #### No authentication, CSRF protection, Origin validation, or content-type enforcement ```python def read_json_body(self) -> dict | None: try: length = int(self.headers.get("Content-Le ...[truncated 4649 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Require authentication** - Generate a cryptographically random token when the server starts. - Require the token on every API request, especially all state-changing endpoints. - Avoid placing reusable authentication tokens in URLs where they may be logged. 2. **Enforce same-origin requests** - Reject state-changing requests unless the `Origin` header exactly matches the configured local UI origin. - Validate the `Host` header against the configured host and port. - Do not treat CORS headers alone as CSRF protection. 3. **Require the correct media type** - Accept workflow writes only when `Content-Type` is `application/json`. - Reject `text/plain`, form-encoded, multipart, and missing content types. - Apply strict request-body size limits before reading the body. 4. **Restrict network exposure** - Permit binding only to loopback addresses by default. - Require an explicit security override and authentication before allowing non-loopback `--host` values. - Clearly warn users if the server is exposed beyond localhost. 5. **Protect destructive operations** - Separate create, update, rename, and delete operations. - Require explicit confirmation and a visible diff before overwriting, renaming, or deleting a workflow. - Do not accept `_original_id` as an implicit deletion primitive in the general save endpoint. - Use atomic writes and retain recoverable backups or revision history. 6. **Treat workflow content as untrusted** - Clearly delimit stored workflow text in generated prompts. - State that stored steps are user-managed data, not system policy or authorization. - Reject or flag workflow instructions that attempt to override higher-priority instructions, obtain secrets, silently make external calls, or expand privileges. - Display the full execution brief or a meaningful diff before the user authorizes reuse. 7. **Add regression tests** - Verify that cross- ...[truncated 286 chars]
