fix(flowext): loopback-only queue server, real self-heal, per-machine browser
Three bugs, all found standing up a second rig on m3ultra:
1. SECURITY: HTTPServer(("", 8017)) bound 0.0.0.0 with no auth, and POST /save
base64-writes bytes to an absolute `dest` taken straight from the request body.
Anyone on the same wifi could write arbitrary files as this user — drop a
~/Library/LaunchAgents plist and you have code execution. It was live on ultra
(verified: `TCP *:8017 (LISTEN)`, 204 from 192.168.1.249). Now binds 127.0.0.1;
FLOW_BIND overrides for a tailnet IP (utun-routed, LAN still can't reach it).
2. The launchd "self-heal" never healed anything. launch.sh backgrounds the queue
server and exits; without AbandonProcessGroup launchd SIGKILLs the process group
on exit, so every 10-min tick started a server that died a second later. The
server that was actually up had been started by a manual ./launch.sh.
Verified after the fix: kill -> 000 -> kickstart -> 204 on a fresh pid.
3. launch.sh hard-coded "Brave Browser" in the osascript. The extension is loaded
into Chrome on m3ultra. FLOW_BROWSER now names it (default Brave, unchanged
for ultra); m3ultra's plist sets "Google Chrome".
Also chmod +x install.sh launch.sh (both had lost the bit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9974b93379
commit
18790a7e4e
@ -21,6 +21,13 @@
|
|||||||
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
||||||
|
<!-- launch.sh backgrounds the queue server, then exits. Without this key launchd
|
||||||
|
SIGKILLs the whole process group on exit, taking the server with it — the
|
||||||
|
"self-heal" then restarts a server that dies a second later, forever. The
|
||||||
|
server you had running was a MANUAL ./launch.sh; the agent never kept one up. -->
|
||||||
|
<key>AbandonProcessGroup</key>
|
||||||
|
<true/>
|
||||||
|
|
||||||
<!-- self-healing: idempotent launch.sh every 10 min (nc check + tab reuse
|
<!-- self-healing: idempotent launch.sh every 10 min (nc check + tab reuse
|
||||||
make repeat runs no-ops), so a dead server or closed tab comes back -->
|
make repeat runs no-ops), so a dead server or closed tab comes back -->
|
||||||
<key>RunAtLoad</key>
|
<key>RunAtLoad</key>
|
||||||
|
|||||||
0
install.sh
Normal file → Executable file
0
install.sh
Normal file → Executable file
@ -32,10 +32,13 @@ fi
|
|||||||
|
|
||||||
# 2. open or focus ONE Flow tab (reuses an existing one so it can't double-run).
|
# 2. open or focus ONE Flow tab (reuses an existing one so it can't double-run).
|
||||||
# Only `activate` (steal system focus) when opening fresh — on the 10-min launchd
|
# Only `activate` (steal system focus) when opening fresh — on the 10-min launchd
|
||||||
# tick an existing tab is re-selected inside Brave without yanking you out of
|
# tick an existing tab is re-selected inside the browser without yanking you out of
|
||||||
# whatever you're doing. Selecting the tab also keeps Memory Saver from discarding it.
|
# whatever you're doing. Selecting the tab also keeps Memory Saver from discarding it.
|
||||||
|
#
|
||||||
|
# Must name the browser the EXTENSION is loaded into. ultra = Brave, m3ultra = Chrome.
|
||||||
|
BROWSER="${FLOW_BROWSER:-Brave Browser}"
|
||||||
osascript <<OSA
|
osascript <<OSA
|
||||||
tell application "Brave Browser"
|
tell application "$BROWSER"
|
||||||
set found to false
|
set found to false
|
||||||
repeat with w in windows
|
repeat with w in windows
|
||||||
set i to 0
|
set i to 0
|
||||||
@ -54,4 +57,4 @@ tell application "Brave Browser"
|
|||||||
end if
|
end if
|
||||||
end tell
|
end tell
|
||||||
OSA
|
OSA
|
||||||
echo "Brave focused on Flow. With Auto-start ticked, the rinse begins on load."
|
echo "$BROWSER focused on Flow. With Auto-start ticked, the rinse begins on load."
|
||||||
|
|||||||
@ -173,10 +173,17 @@ def selftest():
|
|||||||
print("selftest ok")
|
print("selftest ok")
|
||||||
|
|
||||||
def run(port=8017):
|
def run(port=8017):
|
||||||
print(f"--- Flow queue on http://localhost:{port} ---")
|
# ponytail: loopback ONLY. /save base64-writes to an attacker-supplied absolute `dest`
|
||||||
|
# with no auth — bound to 0.0.0.0 that is arbitrary file write as this user (drop a
|
||||||
|
# ~/Library/LaunchAgents plist → code execution) for anyone on the same wifi.
|
||||||
|
# The extension only ever calls localhost. FLOW_BIND=<tailscale-ip> if a remote box
|
||||||
|
# must enqueue — a tailnet IP is routed over utun, so the LAN still can't reach it.
|
||||||
|
# Never set it back to "" / 0.0.0.0.
|
||||||
|
host = os.environ.get("FLOW_BIND", "127.0.0.1")
|
||||||
|
print(f"--- Flow queue on http://{host}:{port} ---")
|
||||||
print(f"queue: {QUEUE_FILE if os.path.exists(QUEUE_FILE) else '(demo — no queue.jsonl)'}")
|
print(f"queue: {QUEUE_FILE if os.path.exists(QUEUE_FILE) else '(demo — no queue.jsonl)'}")
|
||||||
print(f"pending: {sum(1 for t in load_queue() if t['id'] not in done_ids())}")
|
print(f"pending: {sum(1 for t in load_queue() if t['id'] not in done_ids())}")
|
||||||
HTTPServer(("", port), Handler).serve_forever()
|
HTTPServer((host, port), Handler).serve_forever()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user