# OpenWork Secure Vault Security Model and Key Management Explained

> Discover OpenWork Secure Vault's robust security model. Learn how it protects user OAuth credentials with OS-encrypted key files and strict filesystem permissions for enhanced data safety.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: deep-dive
- Published: 2026-08-15

---

**OpenWork protects user OAuth credentials using a desktop-only vault that leverages Electron's `SafeStorage` API with lazy-loaded, OS-encrypted key files protected by strict filesystem permissions.**

The OpenWork repository implements a security architecture designed to keep cryptographic material safeguarding user-level OAuth credentials confined to the desktop environment. This approach ensures sensitive encryption keys never exist in plaintext on disk and remain bound to the operating system's native secure storage capabilities. The implementation prioritizes **minimal attack surface** through lazy initialization and **defensive programming** via atomic file operations and comprehensive error handling.

## Core Security Principles of the OpenWork Vault

The OpenWork vault architecture rests on three foundational security principles that shape every implementation decision in `apps/desktop/electron/secure-vault-key.mjs`.

### Desktop-Only Key Confinement

OpenWork deliberately restricts vault key storage to the local desktop environment. The cryptographic material protecting OAuth credentials never leaves the device, eliminating network-based exfiltration risks for the master encryption key itself. This design choice reflects OpenWork's threat model: compromise of cloud storage should not automatically grant access to decrypted user credentials.

### Lazy-Load Activation

The vault system remains entirely dormant until a user explicitly opts into OpenWork-managed OAuth. The encrypted key file is created only upon first use, leaving the OS secure storage untouched for users who never enable the feature. This **lazy provider pattern** reduces the persistent attack surface and prevents unnecessary security dependencies.

### OS-Native Encryption Binding

Rather than implementing custom cryptography, OpenWork delegates all encryption operations to Electron's `SafeStorage` module. This binds key confidentiality to the operating system's proven keychain or credential store implementations—macOS Keychain, Windows Data Protection API, or Linux Secret Service.

## Key Generation and Storage Implementation

The vault key lifecycle follows a deterministic sequence implemented in `createDesktopVaultKeyProvider`.

### Random Key Generation with `crypto.randomBytes`

When no existing key file is detected, OpenWork generates a 32-byte cryptographically random key:

```javascript
import { randomBytes } from "node:crypto";

// From secure-vault-key.mjs implementation
const newKey = randomBytes(32);  // 256 bits of entropy

```

This key provides 256-bit security suitable for AES-256-GCM or equivalent symmetric encryption of OAuth credentials.

### Secure Storage Encryption via `SafeStorage.encryptStringAsync`

Before any filesystem write, the raw key material undergoes platform-specific encryption:

```javascript
import { SafeStorage } from "electron";

// Async encryption bound to OS secure storage
const encryptedKey = await SafeStorage.encryptStringAsync(
  newKey.toString("base64")
);

```

The `encryptStringAsync` method ensures the ciphertext can only be decrypted by the same OS user on the same machine, leveraging hardware-backed protection where available.

### Atomic File Creation with Permission Lockdown

The implementation prevents race conditions and enforces least-privilege access:

1. Write to temporary file with mode `0o600`
2. Explicit `chmod` to owner-read/write only
3. Atomic `rename` to final path
4. Cleanup of temporary file on any failure

This sequence ensures the encrypted key material never exists with world-readable permissions, even transiently.

## Platform Security Hardening

OpenWork's security model includes platform-specific validations to prevent downgrade attacks.

### Async Encryption Availability Verification

The provider validates that secure async encryption is supported before attempting operations:

```javascript
// Platform safety check from secure-vault-key.mjs
if (!safeStorage.isAsyncEncryptionAvailable()) {
  throw new Error("SafeStorage async encryption unavailable");
}

```

On Linux, this check specifically prevents fallback to the insecure "basic_text" backend, forcing utilization of a proper password store.

### Re-Encryption on Storage Migration

The implementation detects when the OS storage layer indicates ciphertext should be refreshed:

```javascript
// From decryption result handling
if (decrypted.shouldReEncrypt) {
  // Rewrites file with fresh encryption under current OS key
  await rewriteEncryptedKey(filePath, decrypted.plaintext);
}

```

This capability supports key rotation scenarios including hardware migration and OS-level credential store changes.

## Lazy Provider Pattern and Caching

The `createDesktopVaultKeyProvider` function returns a closure that implements sophisticated loading semantics:

```javascript
import { createDesktopVaultKeyProvider } from "./secure-vault-key.mjs";

const getVaultKey = createDesktopVaultKeyProvider({
  filePath: "/user-data/vault.key",
  loadSafeStorage: () => SafeStorage,
});

// First call triggers generation/loading and caches the promise
const keyPromise = getVaultKey();

// Subsequent calls receive identical promise—single load guarantee
const samePromise = getVaultKey();

```

Key provider characteristics:

- **Promise caching** eliminates redundant decryption operations
- **Error cache clearing** enables retry after transient failures
- **Process-scoped singleton** prevents key regeneration races

## Error Handling and Edge Cases

The security model distinguishes between expected and exceptional conditions:

| Condition | Behavior | Rationale |
|-----------|----------|-----------|
| `ENOENT` (missing file) | Trigger new key generation | First-use initialization |
| Decryption failure | Propagate error | Potential tampering or corruption |
| Filesystem permission errors | Propagate error | System integrity issue |
| `shouldReEncrypt` flag | Transparent re-encryption | Maintain security freshness |

This error taxonomy prevents silent failures that could mask security-relevant events while permitting graceful first-run experiences.

## Practical Integration Example

The following pattern demonstrates proper vault key acquisition for credential encryption:

```javascript
import { createDesktopVaultKeyProvider } from "./secure-vault-key.mjs";
import { SafeStorage } from "electron";
import { createCipheriv, randomBytes } from "node:crypto";

const vaultKeyProvider = createDesktopVaultKeyProvider({
  filePath: `${app.getPath("userData")}/openwork-vault.key`,
  loadSafeStorage: () => SafeStorage,
});

async function encryptOAuthToken(token) {
  const vaultKey = await vaultKeyProvider();  // Buffer of 32 bytes
  
  const iv = randomBytes(16);
  const cipher = createCipheriv("aes-256-gcm", vaultKey, iv);
  
  const encrypted = Buffer.concat([
    cipher.update(token, "utf8"),
    cipher.final()
  ]);
  const authTag = cipher.getAuthTag();
  
  return {
    iv: iv.toString("base64"),
    ciphertext: encrypted.toString("base64"),
    tag: authTag.toString("base64"),
  };
}

```

## Key Rotation Procedure

Manual rotation follows the same atomic patterns as initial creation:

```javascript
import { randomBytes } from "node:crypto";
import { writeFile, chmod, rename, rm } from "node:fs/promises";
import { SafeStorage } from "electron";

async function rotateVaultKey(filePath, safeStorage) {
  const newKey = randomBytes(32);
  const encrypted = await safeStorage.encryptStringAsync(
    newKey.toString("base64")
  );
  
  const tmpPath = `${filePath}.${process.pid}.tmp`;
  
  try {
    await writeFile(tmpPath, encrypted, { mode: 0o600 });
    await rename(tmpPath, filePath);
    return newKey;
  } catch (err) {
    await rm(tmpPath, { force: true });
    throw err;
  }
}

```

Rotation should be accompanied by re-encryption of all stored OAuth credentials with the new key material.

## Source File Reference

The complete implementation resides in specific files within the OpenWork repository:

- **`apps/desktop/electron/secure-vault-key.mjs`** — Core vault key provider with generation, encryption, and file protection logic
- **`apps/desktop/electron/secure-vault-key.test.mjs`** — Comprehensive verification of security behaviors including error conditions and re-encryption
- **`apps/desktop/electron/connect-link-keys.mjs`** — Integration example showing key utilization for service linking

## Summary

- OpenWork's secure vault binds encryption keys to OS-native secure storage via Electron's `SafeStorage` API, never persisting plaintext key material
- The lazy-load design creates the encrypted key file only upon first OAuth opt-in, minimizing persistent security dependencies
- Atomic file operations with `0o600` permissions and temporary-write patterns prevent race conditions and permission leaks
- Platform checks explicitly reject insecure Linux fallbacks and verify async encryption availability before operations
- Promise caching in `createDesktopVaultKeyProvider` ensures single key load per process with retry capability after failures

## Frequently Asked Questions

### What encryption protects the OpenWork vault key at rest?

The 32-byte vault key is encrypted by the operating system's native secure storage through Electron's `SafeStorage.encryptStringAsync` before any filesystem write. On macOS this uses Keychain services; on Windows, the Data Protection API; on Linux, the Secret Service API or compatible store. This binds decryption to the original OS user account and machine hardware where supported.

### How does OpenWork prevent the vault key from being stolen by malware?

Multiple layers reduce exposure: the encrypted file has `0o600` permissions (owner-only access); the plaintext key exists only briefly in Node.js memory during lazy loading; and the OS secure storage typically requires user session authentication for decryption. However, running malware with the user's privileges could potentially access the decrypted key—this is an inherent limitation of desktop security models, mitigated by OpenWork's confinement of credentials to the local vault.

### Can the OpenWork vault key be recovered if the user loses their laptop?

No. Because `SafeStorage` encryption is bound to the specific OS installation and user account, the encrypted key file from `apps/desktop/electron/secure-vault-key.mjs` cannot be decrypted on different hardware or after OS reinstallation. Users must re-authenticate with OAuth providers to generate new stored credentials; OpenWork does not implement cloud backup of the master vault key by design.

### What triggers re-encryption of the vault key file?

The `shouldReEncrypt` property returned by `SafeStorage.decryptString` indicates when the underlying OS storage layer recommends ciphertext refresh. This occurs during OS migrations, hardware security module changes, or explicit keychain rotations. OpenWork detects this flag transparently and rewrites the vault file with freshly encrypted key material without user intervention.