The following rules are Demo-safe's hard security guarantees. No implementation may ever violate them:
- Not written to
vault.json - Not stored in
UserDefaults - Not written to any log files
- Not stored in any temp files
Keychain → ClipboardEngine → NSPasteboard
KeychainService.retrieveKey()is the only entry point for reading plaintextClipboardEngine.copyToClipboard()is the only plaintext output path- Plaintext variables are zero-cleared immediately after use
pattern_cache_synccarries only regex + masked previewstate_changedcarries only mode statekey_updatedcarries only keyId + pattern, not value- All keys in IPC messages are transmitted only in masked representation
- Binding to
0.0.0.0or any external interface is prohibited - Only localhost connections accepted
- Prevents remote access
~/.demosafe/ipc.jsonis readable/writable only by the owner- Prevents other users or processes from reading connection info
| Data | Location | Security Level |
|---|---|---|
| Plaintext keys | macOS Keychain (com.demosafe.key.{UUID}) |
System-level encryption + optional Touch ID |
| Structural data | ~/Library/Application Support/DemoSafe/vault.json |
File system permissions |
| Preferences | UserDefaults |
User-level |
| IPC connection info | ~/.demosafe/ipc.json |
chmod 600 |
kSecAttrAccessible:whenUnlockedThisDeviceOnly- Keys are only accessible when the device is unlocked
- Optional Touch ID biometric authentication can be enabled
| Context Mode | Auto-Clear Policy |
|---|---|
| Livestream | Auto-clear after 30 seconds |
| Tutorial Recording | Auto-clear after 10 seconds |
| Internal Demo | Normal clipboard behavior |
| Development | Normal clipboard behavior |
ClipboardEngine.copyToClipboard(keyId)retrieves plaintext from Keychain- Writes to NSPasteboard
- Plaintext variable is immediately zero-cleared
- Based on context mode, schedules
startAutoClear(seconds)for auto-clear
- Extension reads port and token from
ipc.json - Connects via WebSocket to
ws://127.0.0.1:{port} - Sends
handshakerequest withclientTypeandtoken - Core verifies token before accepting subsequent requests
| Item | Description |
|---|---|
| Generation method | SecRandomCopyBytes generates 32 bytes of cryptographically secure random data → hex encoded to 64-character string |
| Lifecycle | Regenerated on every Core startup; old tokens automatically invalidated after Core shutdown |
| Storage location | Written to ~/.demosafe/ipc.json (chmod 600) |
| Rotation strategy | Rotated on Core restart; no runtime manual rotation supported (unnecessary since bound to localhost) |
| Verification failure handling | Core returns AUTH_FAILED error without closing the connection (allows Extension to re-read ipc.json and retry) |
- Exponential backoff: 1s → 2s → 4s → max 30s + random jitter
- Handshake verification must be completed again on reconnection
- Extension should re-read
ipc.jsonon reconnection (Core may have restarted; port/token may have changed)
In demo mode, API Key plaintext must not appear on any display at any time. This is not a best-effort filter — it is a hard guarantee.
| Layer | Coverage | Offline Behavior |
|---|---|---|
| VS Code Extension | Documents in editor | Continue masking with cached patterns |
| Chrome Extension | Known API pages | Continue masking with cached patterns |
| Accessibility Agent | All applications system-wide | Continue masking with cached patterns |
Key Principle: Extensions continue operating with the last cached patterns when Core is offline, ensuring protection is maintained even when Core is unavailable.
The activeServiceIds field in ContextMode can restrict the scope of services with masking enabled in a specific context. Its security semantics are as follows:
| maskingLevel | activeServiceIds | Behavior |
|---|---|---|
.full |
nil (not set) | All registered keys are fully masked — hard guarantee holds |
.full |
Specific services specified | Only keys from specified services are masked — keys from other services are not pattern-matched (equivalent to pausing masking for unlisted services) |
.partial |
Any | Displays prefix + suffix, hides only the middle section — suitable for internal demos with reduced security level |
.off |
Any | Masking completely disabled — development mode only |
Design Decision:
activeServiceIdsexists to give users flexible control in internal demo scenarios. In the default "Livestream" and "Tutorial Recording" contexts, this field is nil (mask all services), ensuring the hard security guarantee is not affected.