@@ -3,7 +3,7 @@ import { fromBech32, toBech32 } from '../../utils/transform'
33import { getPublicKey , getRelayPrivateKey } from '../../utils/event'
44import { Request , Response } from 'express'
55
6- import { escapeHtml } from '../../utils/html'
6+ import { escapeHtml , safeJsonForScript } from '../../utils/html'
77import { createLogger } from '../../factories/logger-factory'
88import { getRemoteAddress } from '../../utils/http'
99import { IController } from '../../@types/controllers'
@@ -171,14 +171,14 @@ export class PostInvoiceController implements IController {
171171 . replaceAll ( '{{invoice_html}}' , escapeHtml ( invoice . bolt11 ) )
172172 . replaceAll ( '{{pubkey_html}}' , escapeHtml ( pubkey ) )
173173 . replaceAll ( '{{amount}}' , ( amount / 1000n ) . toString ( ) )
174- // JS string contexts — JSON.stringify handles all escaping (quotes, backslashes, </script>)
175- . replaceAll ( '{{reference_json}}' , JSON . stringify ( invoice . id ) )
176- . replaceAll ( '{{relay_url_json}}' , JSON . stringify ( relayUrl ) )
177- . replaceAll ( '{{relay_pubkey_json}}' , JSON . stringify ( relayPubkey ) )
178- . replaceAll ( '{{invoice_json}}' , JSON . stringify ( invoice . bolt11 ) )
179- . replaceAll ( '{{pubkey_json}}' , JSON . stringify ( pubkey ) )
180- . replaceAll ( '{{expires_at_json}}' , JSON . stringify ( expiresAt ) )
181- . replaceAll ( '{{processor_json}}' , JSON . stringify ( currentSettings . payments . processor ) )
174+ // JS contexts — safeJsonForScript serializes and escapes < to prevent </script> injection
175+ . replaceAll ( '{{reference_json}}' , safeJsonForScript ( invoice . id ) )
176+ . replaceAll ( '{{relay_url_json}}' , safeJsonForScript ( relayUrl ) )
177+ . replaceAll ( '{{relay_pubkey_json}}' , safeJsonForScript ( relayPubkey ) )
178+ . replaceAll ( '{{invoice_json}}' , safeJsonForScript ( invoice . bolt11 ) )
179+ . replaceAll ( '{{pubkey_json}}' , safeJsonForScript ( pubkey ) )
180+ . replaceAll ( '{{expires_at_json}}' , safeJsonForScript ( expiresAt ) )
181+ . replaceAll ( '{{processor_json}}' , safeJsonForScript ( currentSettings . payments . processor ) )
182182 // nonce is crypto-random base64 — safe in both attribute and script contexts
183183 . replaceAll ( '{{nonce}}' , response . locals . nonce )
184184
0 commit comments