commit e3b2f049795121bcd4e99cf07f221f0207cdac21
parent 0989504aa59c5e6911a7f4b862782e8635ba2b8c
Author: Florian Dold <dold@taler.net>
Date: Tue, 21 Jul 2026 11:06:20 +0200
util: keep control characters out of generated payment QR codes
The EPC and Swiss records are newline delimited and interpolate payto
parameters, which the parser percent-decodes, so a receiver-name containing
%0A made the beneficiary IBAN of the code attacker-chosen.
Diffstat:
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/packages/taler-util/src/qr.ts b/packages/taler-util/src/qr.ts
@@ -24,6 +24,19 @@ import { Result } from "./result.js";
type EncodeResult = { type: "ok"; qrContent: string } | { type: "skip" };
/**
+ * Both record formats are newline delimited, so a value carrying a line break
+ * would shift every field after it -- including the beneficiary account. No
+ * field of either format may contain a control character anyway.
+ */
+function qrField(value: string | undefined): string {
+ if (value == null) {
+ return "";
+ }
+ // eslint-disable-next-line no-control-regex
+ return value.replace(/[\u0000-\u001f\u007f]/g, " ");
+}
+
+/**
* See "Schweizer Implementation Guidelines QR-Rechnung".
*/
function encodePaytoAsSwissQrBill(paytoUri: string): EncodeResult {
@@ -55,11 +68,11 @@ function encodePaytoAsSwissQrBill(paytoUri: string): EncodeResult {
beneficiaryIban, // Beneficiary IBAN
// Group: Beneficiary
"S", // Address type (S: structured)
- parsedPayto.params["receiver-name"], // Beneficiary name
+ qrField(parsedPayto.params["receiver-name"]), // Beneficiary name
"", // street
"", // apt. nr.
- parsedPayto.params["receiver-postal-code"], // town, // postal code
- parsedPayto.params["receiver-town"], // town
+ qrField(parsedPayto.params["receiver-postal-code"]), // town, // postal code
+ qrField(parsedPayto.params["receiver-town"]), // town
countryCode, // Country
// Group: Ultimate Debtor (not used in version 0200)
"", // Ultimate Debtor Address type (S: structured)
@@ -82,9 +95,9 @@ function encodePaytoAsSwissQrBill(paytoUri: string): EncodeResult {
"", // Debtor country
// Group: Reference
referenceType, // reference type
- referenceValue, // Reference
+ qrField(referenceValue), // Reference
// Group: Additional Information
- parsedPayto.params["message"], // Unstructured data
+ qrField(parsedPayto.params["message"]), // Unstructured data
"EPD", // End of payment data
];
@@ -129,11 +142,11 @@ function encodePaytoAsEpcQr(paytoUri: string): EncodeResult {
"1", // character set (1: UTF-8)
"SCT", // Identification
"", // optional BIC
- parsedPayto.params["receiver-name"], // Beneficiary name
+ qrField(parsedPayto.params["receiver-name"]), // Beneficiary name
beneficiaryIban, // Beneficiary IBAN
amt, // Amount (optional)
"", // AT-44 Purpose
- parsedPayto.params["message"], // AT-05 Unstructured remittance information
+ qrField(parsedPayto.params["message"]), // AT-05 Unstructured remittance information
];
return {
type: "ok",