How OpenWhispr Manages Temporary Audio Files: Secure Lifecycle and Privacy Protection
OpenWhispr manages temporary audio files by capturing audio in the renderer process via the MediaRecorder API, transmitting the buffer to the main process through the audio-buffer IPC channel, writing it to a uniquely-named temporary directory using fs.mkdtempSync, processing it through the transcription engine, and immediately purging it with fs.unlink after transcription completes, ensuring zero residual voice data remains on disk.
OpenWhispr is an open-source Electron-based voice transcription application that guarantees user privacy through strict ephemeral storage practices. Understanding how OpenWhispr manages temporary audio files is essential for developers auditing the security model or building similar privacy-first audio applications. The entire workflow—from microphone capture to cryptographic deletion—is orchestrated across four distinct stages in the main and renderer processes.
The Four-Stage Temporary File Lifecycle
The temporary audio file lifecycle in OpenWhispr follows a strict chain-of-custody pattern designed to minimize the window of time that raw voice data exists on persistent storage.
Stage 1: Audio Capture and IPC Transfer
In the renderer process, the useAudioRecording.js hook utilizes the browser's MediaRecorder API to capture raw audio streams. When the user stops recording, the captured audio exists as a JavaScript Blob. Rather than writing this data to disk in the renderer (which lacks secure filesystem access), the application transmits the binary buffer to the main process via Electron's Inter-Process Communication (IPC) system.
The renderer sends the payload through the audio-buffer channel:
// src/hooks/useAudioRecording.js
window.api.send('audio-buffer', { buffer: audioBlobArrayBuffer });
This approach keeps the renderer process sandboxed while ensuring the main process—which possesses filesystem privileges—handles all disk operations.
Stage 2: Secure Temporary File Creation
Upon receiving the IPC message, the audio manager (/src/helpers/audioManager.js) generates an isolated temporary directory using Node.js filesystem utilities. The code creates a unique directory prefix to prevent naming collisions and writes the raw buffer to a WebM file inside this directory.
The implementation uses os.tmpdir() to locate the system temporary folder and fs.mkdtempSync to generate a cryptographically random directory name:
// src/helpers/audioManager.js
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openwhispr-audio-'));
const tmpPath = path.join(tmpDir, `recording-${Date.now()}.webm`);
await fs.promises.writeFile(tmpPath, Buffer.from(buffer));
This design ensures each recording session receives its own isolated path (e.g., /tmp/openwhispr-audio-XXXXXX/recording-1234567890.webm), eliminating race conditions and preventing filename collisions during concurrent recordings.
Stage 3: Transcription Engine Processing
With the temporary file persisted to disk, the whisper wrapper (/src/helpers/whisper.js) launches the selected transcription engine—whether local whisper.cpp, NVIDIA Parakeet, or a cloud provider. The engine reads the temporary file path, performs the speech-to-text conversion, and returns the transcript result.
// src/helpers/whisper.js
const transcript = await whisper.transcribeFile(tmpPath, { language: 'en' });
The transcription engine operates on the file directly, streaming the audio data into the inference pipeline without creating additional copies or cache files.
Stage 4: Guaranteed Cleanup and Privacy Protection
Regardless of whether transcription succeeds or fails, the application immediately deletes the temporary file. The IPC handlers (/src/helpers/ipcHandlers.js) orchestrate this cleanup by calling fs.unlink on the temporary path after receiving the transcription result.
// src/helpers/ipcHandlers.js
await fs.promises.unlink(tmpPath);
This cleanup occurs in a finally block or equivalent error-handling structure, ensuring that temporary files are removed even if the transcription engine crashes or returns an error. The result is then forwarded to the renderer via the transcription-result channel:
// Renderer receives final text
window.api.on('transcription-result', ({ text }) => {
// Display transcription to user
});
Key Source Files and Responsibilities
Understanding the division of responsibilities across the codebase clarifies how OpenWhispr maintains security boundaries:
/src/helpers/audioManager.js: Generates unique temporary directories, writes audio buffers to disk, and exposes cleanup utilities./src/helpers/whisper.js: Wraps the transcription engine logic, accepting file paths and returning text without handling filesystem cleanup./src/helpers/ipcHandlers.js: Central router connecting renderer IPC events to audio manager functions and scheduling post-transcription cleanup./src/hooks/useAudioRecording.js: Browser-side hook managing MediaRecorder lifecycle and binary buffer transmission.
Implementation Example: Complete Flow
The following condensed example illustrates the complete temporary file lifecycle from capture to deletion:
// Renderer Process (useAudioRecording.js)
const recorder = new MediaRecorder(stream);
recorder.onstop = async () => {
const blob = new Blob(chunks, { type: 'audio/webm' });
const buffer = await blob.arrayBuffer();
window.api.send('audio-buffer', { buffer });
};
// Main Process (ipcHandlers.js / audioManager.js)
window.api.on('audio-buffer', async (event, { buffer }) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openwhispr-audio-'));
const tmpPath = path.join(tmpDir, `recording-${Date.now()}.webm`);
try {
await fs.promises.writeFile(tmpPath, Buffer.from(buffer));
const transcript = await whisper.transcribeFile(tmpPath);
event.sender.send('transcription-result', { text: transcript });
} finally {
await fs.promises.unlink(tmpPath); // Guaranteed cleanup
}
});
Summary
OpenWhispr manages temporary audio files through a privacy-first pipeline that minimizes disk persistence:
- Renderer isolation: Audio capture occurs in the sandboxed renderer, with raw data transmitted via IPC rather than written locally.
- Cryptographic uniqueness: Each recording receives a unique directory via
fs.mkdtempSyncto prevent collision attacks. - Atomic cleanup: The
fs.unlinkoperation executes immediately after transcription, regardless of success or failure, ensuring no forensic residue remains. - Single-responsibility architecture: File creation, transcription, and deletion are handled by distinct modules (
audioManager.js,whisper.js,ipcHandlers.js) to prevent cleanup logic from being bypassed.
Frequently Asked Questions
Where does OpenWhispr store temporary audio files?
OpenWhispr stores temporary audio files exclusively within the operating system's designated temporary directory, retrieved via os.tmpdir(). Each recording creates a uniquely-named subdirectory (e.g., openwhispr-audio-XXXXXX) inside this system temp folder to isolate individual sessions and prevent filename collisions.
How does OpenWhispr ensure temporary files are deleted after transcription?
The application guarantees deletion through the fs.unlink function called in the main process IPC handler (ipcHandlers.js). This cleanup executes in a finally block or equivalent error-handling structure, ensuring the temporary file is removed whether the transcription succeeds, fails, or throws an exception, leaving zero residual audio data on disk.
Why does OpenWhispr use the main process instead of the renderer to write audio files?
OpenWhispr utilizes the main process for filesystem operations because Electron's renderer process runs in a sandboxed browser environment without direct access to Node.js filesystem APIs. By transmitting the audio buffer via IPC to the main process, the application leverages privileged filesystem access while maintaining renderer security, ensuring that temporary files can be written to os.tmpdir() and securely deleted using fs.unlink.
What prevents naming collisions when multiple recordings occur simultaneously?
The audioManager.js module prevents collisions by invoking fs.mkdtempSync with the prefix openwhispr-audio-, which generates a cryptographically random directory suffix (e.g., openwhispr-audio-a1B2c3). Each recording session writes its WebM file into this unique directory, ensuring that concurrent transcriptions operate on isolated file paths without overwriting each other.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →