Harden network and privacy defaults; fix several bugs
Security:
- Bind HTTP gateway to 127.0.0.1 by default; add --bind for LAN opt-in
- Restrict Reticulum mesh surface to GET /api/sites only (CSRF cannot
authenticate mesh callers, so gate by whitelist)
- Cap request body size at 16 MiB to prevent memory DoS
- Redact /bookmark query strings from request logs so the bookmark token
and URLs do not land in stdout / docker / journal logs
- Tighten FTS5 sanitizer: strip colon, drop AND/OR/NOT/NEAR operator words
- Expand .dockerignore; document trust model in README
Features:
- Add sharing mode toggle (share everything except private vs share only
public-tagged) with /share/preview so users can see what subscribers
would receive before enabling sharing
Bugs:
- handle_export() crashed on every call (missing query kwarg)
- Dead float16 decompression branch in embeddings.py silently corrupted
the HNSW index when compress_embeddings was on
- GATEWAY_PORT staleness: --port and find_available_port had no effect
on the actual bind
- semantic_search default mismatched between db.py ("1") and the rest of
the app ("0"), causing embeddings to be generated when the UI said off
- Connection pool returned connections with uncommitted transactions to
the next consumer
- Gateway POST body decode 502'd on non-UTF-8 input
- ensure_rns_config clobbered user-edited ~/.reticulum/config; now only
rewrites files it authored (sentinel-tagged)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ce50150363
commit
1bc695f508
8 changed files with 266 additions and 56 deletions
25
gateway.py
25
gateway.py
|
|
@ -1,3 +1,4 @@
|
|||
import re
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
|
|
@ -9,6 +10,7 @@ APP_NAME = "tinyweb"
|
|||
ASPECTS = ["server"]
|
||||
GATEWAY_PORT = 8080
|
||||
REQUEST_TIMEOUT = 60
|
||||
MAX_BODY_SIZE = 16 * 1024 * 1024 # 16 MiB — covers /import and every other form
|
||||
|
||||
|
||||
class GatewayState:
|
||||
|
|
@ -71,8 +73,18 @@ class GatewayHandler(BaseHTTPRequestHandler):
|
|||
|
||||
body = {}
|
||||
if method == "POST":
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
raw = self.rfile.read(length).decode()
|
||||
try:
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
except ValueError:
|
||||
self.send_error(400, "Invalid Content-Length")
|
||||
return
|
||||
if length < 0:
|
||||
self.send_error(400, "Invalid Content-Length")
|
||||
return
|
||||
if length > MAX_BODY_SIZE:
|
||||
self.send_error(413, "Request body too large")
|
||||
return
|
||||
raw = self.rfile.read(length).decode("utf-8", errors="replace")
|
||||
body = parse_qs(raw)
|
||||
|
||||
# Parse cookies
|
||||
|
|
@ -152,7 +164,14 @@ class GatewayHandler(BaseHTTPRequestHandler):
|
|||
self._forward("POST")
|
||||
|
||||
def log_message(self, format, *args):
|
||||
print(f"[Gateway] {args[0]}")
|
||||
try:
|
||||
msg = format % args
|
||||
except TypeError:
|
||||
msg = format
|
||||
# /bookmark carries a long-lived token and the URL being indexed —
|
||||
# redact the query so it doesn't end up in stdout, journald, docker logs, etc.
|
||||
msg = re.sub(r'(/bookmark)\?\S*', r'\1?[redacted]', msg)
|
||||
print(f"[Gateway] {msg}")
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue