security(C1): remove private key from get-config IPC response
Move atmPrivateKey and adminToken out of the general get-config IPC handler into a dedicated one-shot get-atm-secrets handler that returns secrets only once per app lifecycle. Subsequent calls return empty strings. This prevents XSS or DevTools from repeatedly querying getConfig() to steal the ATM's Nostr private key. TODO: Move signing/encryption to main process entirely (Phase 2) so the private key never crosses the IPC boundary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d8841f7fe9
commit
2273303b13
4 changed files with 56 additions and 10 deletions
|
|
@ -65,6 +65,10 @@ interface LightningConfig {
|
|||
|
||||
/**
|
||||
* Load configuration - async to support Electron IPC
|
||||
*
|
||||
* SECURITY: Public config comes from get-config IPC.
|
||||
* Secrets (private key, admin token) come from the one-shot get-atm-secrets IPC,
|
||||
* which returns secrets only once per app lifecycle.
|
||||
*/
|
||||
async function loadLightningConfig(): Promise<LightningConfig> {
|
||||
// Development defaults (local Docker infrastructure)
|
||||
|
|
@ -82,13 +86,15 @@ async function loadLightningConfig(): Promise<LightningConfig> {
|
|||
if (isElectron && window.electronAPI) {
|
||||
try {
|
||||
const runtimeConfig = await window.electronAPI.getConfig()
|
||||
// Secrets come from a separate one-shot IPC handler
|
||||
const secrets = await window.electronAPI.getAtmSecrets()
|
||||
return {
|
||||
relayUrl: runtimeConfig.relayUrl || defaults.relayUrl,
|
||||
lightningPubPubkey: runtimeConfig.lightningPubPubkey || defaults.lightningPubPubkey,
|
||||
lightningPubApiUrl: runtimeConfig.lightningPubApiUrl || defaults.lightningPubApiUrl,
|
||||
extensionApiUrl: runtimeConfig.extensionApiUrl || defaults.extensionApiUrl,
|
||||
adminToken: runtimeConfig.adminToken || defaults.adminToken,
|
||||
atmPrivateKey: runtimeConfig.atmPrivateKey || defaults.atmPrivateKey,
|
||||
adminToken: secrets.adminToken || defaults.adminToken,
|
||||
atmPrivateKey: secrets.atmPrivateKey || defaults.atmPrivateKey,
|
||||
appId: runtimeConfig.appId || defaults.appId,
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
8
apps/machine/src/types/electron.d.ts
vendored
8
apps/machine/src/types/electron.d.ts
vendored
|
|
@ -7,8 +7,6 @@ export interface RuntimeConfig {
|
|||
lightningPubPubkey: string
|
||||
lightningPubApiUrl: string
|
||||
extensionApiUrl: string
|
||||
atmPrivateKey: string
|
||||
adminToken: string
|
||||
appId: string
|
||||
machineModel: string
|
||||
fiatCode: string
|
||||
|
|
@ -18,11 +16,17 @@ export interface RuntimeConfig {
|
|||
allowMockFallback: boolean
|
||||
}
|
||||
|
||||
export interface AtmSecrets {
|
||||
atmPrivateKey: string
|
||||
adminToken: string
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI?: {
|
||||
getVersion: () => Promise<string>
|
||||
getConfig: () => Promise<RuntimeConfig>
|
||||
getAtmSecrets: () => Promise<AtmSecrets>
|
||||
loadCassettes: () => Promise<{ denomination: number; count: number }[]>
|
||||
setCassettes: (cassettes: { denomination: number; count: number }[]) => Promise<void>
|
||||
getInventory: () => Promise<Record<number, number>>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue