Skip to content

Repository files navigation

hushscribe

Transcribe confidential audio. A static web page that transcribes recordings using Privatemode.ai confidential-computing inference.

CI status GitHub Pages deployment status MIT licence Unit test coverage No backend

dhcgn.github.io/hushscribe
Nothing to install and no account here — it runs in the browser tab you open it in.
Bring a Privatemode API key, drop in a file, get a timestamped transcript.
You pay Privatemode about €0.014 per audio minute; a five-minute recording costs roughly seven cents.

Your recording is encrypted before it leaves the browser tab and stays encrypted the whole way — including while the model is transcribing it. Nobody along the chain can read it: not the inference provider, not the cloud it runs on. Ordinary encryption protects data in transit and at rest; confidential computing keeps it unreadable while it is being used.

There is no backend. Bring an API key, drop a file, get a timestamped transcript.

ARCHITECTURE.md for how it works and why it is built this way. → AGENT.md if you are an AI agent working on this repo.

A finished transcript: the audio player, the estimated cost, and a clickable timestamped segment list with .vtt, .srt, .txt, .json, Copy and Redo buttons.

The full view, showing the headline, the explanation of confidential computing, the attestation row, and the API key field. The compact view, with the explanatory text removed and the API key field hidden because a key is already stored.
Full view. Explains itself: what confidential computing adds, where to get a key, which model to pick, and what a wrong language does to your transcript. Compact view — about 58% shorter, remembered across visits. The explanations go; the attestation row, the per-minute rate, and the wrong-language warning stay. A denser layout must not become a less honest one.

Built on Privatemode, by Edgeless Systems

hushscribe is a front end and nothing more. Everything that makes the claim above true — the attested confidential VMs, the remote-attestation verifier, the end-to-end encryption, the models — is Privatemode, built by Edgeless Systems, a German company working on confidential computing.

This repository contributes a browser UI and the discipline needed to keep that UI trustworthy. It contributes none of the cryptography. Without their work there would be nothing here worth trusting.

What it costs

Transcription is billed by Privatemode per audio minute, not per file or per request:

Model Rate One hour of audio
whisper-large-v3 €0.014 / min ≈ €0.84
voxtral-mini-3b €0.004 / min ≈ €0.24

Prices as of September 2026, plus VAT where applicable — see privatemode.ai/pricing for current figures. hushscribe estimates each file's cost as soon as your browser reads its length, before the transcript comes back. hushscribe itself is free and takes no cut; you pay Privatemode directly.


Quick start

npm ci
npm run dev

Then open the page, paste a Privatemode API key, press Verify & save, and drop in an audio or video file.

Scripts

Command What it does
npm run dev Vite dev server on :5173
npm run build Static build into dist/
npm run preview Serve dist/ on :4173, exactly as Pages will
npm test Unit tests, then the GUI suite
npm run test:unit Vitest — pure logic, no browser, fast
npm run coverage Vitest with coverage, and fails below the thresholds
npm run test:e2e Playwright against the real production bundle
npm run test:ui Playwright's UI mode, for debugging a failing test
npm run test:smoke Opt-in. Transcribes test-data/ against a real enclave. Costs credit.

No test needs an API key except test:smoke. The GUI suite injects a stand-in client at the single seam production code exposes, so no mock code ever ships.

The coverage badge counts the four pure modules onlygate, segments, pricing, manifest. app.js is DOM wiring, covered by Playwright against the real built bundle rather than by Vitest, so measuring it here would report a number about the tool rather than the code. Hence unit coverage, and this is what it means. CI enforces the thresholds on every push, so the figure cannot quietly rot.

test-data/ holds ~83 MB of public-domain speeches (MLK, JFK, Eisenhower, a German talk) across every supported container, plus .opus files that must be rejected. Only test:smoke reads them. CI checks out the repo without them — a non-cone sparse checkout paired with a blobless clone, so the bytes are never fetched on a build.

Local development key

cp .env.example .env      # then paste your key

.env is gitignored. It prefills the key field during npm run dev and enables test:smoke. It is substituted with '' in every build, and CI fails the pipeline if a key-shaped string appears in dist/ — a bundle published to GitHub Pages is public.

Supported input

flac mp3 mp4 mpeg mpga m4a ogg wav webm — up to 50 MB and 1 hour per file. Anything else is rejected with the reason. Automatic re-encoding is stage 2.

Set a language to get timestamps, clickable segments, and .vtt / .srt subtitles; leave it on auto-detect and you get plain text. That coupling is the API's, not ours — verbose_json requires a language.

Install it as an app

The page ships a web app manifest, so it installs like a native app and runs in its own window — no store, no packaging, the same static page either way.

A browser install prompt reading 'Install hushscribe — confidential speech-to-text app', publisher dhcgn.github.io, with Install and Not now buttons. hushscribe running as a standalone Windows app in its own window, with its icon pinned to the taskbar.
Installing. On Windows, Chrome and Edge offer this from the address bar. The publisher shown is dhcgn.github.io — the same page, not a repackaged binary. Installed. Its own window and taskbar icon. It still has no backend and still stores everything locally; only the browser chrome has gone.

Being installed changes nothing about how it works: same encryption, same attested enclave, same localStorage. It is a shortcut and a window, not a different program.

On a phone

hushscribe on an Android phone in compact view: the sealed attestation row with an Edit key button, and the model, language and prompt controls stacked in a single column.

The layout is a single column on a narrow screen, so nothing is cut off and nothing needs pinching. Recording a meeting or an interview on your phone and transcribing it on the same device is a first-class path, not a fallback — the file never leaves the handset except encrypted, exactly as on a desktop.

Compact view earns its keep here. This is it enabled: the attestation row, the rate, the wrong-language warning and every control, with the explanatory text out of the way. The Edit key button in the attestation row is how you reach the API key field once it has been stored.

Android — Chrome offers Install app from the ⋮ menu, or prompts you directly.
iPhone and iPad — open it in Safari, then ShareAdd to Home Screen.

Either way you get a home-screen icon that launches straight into the app, with no address bar in the way.

The service worker caches nothing, deliberately. hushscribe cannot work offline — every transcription needs the API — so a cache would buy nothing, while a stale one could pin an old bundle. That bundle carries the attestation verifier and its pinned hash, so a fix must reach every client on the next load.

What is stored

Everything lives in your browser's localStorage and nowhere else: the API key, saved prompts, your last 20 transcripts. Media files are never stored — they exist only in the tab you dropped them into, which is why history has no player. Export or delete it all from the page.

Tick don't save new transcripts in this browser in the history panel and nothing new is written to disk at all: results stay on screen for as long as the tab is open and go no further. Forgetting the key, or clearing everything, also ends the live session rather than only deleting the stored copy.

Deployment

Live at dhcgn.github.io/hushscribe.

Only tagged releases publish. A commit on main is not a version. Cut one and the tag becomes what the site reports:

gh release create v1.2.0 --generate-notes

The footer shows that tag and links to its release notes, so the page always says which build you are looking at. Untagged builds — local, PR previews — say so instead of claiming a version they do not have.

Every pull request gets a preview at …/hushscribe/pr/<n>/, posted as a comment on the PR and deleted when it closes. Previews skip the coverage gate (CI already ran it on the same commit) and do not touch the released site.

One-time setup on a fresh clone or fork: Pages must serve the gh-pages branch — production at the root, previews under pr/. The workflow cannot set this for you; GITHUB_TOKEN is refused with Resource not accessible by integration.

gh api -X PUT repos/<owner>/<repo>/pages -f build_type=legacy -f 'source[branch]=gh-pages' -f 'source[path]=/'

Or Settings → Pages → Source: Deploy from a branch → gh-pages / /. The branch itself is created by the first deploy.

BASE_PATH defaults to /hushscribe/ for a project site — set it to / for a custom domain, and keep it in step with base in vite.config.js. The workflow overrides it per deploy, one level deeper for a preview.

Verifying the claim

  1. The enclave — the page shows the attested measurement it verified against.
  2. The SDKreproducibly buildable from edgelesssys/privatemode-public; the build pins its Wasm SHA-256.
  3. This page — you are reading its source. Pages publishes from this repo through a public Actions run, so the bytes you load trace back to a commit you can audit.

A trust claim nobody can trace is just a slogan.

Licence

MIT

About

Transcribe confidential audio in the browser. Encrypted on-device, readable only inside an attested TEE — nobody in the chain sees your recording or transcript, not even while it is being transcribed. Static page, no backend, bring your own Privatemode key.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages