release: v1.16.0 — offer live pricing, bot health telemetry, edge blocklist #247

Merged
developer merged 11 commits from development into master 2026-07-11 11:56:09 +00:00
2 changed files with 47 additions and 4 deletions
Showing only changes of commit b54371845f - Show all commits
+23 -4
View File
@@ -117,9 +117,28 @@ func offerPrice(e catalogEntry, method string, cur Currency) string {
return "—"
}
// offerCell escapes an owner-entered title for a markdown table cell: a literal pipe would break the
// column layout, and a newline would break the row.
// offerCellReplacer neutralises every metacharacter of an admin-entered title so it renders as
// literal text in the public offer. The title is operator input (the /_gm catalog editor) that flows
// into a markdown table cell and then through marked into the /offer/ HTML, which is deliberately not
// sanitised — so escaping here is the trust boundary. It covers HTML (no tag or entity reaches the
// page), the markdown table pipe and the row newline, and the link brackets (a title must never
// become a "javascript:" link). marked passes the entities through unchanged, so the reader sees the
// exact title. NewReplacer scans once and never re-scans its own output, so "&" → "&" does not
// double-escape the entities the other rules emit.
var offerCellReplacer = strings.NewReplacer(
"&", "&",
"<", "&lt;",
">", "&gt;",
`"`, "&quot;",
"'", "&#39;",
"|", `\|`,
"[", `\[`,
"]", `\]`,
"\n", " ",
)
// offerCell escapes an admin-entered title for safe, literal rendering in a markdown table cell of
// the public offer (see [offerCellReplacer]).
func offerCell(s string) string {
s = strings.ReplaceAll(s, "\n", " ")
return strings.ReplaceAll(s, "|", "\\|")
return offerCellReplacer.Replace(s)
}
+24
View File
@@ -60,6 +60,30 @@ func TestProjectOfferPricingMissingRailAndEscaping(t *testing.T) {
}
}
// TestProjectOfferPricingEscapesHTMLAndLinks checks an admin title carrying HTML or a markdown link
// is neutralised so it cannot inject markup into the public offer: the tag becomes entities and the
// link brackets are escaped (so no "javascript:" anchor forms). The raw metacharacters must not
// survive into the projected markdown.
func TestProjectOfferPricingEscapesHTMLAndLinks(t *testing.T) {
entries := []catalogEntry{{
id: uuid.New(),
title: `<script>alert(1)</script> [x](javascript:alert(2)) & "q"`,
atoms: []atomQty{{atomType: "hints", quantity: 1}},
prices: []priceRow{{method: "", currency: CurrencyChip, amount: 5}},
}}
md := projectOfferPricing(entries)
for _, bad := range []string{"<script>", "</script>", "[x]", `& "q"`} {
if strings.Contains(md, bad) {
t.Errorf("unescaped %q survived into the projection:\n%s", bad, md)
}
}
for _, want := range []string{"&lt;script&gt;", `\[x\]`, "&amp;", "&quot;q&quot;"} {
if !strings.Contains(md, want) {
t.Errorf("want escaped %q in:\n%s", want, md)
}
}
}
// TestProjectOfferPricingEmpty checks an empty catalog projects to the empty string (no stray table
// headers), so the offer's pricing marker is replaced with nothing.
func TestProjectOfferPricingEmpty(t *testing.T) {