From ef1c7a28e2b9f81e46e1244b7db1272b0c125801 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Fri, 19 Jun 2026 17:47:25 -0700 Subject: [PATCH] [eric] publish: don't embed the install token in preview app JS; stub OUTPUT_* (run only when published) --- backend/apps/outputs/app_builder_skill.md | 4 ++- backend/apps/outputs/html_inject.py | 42 ++++++++--------------- backend/apps/outputs/models.py | 8 ----- backend/apps/outputs/outputs.py | 33 ++---------------- backend/tests/test_publish.py | 14 ++++---- 5 files changed, 27 insertions(+), 74 deletions(-) diff --git a/backend/apps/outputs/app_builder_skill.md b/backend/apps/outputs/app_builder_skill.md index 03853de4..4fbc82e1 100644 --- a/backend/apps/outputs/app_builder_skill.md +++ b/backend/apps/outputs/app_builder_skill.md @@ -330,7 +330,9 @@ export const JOBS_LIST = '/api/jobs/list'; The FastAPI backend above runs in preview but is **not hosted when an app is published** to the web. For features that should keep working on a published `{slug}.openswarm.host` link, use these two runtime calls instead of a backend. -They behave the same in preview and when published. +They run on the published site (same-origin, no credentials). In the App Builder +**preview** they throw a clear "available once published" error, preview can't run +them without embedding a credential into your app, so test these by publishing. **AI (Claude):** call `window.OUTPUT_LLM` with an Anthropic-style messages body. The model is chosen for you (a cheap default), so don't pass one. diff --git a/backend/apps/outputs/html_inject.py b/backend/apps/outputs/html_inject.py index a579d645..ba56f156 100644 --- a/backend/apps/outputs/html_inject.py +++ b/backend/apps/outputs/html_inject.py @@ -54,32 +54,20 @@ def _validate_against_schema(data: dict, schema: dict) -> str | None: return f"Schema validation failed at {path}: {exc.message}" -def _runtime_helpers_js(token: str, output_id: str | None) -> str: - """OUTPUT_COMPUTE / OUTPUT_LLM: the same runtime API the published edge injects, - pointed at this install's backend so an app works in preview AND when published. - The install token rides in the header (the iframe already exposes it via the - relative-URL rewrite; this is the same local-only credential, not a cloud secret).""" - auth = json.dumps(f"Bearer {token}") - js = "" - if output_id: - oid = json.dumps(output_id) - js += ( - " window.OUTPUT_COMPUTE = async function (input) {\n" - " var r = await fetch('/api/outputs/execute', {method:'POST', headers:{'Content-Type':'application/json','Authorization': " + auth + "}, body: JSON.stringify({output_id: " + oid + ", input_data: input || {}, force: true})});\n" - " var d = await r.json();\n" - " if (d.error) throw new Error(d.error);\n" - " return d.backend_result;\n" - " };\n" - ) - js += ( - " window.OUTPUT_LLM = async function (body) {\n" - " return fetch('/api/outputs/llm', {method:'POST', headers:{'Content-Type':'application/json','Authorization': " + auth + "}, body: JSON.stringify(body || {})});\n" - " };\n" +def _runtime_helpers_js() -> str: + """OUTPUT_COMPUTE / OUTPUT_LLM only run for real on the published edge, where they + are same-origin and carry NO credentials. In the App Builder preview we + deliberately do NOT wire them to the authenticated backend: doing so would embed + this install's token into the app's own JS (the exact exposure SECURITY.md item A + is about). Preview defines readable stubs instead, the app degrades with a clear + message rather than crashing or leaking a credential.""" + return ( + " window.OUTPUT_COMPUTE = async function () { throw new Error('OUTPUT_COMPUTE runs once this app is published.'); };\n" + " window.OUTPUT_LLM = async function () { throw new Error('OUTPUT_LLM runs once this app is published.'); };\n" ) - return js -def _build_data_injection(input_json: str, result_json: str, backend_url_json: str = "null", runtime: dict | None = None) -> str: +def _build_data_injection(input_json: str, result_json: str, backend_url_json: str = "null", with_runtime: bool = False) -> str: """Build a