Commit98782ff8Recorded25 Mar 2026Repositorysigil-lemonsqueezy

Fix License API error handling and timing-safe comparison

Message

Add HTTP 5xx status check in license-post to raise a clear error instead of passing HTML error bodies to json-decode. Rewrite timing-safe-equal? to always iterate max(len-a, len-b) characters without early-returning on length mismatch.

Changed
 src/sigil/lemonsqueezy/license.sgl |  3 +++
 src/sigil/lemonsqueezy/webhook.sgl | 19 ++++++++++++-------
 2 files changed, 15 insertions(+), 7 deletions(-)
Diff
src/sigil/lemonsqueezy/license.sglmodified
@@ -177,6 +177,9 @@
177
(error "License API request failed: no response"))
178
(let ((status (http-response-status response))
179
(resp-body (http-response-body response)))
+180
(if (>= status 500)
+181
(error (string-append "License API server error "
+182
(number->string status))))
183
(if (and resp-body (not (string=? resp-body "")))
184
(json-decode resp-body)
185
#{}))))
src/sigil/lemonsqueezy/webhook.sglmodified
@@ -124,17 +124,22 @@
124
125
;;; Timing-safe string comparison to prevent timing attacks.
126
;;; Compares every character regardless of mismatches.
+127
;;; Length difference is folded into the result without early return
+128
;;; to avoid leaking length information via timing.
129
(define (timing-safe-equal? a b)
128
(if (not (= (string-length a) (string-length b)))
129
#f
130
(let loop ((i 0) (diff 0))
131
(if (>= i (string-length a))
+130
(let ((len-a (string-length a))
+131
(len-b (string-length b)))
+132
(let ((len (if (> len-a len-b) len-a len-b))
+133
(len-diff (if (= len-a len-b) 0 1)))
+134
(let loop ((i 0) (diff len-diff))
+135
(if (>= i len)
136
(= diff 0)
137
(loop (+ i 1)
138
(+ diff
135
(if (char=? (string-ref a i)
136
(string-ref b i))
137
0 1)))))))
+139
(if (and (< i len-a) (< i len-b)
+140
(char=? (string-ref a i)
+141
(string-ref b i)))
+142
0 1))))))))
143
144
;; ---------------------------------------------------------------
145
;; API functions