Skip to content

fix(browser): make the proxy work on real deploys (dual-port launch + cross-origin CSP)#518

Merged
jaylfc merged 4 commits into
masterfrom
fix/browser-proxy-frame-ancestors
Jun 2, 2026
Merged

fix(browser): make the proxy work on real deploys (dual-port launch + cross-origin CSP)#518
jaylfc merged 4 commits into
masterfrom
fix/browser-proxy-frame-ancestors

Conversation

@jaylfc
Copy link
Copy Markdown
Owner

@jaylfc jaylfc commented Jun 2, 2026

Problem

The Browser app (#511) does not work on a real systemd deploy — pages fail to load. Root-caused live on a Pi. Two independent bugs:

1. The second proxy origin never starts

The systemd unit ran uvicorn tinyagentos.app:create_app directly. The dual-port browser-proxy origin only launches via __main__.main()_serve_dual_port, so nothing listened on the proxy port (proxy-config returned {"port":0}) and the iframe's redeem→proxy chain 404'd.

Fix: ExecStartpython -m tinyagentos, and inject TAOS_HOST/TAOS_PORT into the unit Environment (in install-server.sh, both system + user paths) so the module entrypoint binds the same host/port and runs both origins. TAOS_BROWSER_PROXY_PORT was already injected.

2. Cross-origin framing was CSP-blocked

Proxied responses set frame-ancestors 'self'. The shell (main port) and proxy (proxy port) are different origins, so this blocked the shell from embedding the proxy iframe entirely — even once both origins were up.

Fix: compute frame-ancestors per-request as 'self' <shell-origin> (same host, main port), so only the taOS shell can frame the proxy (clickjacking defence preserved). Needs main_port on app.state and main_port/browser_proxy_port added to the proxy origin's _SharedState allowlist.

Verification

Verified live on a Pi after deploying these files: the Browser app renders real pages through the proxy and links are rewritten to flow back through it (was "Not Found" / CSP-blocked before).

Tests

  • New CSP unit tests: frame-ancestors is 'self' alone in single-port fallback, and 'self' <shell-origin> when a shell origin is supplied.
  • Existing CSP tests unchanged and passing (12/12 local).

Summary by CodeRabbit

  • New Features

    • Enhanced Content-Security-Policy configuration for the browser proxy, enabling dynamic frame-ancestors directive support based on shell origin.
    • Improved server startup process with better environment variable configuration.
  • Tests

    • Added test coverage for Content-Security-Policy frame-ancestors directive validation.

… cross-origin CSP)

The Browser app (#511) never worked on a systemd deploy. Two bugs:

1. The unit's ExecStart ran 'uvicorn tinyagentos.app:create_app' directly,
   which never starts the second browser-proxy origin (that only launches
   via __main__.main() -> _serve_dual_port). So nothing listened on the
   proxy port and pages failed to load. Switch ExecStart to
   'python -m tinyagentos' and inject TAOS_HOST/TAOS_PORT into the unit
   Environment (install-server.sh) so the module entrypoint binds the same
   host/port and runs both origins.

2. Proxied responses set 'frame-ancestors self', which blocks the taOS
   shell (main origin) from embedding the proxy origin (different port =
   different origin) — so even with both origins up, the iframe was CSP-
   blocked. Compute frame-ancestors per-request as "'self' <shell-origin>"
   (same host, main port), so only the shell can frame it (clickjacking
   defence preserved). Requires main_port on app.state and main_port/
   browser_proxy_port added to the proxy origin's shared-state allowlist.

Verified live: example.org renders through the proxy and links are
rewritten to flow back through it. Adds CSP unit tests for both the
single-port ('self' only) and dual-origin (shell origin allowed) cases.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Warning

Review limit reached

@jaylfc, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 11 minutes and 18 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d454133d-7dc8-4cb8-a574-2ab678e3df56

📥 Commits

Reviewing files that changed from the base of the PR and between 86ce4b7 and 0f4ced5.

📒 Files selected for processing (4)
  • scripts/systemd/tinyagentos.service
  • scripts/taos-graceful-stop.sh
  • tests/routes/desktop_browser/test_proxy_shell.py
  • tinyagentos/routes/desktop_browser/proxy.py
📝 Walkthrough

Walkthrough

The pull request refactors TinyAgentOS startup to use a module entrypoint and enables the desktop browser proxy to compute Content-Security-Policy frame-ancestors directives dynamically based on the incoming request's shell origin, rather than using a static 'self' fallback.

Changes

Dynamic Frame-Ancestors CSP

Layer / File(s) Summary
Module entrypoint and runtime configuration
scripts/install-server.sh, scripts/systemd/tinyagentos.service, tinyagentos/__main__.py
App transitions from direct uvicorn CLI invocation to python -m tinyagentos with environment variables (TAOS_HOST, TAOS_PORT, TAOS_BROWSER_PROXY_PORT) injected by the install script; __main__.py records the main listening port to app.state.main_port for downstream use.
Dynamic CSP with shell origin support
tinyagentos/browser_proxy_origin.py, tinyagentos/routes/desktop_browser/csp.py, tinyagentos/routes/desktop_browser/proxy.py
CSP frame-ancestors directive is computed at runtime based on shell origin derived from the incoming request. Shared-state allowlist is expanded to permit proxy access to main_port and browser_proxy_port. The proxied_response_csp function now accepts an optional shell_origin parameter and computes frame-ancestors to allow 'self' plus the provided origin when present. Proxy helper _shell_origin() derives the shell origin from request headers and port state, then passes it to CSP generation.
Test coverage for dynamic frame-ancestors
tests/routes/desktop_browser/test_csp.py
Helper method _frame_ancestors() extracts the CSP directive from the generated string; two tests verify that frame-ancestors defaults to 'self' when no shell origin is provided and includes the provided origin alongside 'self' when one is supplied.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • jaylfc/tinyagentos#301: Introduced the desktop browser proxy CSP builder and related tests; this PR extends that work with dynamic frame-ancestors computation.
  • jaylfc/tinyagentos#511: Established the dual-port isolated browser-proxy origin and shared-state delegation mechanism that this PR builds upon for shell origin derivation.

Poem

🐰 A rabbit hops through ports and frames,
Where origins dance with CSP names,
Shell whispers to proxy with care,
"Allow 'self' and share this rare,
Dynamic ancestors, now aware!" 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main fixes: enabling the proxy on real deploys by launching via the module entrypoint (dual-port launch) and computing frame-ancestors CSP per-request to allow cross-origin framing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/browser-proxy-frame-ancestors

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

jaylfc added 2 commits June 2, 2026 18:58
…ng 6969

Addresses CodeRabbit on #517: a custom-port install would drain a
hardcoded localhost:6969 (silently no-op via '|| true'). systemd already
passes TAOS_PORT into the unit Environment (this PR), so the hook inherits
it and falls back to 6969 only when unset.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/install-server.sh`:
- Around line 1167-1170: The current in-place sed invocation that injects
Environment lines into the user unit (the sed -i ... on "$unit") is
Linux-specific and inconsistent with the earlier temp-file render approach;
replace this sed -i usage with the same temp-file/atomic-rewrite pattern used
for the initial unit rendering: create a temporary file, write the existing file
contents but with the Environment=PYTHONUNBUFFERED=1 line followed by the three
Environment=TAOS_* lines (expanding $TAOS_PORT and $TAOS_BROWSER_PROXY_PORT),
then mv the temp file over "$unit" (or use an atomic install method) so the
operation is portable and consistent with the initial unit rendering logic.
- Around line 1122-1125: The use of sed -i with embedded "\n" is not portable;
replace the single-line sed -i invocation that injects Environment lines into
"$unit" with a POSIX-safe temp-file approach: run a sed command that uses the
append (a\) syntax to add the three Environment= lines after the matching
Environment=PYTHONUNBUFFERED=1 line (escaping newlines per sed's a\ form) and
write the output to a temp file, then use $sudo_cmd install (same pattern used
earlier) to atomically install the temp file over "$unit"; ensure you reference
TAOS_PORT and TAOS_BROWSER_PROXY_PORT when emitting the Environment lines and
remove the sed -i usage entirely.

In `@tinyagentos/routes/desktop_browser/proxy.py`:
- Around line 112-132: The _shell_origin function incorrectly strips ports from
IPv6 Host headers and trusts x-forwarded-proto; fix by robustly parsing the Host
header and validating the scheme: in _shell_origin, derive host by checking if
the Host header starts with '[' (take up to the matching ']') else split on the
last ':' to remove a numeric port, ensure the resulting host is non-empty, and
validate scheme = request.headers.get("x-forwarded-proto") or request.url.scheme
or "http" against an allowed set (e.g., "http" and "https") falling back to
"http" on invalid values; return f"{scheme}://{host}:{main_port}" only when host
and scheme pass validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ef9c4898-0516-44da-93c1-a980e326df41

📥 Commits

Reviewing files that changed from the base of the PR and between 3f04596 and 86ce4b7.

📒 Files selected for processing (7)
  • scripts/install-server.sh
  • scripts/systemd/tinyagentos.service
  • tests/routes/desktop_browser/test_csp.py
  • tinyagentos/__main__.py
  • tinyagentos/browser_proxy_origin.py
  • tinyagentos/routes/desktop_browser/csp.py
  • tinyagentos/routes/desktop_browser/proxy.py

Comment thread scripts/install-server.sh
Comment on lines +1122 to +1125
# Inject bind host/port + proxy port into the unit's Environment block.
# ExecStart now runs `python -m tinyagentos`, which reads these (rather
# than uvicorn CLI args), so the dual-port browser-proxy origin starts.
$sudo_cmd sed -i "s|^Environment=PYTHONUNBUFFERED=1|Environment=PYTHONUNBUFFERED=1\nEnvironment=TAOS_HOST=0.0.0.0\nEnvironment=TAOS_PORT=$TAOS_PORT\nEnvironment=TAOS_BROWSER_PROXY_PORT=$TAOS_BROWSER_PROXY_PORT|" "$unit"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

sed -i with \n may not be portable across Linux and macOS.

The script uses sed -i with \n in the replacement string to inject multiple Environment= lines. This has two portability issues:

  1. sed -i syntax differs: macOS BSD sed requires sed -i '', while GNU sed (Linux) uses sed -i. The script earlier (lines 1113-1121) avoided this by writing to a temp file first, then using install.
  2. \n in replacement: GNU sed interprets \n as a literal newline in the replacement, but BSD sed may not. Some implementations require an actual embedded newline.

Both issues would cause the systemd unit to be malformed or the sed command to fail on macOS (though this is a Linux-only code path, so macOS is not affected here).

🔧 Proposed fix: Use consistent temp-file pattern
-    # Inject bind host/port + proxy port into the unit's Environment block.
-    # ExecStart now runs `python -m tinyagentos`, which reads these (rather
-    # than uvicorn CLI args), so the dual-port browser-proxy origin starts.
-    $sudo_cmd sed -i "s|^Environment=PYTHONUNBUFFERED=1|Environment=PYTHONUNBUFFERED=1\nEnvironment=TAOS_HOST=0.0.0.0\nEnvironment=TAOS_PORT=$TAOS_PORT\nEnvironment=TAOS_BROWSER_PROXY_PORT=$TAOS_BROWSER_PROXY_PORT|" "$unit"
+    # Inject bind host/port + proxy port into the unit's Environment block.
+    # ExecStart now runs `python -m tinyagentos`, which reads these (rather
+    # than uvicorn CLI args), so the dual-port browser-proxy origin starts.
+    $sudo_cmd sed \
+        -e "/^Environment=PYTHONUNBUFFERED=1/a\\
+Environment=TAOS_HOST=0.0.0.0\\
+Environment=TAOS_PORT=$TAOS_PORT\\
+Environment=TAOS_BROWSER_PROXY_PORT=$TAOS_BROWSER_PROXY_PORT" \
+        "$unit" > /tmp/tinyagentos.service.postenv
+    $sudo_cmd mv /tmp/tinyagentos.service.postenv "$unit"

This uses a\ (append) command with backslash-escaped newlines, which is POSIX-compliant, and avoids sed -i.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install-server.sh` around lines 1122 - 1125, The use of sed -i with
embedded "\n" is not portable; replace the single-line sed -i invocation that
injects Environment lines into "$unit" with a POSIX-safe temp-file approach: run
a sed command that uses the append (a\) syntax to add the three Environment=
lines after the matching Environment=PYTHONUNBUFFERED=1 line (escaping newlines
per sed's a\ form) and write the output to a temp file, then use $sudo_cmd
install (same pattern used earlier) to atomically install the temp file over
"$unit"; ensure you reference TAOS_PORT and TAOS_BROWSER_PROXY_PORT when
emitting the Environment lines and remove the sed -i usage entirely.

Comment thread scripts/install-server.sh
Comment on lines +1167 to +1170
# Inject bind host/port + proxy port into the unit's Environment block.
# ExecStart now runs `python -m tinyagentos`, which reads these (rather
# than uvicorn CLI args), so the dual-port browser-proxy origin starts.
sed -i "s|^Environment=PYTHONUNBUFFERED=1|Environment=PYTHONUNBUFFERED=1\nEnvironment=TAOS_HOST=0.0.0.0\nEnvironment=TAOS_PORT=$TAOS_PORT\nEnvironment=TAOS_BROWSER_PROXY_PORT=$TAOS_BROWSER_PROXY_PORT|" "$unit"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Same sed -i portability issue in user unit path.

The user unit installation uses the same sed -i with \n pattern. While this is Linux-only (macOS uses launchd), the approach is inconsistent with the earlier temp-file pattern used for the initial unit rendering (lines 1154-1166).

🔧 Proposed fix: Use consistent append pattern
-    # Inject bind host/port + proxy port into the unit's Environment block.
-    # ExecStart now runs `python -m tinyagentos`, which reads these (rather
-    # than uvicorn CLI args), so the dual-port browser-proxy origin starts.
-    sed -i "s|^Environment=PYTHONUNBUFFERED=1|Environment=PYTHONUNBUFFERED=1\nEnvironment=TAOS_HOST=0.0.0.0\nEnvironment=TAOS_PORT=$TAOS_PORT\nEnvironment=TAOS_BROWSER_PROXY_PORT=$TAOS_BROWSER_PROXY_PORT|" "$unit"
+    # Inject bind host/port + proxy port into the unit's Environment block.
+    # ExecStart now runs `python -m tinyagentos`, which reads these (rather
+    # than uvicorn CLI args), so the dual-port browser-proxy origin starts.
+    sed \
+        -e "/^Environment=PYTHONUNBUFFERED=1/a\\
+Environment=TAOS_HOST=0.0.0.0\\
+Environment=TAOS_PORT=$TAOS_PORT\\
+Environment=TAOS_BROWSER_PROXY_PORT=$TAOS_BROWSER_PROXY_PORT" \
+        "$unit" > "$unit.tmp"
+    mv "$unit.tmp" "$unit"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Inject bind host/port + proxy port into the unit's Environment block.
# ExecStart now runs `python -m tinyagentos`, which reads these (rather
# than uvicorn CLI args), so the dual-port browser-proxy origin starts.
sed -i "s|^Environment=PYTHONUNBUFFERED=1|Environment=PYTHONUNBUFFERED=1\nEnvironment=TAOS_HOST=0.0.0.0\nEnvironment=TAOS_PORT=$TAOS_PORT\nEnvironment=TAOS_BROWSER_PROXY_PORT=$TAOS_BROWSER_PROXY_PORT|" "$unit"
# Inject bind host/port + proxy port into the unit's Environment block.
# ExecStart now runs `python -m tinyagentos`, which reads these (rather
# than uvicorn CLI args), so the dual-port browser-proxy origin starts.
sed \
-e "/^Environment=PYTHONUNBUFFERED=1/a\\
Environment=TAOS_HOST=0.0.0.0\\
Environment=TAOS_PORT=$TAOS_PORT\\
Environment=TAOS_BROWSER_PROXY_PORT=$TAOS_BROWSER_PROXY_PORT" \
"$unit" > "$unit.tmp"
mv "$unit.tmp" "$unit"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/install-server.sh` around lines 1167 - 1170, The current in-place sed
invocation that injects Environment lines into the user unit (the sed -i ... on
"$unit") is Linux-specific and inconsistent with the earlier temp-file render
approach; replace this sed -i usage with the same temp-file/atomic-rewrite
pattern used for the initial unit rendering: create a temporary file, write the
existing file contents but with the Environment=PYTHONUNBUFFERED=1 line followed
by the three Environment=TAOS_* lines (expanding $TAOS_PORT and
$TAOS_BROWSER_PROXY_PORT), then mv the temp file over "$unit" (or use an atomic
install method) so the operation is portable and consistent with the initial
unit rendering logic.

Comment thread tinyagentos/routes/desktop_browser/proxy.py
…518)

- Handle IPv6 literal Host headers: a bare [::1] would be mangled by
  rsplit(':',1) into '[', producing a malformed frame-ancestors CSP that
  breaks framing for IPv6 access. Extract via the closing bracket instead.
- Clamp x-forwarded-proto to http/https (take the first value of a comma
  list) so a hostile/odd proto can't deform the CSP.
- Unit tests for _strip_port covering host:port, bare host, IPv6 ±port, empty.
@jaylfc jaylfc merged commit 3cb14b8 into master Jun 2, 2026
7 checks passed
@jaylfc jaylfc deleted the fix/browser-proxy-frame-ancestors branch June 2, 2026 18:31
@github-project-automation github-project-automation Bot moved this from Todo to Done in TinyAgentOS Roadmap Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant