mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-09 08:03:50 +00:00
fix(apps): use an unguessable nonce for the sandboxed SDK handoff
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018Gmsk9kAG7p9t2Qy6ADRJz
This commit is contained in:
co-authored by
Claude Fable 5
parent
4f4db90bf3
commit
9069eccb60
@@ -826,6 +826,10 @@ fn raw_app_wrapper_html(secret: &str) -> String {
|
||||
document.body.appendChild(s);
|
||||
}
|
||||
window.addEventListener('message', function (e) {
|
||||
// Only our embedder may supply the context. It now carries the SDK's token
|
||||
// and API base, so accepting it from any window would let another frame
|
||||
// point the bundle's API calls at a host it controls.
|
||||
if (e.source !== window.parent) return;
|
||||
var d = e.data || {};
|
||||
if (d.type === 'windmill:ctx') {
|
||||
window.ctx = d.ctx;
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
import type { Runnable } from './rawAppPolicy'
|
||||
import { getContext, onMount, untrack } from 'svelte'
|
||||
import { unsandboxedRawAppHtml } from './utils'
|
||||
import { randomUUID } from '$lib/utils/uuid'
|
||||
import { randomSecret } from '$lib/utils/uuid'
|
||||
|
||||
// Per-mount secret proving a `windmill:ready` came from the document we loaded.
|
||||
const handshakeNonce = randomUUID()
|
||||
// Gates a credential, so it must be unguessable — not `randomUUID`.
|
||||
const handshakeNonce = randomSecret()
|
||||
|
||||
interface Props {
|
||||
workspace: string
|
||||
|
||||
@@ -5,3 +5,13 @@ export function randomUUID() {
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
/** Unguessable random token, for values whose unpredictability is load-bearing —
|
||||
* anything gating a credential. `randomUUID` above is `Math.random()`-based and
|
||||
* must not be used for those. `getRandomValues` (unlike `crypto.randomUUID`) is
|
||||
* also available in insecure contexts, so this needs no fallback. */
|
||||
export function randomSecret(bytes = 32): string {
|
||||
const buf = new Uint8Array(bytes)
|
||||
crypto.getRandomValues(buf)
|
||||
return Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('')
|
||||
}
|
||||
|
||||
@@ -68,10 +68,11 @@ export function setClient(token?: string, baseUrl?: string) {
|
||||
if (token === undefined) {
|
||||
token = getEnv("WM_TOKEN") ?? "no_token";
|
||||
}
|
||||
// Cookies are the fallback for when there is no bearer token. Sending both is
|
||||
// not just redundant, it breaks the browser case outright: the API answers
|
||||
// `Access-Control-Allow-Origin: *` and so can never allow credentials, and a
|
||||
// Credentials must be off whenever a bearer token is in play: the API answers
|
||||
// `Access-Control-Allow-Origin: *` and so can never allow them, and a
|
||||
// credentialed cross-origin request is rejected before the token is looked at.
|
||||
// (`"no_token"` is still sent as a bearer, so this branch is about credentials
|
||||
// mode only — it does not make cookie auth work.)
|
||||
OpenAPI.WITH_CREDENTIALS = token === "no_token";
|
||||
OpenAPI.TOKEN = token;
|
||||
OpenAPI.BASE = baseUrl + "/api";
|
||||
|
||||
Reference in New Issue
Block a user