security(TASK-8): fix 8 pentest findings — C1/C3/H1/M1/M2/L2

CRITICAL:
- C1: /lnd-connect-info now requires session auth, CORS wildcard removed
- C3: DEV_MODE removed from production service file (dev override only)

HIGH:
- H1: node-message endpoint now verifies ed25519 signatures when
  provided, logs warning for unsigned messages

MEDIUM:
- M1: content.add rejects filenames containing ".." (path traversal)
- M2: NIP-07 postMessage responses use specific origin instead of '*'

LOW:
- L2: Onion validation now enforces strict v3 format (56 base32 chars
  + ".onion", exactly 62 chars, no colons)

Previously fixed: C2 (RPC creds generated per-install from secrets)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-18 19:45:10 +00:00
parent 302f22019d
commit 0d28d28bf7
7 changed files with 44 additions and 16 deletions

View File

@@ -234,7 +234,7 @@ export const useAppLauncherStore = defineStore('appLauncher', () => {
const remember = await requestConsent(title.value || 'App', 'signEvent', eventKind, content)
if (remember) saveApprovedOrigin(origin)
} catch {
source.postMessage({ type: 'nostr-response', id, error: 'User denied signing request' }, '*')
source.postMessage({ type: 'nostr-response', id, error: 'User denied signing request' }, origin || '*')
return
}
}
@@ -278,10 +278,10 @@ export const useAppLauncherStore = defineStore('appLauncher', () => {
} else {
throw new Error(`Unsupported NIP-07 method: ${method}`)
}
source.postMessage({ type: 'nostr-response', id, result }, '*')
source.postMessage({ type: 'nostr-response', id, result }, origin || '*')
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error'
source.postMessage({ type: 'nostr-response', id, error: message }, '*')
source.postMessage({ type: 'nostr-response', id, error: message }, origin || '*')
}
}

View File

@@ -635,10 +635,12 @@ async function handleNostrRequest(event: MessageEvent) {
else if (method === 'nip44.encrypt') { result = (await rpcClient.call<{ ciphertext: string }>({ method: 'identity.nostr-encrypt-nip44', params: { id: identityId || undefined, pubkey: params.pubkey, plaintext: params.plaintext } })).ciphertext }
else if (method === 'nip44.decrypt') { result = (await rpcClient.call<{ plaintext: string }>({ method: 'identity.nostr-decrypt-nip44', params: { id: identityId || undefined, pubkey: params.pubkey, ciphertext: params.ciphertext } })).plaintext }
else { throw new Error(`Unsupported NIP-07 method: ${method}`) }
source.postMessage({ type: 'nostr-response', id, result }, '*')
const targetOrigin = appUrl.value ? new URL(appUrl.value).origin : '*'
source.postMessage({ type: 'nostr-response', id, result }, targetOrigin)
} catch (err) {
if (import.meta.env.DEV) console.error(`[NIP-07] ${method} FAILED:`, err instanceof Error ? err.message : err)
source.postMessage({ type: 'nostr-response', id, error: err instanceof Error ? err.message : 'Unknown error' }, '*')
const targetOrigin = appUrl.value ? new URL(appUrl.value).origin : '*'
source.postMessage({ type: 'nostr-response', id, error: err instanceof Error ? err.message : 'Unknown error' }, targetOrigin)
}
}