You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
review round 1: apply council (4 Claude + 4 Codex personas) + CodeRabbit findings
Security/correctness:
- sdk: proxy mode (baseUrl) never sends Authorization, even when apiKey
is also passed — the documented contract now holds (cross-engine High)
- sdk: presigned-URL query strings (bearer-like signatures) redacted
from rawFetch error messages; non-https upload_url refused
- proxy: reject empty path segments so matching always equals forwarding
API semantics (the one breaking-ish change, pre-publish):
- proxy: allowedPaths now REPLACES the defaults, exactly like
allowedModels — spread DEFAULT_ALLOWED_PATHS to extend, [] to disable
the asset routes (consensus P1: two opposite override semantics on one
config object; forks could never turn the default uploads off)
- proxy: defaults gain GET /v1/assets/:id for rf.assets.get
Resilience (perf P1):
- sdk: rf.assets.upload retries transient failures (network/timeout/5xx,
250/750ms) on all three legs — parity with the studio path it replaces
- sdk: PUT timeout scales with file size (2 Mbit/s floor, min 120s)
DX:
- sdk: rf.assets.get(id) re-mints expired signed urls; "store the id"
documented; RunflowErrorCode union for autocompletable catch blocks
- proxy: 403/415 bodies carry actionable messages + machine codes
(path_not_allowed, model_not_allowed, origin_not_allowed,
json_content_type_required)
- studio: uploadFile defaults to the SDK presigned flow through
runflowProxy (zero-config); hosts with an explicit urls.upload keep
the legacy multipart path — ends the two-contradictory-upload-stories
problem; READMEs aligned
Maintainability:
- studio: useShellConfig fallback warns once instead of silently serving
built-ins; = WORKFLOWS default params removed; StudioShellProps
documents mount-only source + stable-reference expectations
- studio: config memo keys on fields, not object identity — inline
copy={{...}}/sentinel={{...}} no longer re-mint the context value
- studio: mask controller clamps brush size, warns once on
unattached/unsynced use, willReadFrequently on CPU-read canvases;
shell lazily inits the controller ref
- ci: biome warning-count ratchet (budget 45)
CodeRabbit CLI (3 of 8 taken; rest skipped with reasons in the PR):
- pin.ts boundary doc corrected (0.66 falls lower/right)
- unmount(): theme CSS variables cleared
- blob preview URLs revoked on unmount (GeneratePanel + StudioShell) via
ref mirror — unmount-only, never revokes live previews
Tests: 72 → 84 (auth-mode pair; retry/4xx/https/redaction/assets.get;
replacement/spread/opt-out/empty-segment/coded-403 proxy coverage).
Copy file name to clipboardExpand all lines: .changeset/proxy-allowed-paths.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,4 +2,4 @@
2
2
"@runflow-io/proxy": minor
3
3
---
4
4
5
-
Add `allowedPaths` — an extensible, strictly-matched route allow-list on top of the built-ins (dispatch, run polling, health). Defaults now include the asset-upload pair `rf.assets.upload` needs (`POST /v1/asset-uploads`, `POST /v1/asset-uploads/:id/confirmations`); customer rules are additive, support method arrays and `:param` segments, and reject traversal. Org-data reads (run listing, billing) remain strictly opt-in. The handler now also exposes `PUT`/`PATCH`/`DELETE` for framework route exports. `RateLimitResult`'s `void` member is now `undefined` (type-level only).
5
+
Add `allowedPaths` — an extensible, strictly-matched route allow-list on top of the built-ins (dispatch, run polling, health). Defaults cover what `rf.assets.upload`/`rf.assets.get` need (`POST /v1/asset-uploads`, `POST /v1/asset-uploads/:id/confirmations`, `GET /v1/assets/:id`). Like `allowedModels`, a custom list **replaces** the defaults — spread the exported `DEFAULT_ALLOWED_PATHS` to extend, or pass `[]` to disable the asset routes. Rules support method arrays and `:param` segments, reject traversal (including percent-encoded) and empty segments. 403/415 bodies now carry actionable messages plus machine-readable `code`s (`path_not_allowed`, `model_not_allowed`, `origin_not_allowed`, `json_content_type_required`). The handler also exposes `PUT`/`PATCH`/`DELETE` for framework route exports. `RateLimitResult`'s `void` member is now `undefined` (type-level only).
Copy file name to clipboardExpand all lines: .changeset/sdk-assets-pin.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,8 @@
2
2
"@runflow-io/sdk": minor
3
3
---
4
4
5
-
Add `rf.assets.upload(file)` — the browser-safe presigned upload flow (create session → PUT to storage → confirm), returning a model-ready signed HTTPS `url` plus the stable `runflow://assets/{id}``ref`. Fixes the most common external-fork failure: browser file uploads ending up as `data:` URIs that models reject with a 422.
5
+
Add `rf.assets.upload(file)` — the browser-safe presigned upload flow (create session → PUT to storage → confirm) with transient-failure retry and a size-scaled PUT timeout, returning a model-ready signed HTTPS `url` plus the stable `runflow://assets/{id}``ref`. Fixes the most common external-fork failure: browser file uploads ending up as `data:` URIs that models reject with a 422. Add `rf.assets.get(id)` to re-mint an expired signed url (store the `id`, not the `url`).
6
6
7
7
Export `composePinPrompt`, `composeRegionPrompt`, `pinRegion`, and `PinPoint` — the pin→region prompt convention (3×3 grid baked into the edit prompt) that previously existed only as private copies inside the studio bundle.
8
+
9
+
Hardening: proxy mode (`baseUrl`) now never sends `Authorization`, even when `apiKey` is also passed (the documented contract); presigned-URL query strings are redacted from error messages; non-https `upload_url`s are refused. New `RunflowErrorCode` union for autocompletable `catch` handling.
Copy file name to clipboardExpand all lines: .changeset/studio-props-mask.md
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,8 @@
2
2
"@runflow-io/studio": minor
3
3
---
4
4
5
-
`<StudioShell>` accepts four optional customization props — `tools` (workflow catalogue), `source` (initial asset URL or sample list), `sentinel` (`{ enabled, taskDescription }`), and `copy` (brand/labels) — making vertical forks possible without rebuilding on `./headless`. Zero props renders exactly as before. `mount()` forwards them via the new `props` option.
5
+
`<StudioShell>` accepts four optional customization props — `tools` (workflow catalogue), `source` (initial asset URL or sample list, read at mount), `sentinel` (`{ enabled, taskDescription }`), and `copy` (brand/labels) — making vertical forks possible without rebuilding on `./headless`. Zero props renders exactly as before. `mount()` forwards them via the new `props` option.
6
6
7
-
`./headless` now exports `createMaskController` — the framework-free dual-canvas brush engine (stroke interpolation, coverage, full-resolution thresholded mask blob) the shell itself uses, so headless consumers get working mask creation for inpaint workflows without rebuilding it.
7
+
`./headless` now exports `createMaskController` — the framework-free dual-canvas brush engine (stroke interpolation, coverage, full-resolution thresholded mask blob, guarded against unattached use and bad brush sizes) the shell itself uses, so headless consumers get working mask creation for inpaint workflows without rebuilding it.
8
+
9
+
The shell's file uploads now default to the SDK's presigned flow through `runflowProxy` (zero-config — no separate `upload` endpoint needed); hosts that explicitly set `urls.upload` keep the legacy multipart path. `unmount()` now also clears theme CSS variables, and blob preview URLs are revoked on unmount.
error: `Path not allowed: ${req.method} /${upstreamPath.slice(0,120)}. The proxy forwards model dispatch, run polling, health, and the asset upload/read routes by default; add other upstream routes via the allowedPaths option.`,
167
+
code: "path_not_allowed",
168
+
},
169
+
403,
170
+
);
158
171
}
159
172
160
173
// CSRF gate — must run before any authenticate hook so a malicious
161
174
// page can't drain cookie credentials into the customer's API key.
162
175
if(req.method!=="GET"&&req.method!=="HEAD"){
163
176
if(!originAllowed(req,c.allowedOrigins)){
164
-
returnjson({error: "Origin not allowed"},403);
177
+
returnjson({error: "Origin not allowed",code: "origin_not_allowed"},403);
0 commit comments