diff --git a/app.py b/app.py index ac15115..bb1da33 100644 --- a/app.py +++ b/app.py @@ -164,6 +164,17 @@ def format_direct_answer(question: str, results: list[dict]) -> str: return "\n".join(lines).strip() +def sanitize_outbound_url(url: str) -> str: + """Allow only absolute http/https URLs for outbound links shown in UI.""" + if not url: + return "" + candidate = url.strip() + parsed = urlparse(candidate) + if parsed.scheme.lower() in {"http", "https"} and parsed.netloc: + return candidate + return "" + + # --- Default Profile --- DEFAULT_PROFILE = """You are a coding companion running locally on a machine called "jarvis". @@ -571,7 +582,7 @@ async def query_searxng(query: str, max_results: int = 5) -> list[dict]: return [ { "title": "Current Weather", - "url": f"https://wttr.in/{location}", + "url": sanitize_outbound_url(f"https://wttr.in/{location}"), "content": resp.text.strip(), } ] @@ -603,9 +614,11 @@ async def query_searxng(query: str, max_results: int = 5) -> list[dict]: results.append( { "title": box.get("infobox", "Info"), - "url": box.get("urls", [{}])[0].get("url", "") - if box.get("urls") - else "", + "url": sanitize_outbound_url( + box.get("urls", [{}])[0].get("url", "") + if box.get("urls") + else "" + ), "content": content, } ) @@ -613,7 +626,7 @@ async def query_searxng(query: str, max_results: int = 5) -> list[dict]: results.append( { "title": r.get("title", ""), - "url": r.get("url", ""), + "url": sanitize_outbound_url(r.get("url", "")), "content": r.get("content", ""), } ) diff --git a/docs/wiki/current-wip.md b/docs/wiki/current-wip.md index 018a9c1..bbbe6b7 100644 --- a/docs/wiki/current-wip.md +++ b/docs/wiki/current-wip.md @@ -13,9 +13,9 @@ Total identified items: 26 - P3: Nice-to-have polish. ## Top 10 (Urgency Order) -1. [P0] Add authentication/authorization for all write and admin endpoints. -2. [P0] Add CSRF/origin protection for browser-initiated state-changing requests. -3. [P0] Block unsafe URL schemes in rendered search-result links (e.g., javascript:). +1. [P0][DONE] Add authentication/authorization for all write and admin endpoints. +2. [P0][DONE] Add CSRF/origin protection for browser-initiated state-changing requests. +3. [P0][DONE] Block unsafe URL schemes in rendered search-result links (e.g., javascript:). 4. [P0] Add rate limiting and request body size limits for chat/search/profile APIs. 5. [P1] Restrict settings updates to an allowlist of valid keys. 6. [P1] Add pagination + hard caps on list endpoints (memories, conversations, message history). diff --git a/readme.md b/readme.md index ff49e15..eb8a15f 100644 --- a/readme.md +++ b/readme.md @@ -44,9 +44,9 @@ Total identified items: 26 Top 10 (brief): -1. P0: Add auth for write/admin endpoints -2. P0: Add CSRF/origin protection for state-changing requests -3. P0: Block unsafe URL schemes in rendered links +1. P0 [DONE]: Add auth for write/admin endpoints +2. P0 [DONE]: Add CSRF/origin protection for state-changing requests +3. P0 [DONE]: Block unsafe URL schemes in rendered links 4. P0: Add rate limiting and request size limits 5. P1: Restrict `/api/settings` updates to allowlisted keys 6. P1: Add pagination + hard caps for list APIs @@ -57,7 +57,7 @@ Top 10 (brief): Item 1 executive summary: keep guest mode for conversational chat, require 4-digit admin PIN for advanced/destructive actions, and enforce local/LAN-only backend policy by default. -Implementation status: complete (guest session by default + admin unlock + admin-only write enforcement + origin checks + audit logging + capability tests). +Implementation status: complete (guest session by default + admin unlock + admin-only write enforcement + origin checks + safe-link sanitization + audit logging + capability tests). ## TODO diff --git a/templates/index.html b/templates/index.html index 5629d52..57c1d76 100644 --- a/templates/index.html +++ b/templates/index.html @@ -970,7 +970,13 @@ async function sendSearch() { if (data.raw_results) { let rawHtml = '
🔍 View raw search results (' + data.raw_results.length + ')