Is EkaScribe: AI-Powered Medical Scribe safe?
EkaScribe loads a remote medical-scribe worker from jsDelivr to handle auth tokens and audio uploads.
When EkaScribe initializes, it fetches a medical-scribe SDK worker script from cdn.jsdelivr.net and starts it as a SharedWorker. The extension then posts auth-token updates and audio frames with file names to that worker for compression and upload through the SDK.
Who publishes itOrbi Health Private Limited - no other listings under this identity
Orbi Health Private Limited - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote jsDelivr Worker Handles Scribe Auth and Uploads
Opening the EkaScribe panel, the extension downloads worker code for its scribe SDK from jsDelivr instead of shipped code only.
That worker handles session-token updates and audio uploads, so a CDN script touches credential data.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
You open the EkaScribe panel to use the medical-scribe workflow.
The extension side panel is declared as index.html, which loads the main application bundle.
The extension downloads a jsDelivr worker bundle and uses it for scribe-session worker operations.
Dynamic analysis observed this path from the panel load through SDK initialization to worker setup.
Observed when the extension panel initialized the medical-scribe SDK.
- Scribe session tokentoken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkb2N0b3Itb2lkIn0.signature (illustrative)
Lets the worker-backed scribe flow authenticate requests as your EkaScribe session.
- Upload authorization headerAuthorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkb2N0b3Itb2lkIn0.signature (illustrative)
Lets upload requests carry your session authorization with each audio chunk.
- Audio frames for the consultationaudioFrames: Float32Array(432000) (illustrative)
Contains the recorded consultation audio that the worker is asked to compress and upload.
- Chunk upload metadatafileName: consult_2026-07-13T05-58-00.000Z.mp3; storageProvider: s3 (illustrative)
Ties each uploaded audio chunk to a file name, storage provider, and upload instructions.
The shipped bundle fetches a remote worker, then sends auth and upload messages to it
Readable equivalent of the worker fetch helper
assets/main-CnDDJVNJ.jsasync function fetchWorkerScriptAsBlobUrl(workerUrl) { const response = await fetch(workerUrl); if (!response.ok) { throw Error(`Failed to fetch worker script: ${response.status} ${response.statusText}`); } const scriptText = await response.text(); const workerBlob = new Blob([scriptText], { type: "application/javascript" }); return URL.createObjectURL(workerBlob);}Readable equivalent of SDK initialization
assets/main-CnDDJVNJ.jsfunction createInstance() { if (sdkInstance) return; const config = { env: ENV, flavour: "extension", enableTracking: true, allianceConfig: { baseUrl: ALLIANCE_BASE_URL, useWorker: true, }, ...(sharedWorkerUrl ? { sharedWorkerUrl } : {}), }; sdkInstance = createSdk(config); sdkInstance.registerCallback("onTokenRequired", async () => { await refreshToken(); return ""; }); sdkInstance.registerCallback("onError", event => { console.error("[EkaScribe SDK Error]", event.type, event.error); }); registerCallbacks(sdkInstance);}async function initEkaScribe() { if (!initPromise) { initPromise = (async () => { try { const url = await fetchWorkerScriptAsBlobUrl("https://cdn.jsdelivr.net/npm/med-scribe-alliance-ts-sdk@2.0.37/dist/worker.bundle.js"); if (url) sharedWorkerUrl = url; } catch {} createInstance(); })(); } return initPromise;}Readable equivalent of worker setup and messages
assets/main-CnDDJVNJ.jsclass UploadWorkerManager { constructor(callbackRegistry, fileManager, transport, workerConfig) { this.worker = null; this.port = null; this.useWorker = false; this.uploadPayload = {}; this.storageProviderName = ""; this.uploadHeaders = {}; this.pendingUploads = new Set(); this.allUploadsResolver = null; this.callbackRegistry = callbackRegistry; this.fileManager = fileManager; this.transport = transport; if (!workerConfig?.forceMainThread && typeof SharedWorker !== "undefined" && workerConfig?.workerScriptUrl) { try { this.worker = new SharedWorker(workerConfig.workerScriptUrl, { name: "scribe-sdk-worker" }); this.port = this.worker.port; this.port.onmessage = event => { this.handleWorkerMessage(event.data); }; this.port.start(); this.useWorker = true; } catch (error) { console.warn("[ScribeSDK] SharedWorker failed to initialize, falling back to main thread:", error); this.worker = null; this.port = null; this.useWorker = false; } } } updateAuthToken(token) { if (this.useWorker && this.port) { this.postToWorker({ type: "update_auth_token", token }); } this.transport.setAuthToken(token); this.uploadHeaders.Authorization = `Bearer ${token}`; } compressAndUpload(audioFrames, fileName, chunkIndex) { const chunkSeconds = audioFrames.length / H$1; if (chunkSeconds > 27) { this.fileManager.markFailure(chunkIndex, new Blob(), `Chunk exceeds maximum length: ${chunkSeconds.toFixed(1)}s > 25s`); this.callbackRegistry.dispatch("onError", { type: m$3.VALIDATION_ERROR, timestamp: new Date().toISOString(), error: { code: g$1.CHUNK_LENGTH_EXCEEDED, message: `Audio chunk "${fileName}" exceeds maximum length of 25s (actual: ${chunkSeconds.toFixed(1)}s). Upload skipped.`, }, }); return; } if (this.useWorker && this.port) { this.compressAndUploadViaWorker(audioFrames, fileName); } else { this.compressAndUploadOnMainThread(audioFrames, fileName, chunkIndex); } } compressAndUploadViaWorker(audioFrames, fileName) { this.postToWorker({ type: "compress_and_upload", audioFrames, fileName, storageProvider: this.storageProviderName, upload: this.uploadPayload, headers: { ...this.uploadHeaders }, }); }}- cdn.jsdelivr.net
Serves med-scribe-alliance-ts-sdk version 2.0.37 worker.bundle.js, which the extension converts into the worker script URL.