- Content Security Policy via <meta> tag (blocks exfiltration to foreign domains)
- Subresource Integrity on all static and dynamically loaded scripts
- Nginx security headers snippet (HSTS, CSP, frame-ancestors on all responses)
- Auto-minify and SRI hash update in deploy.sh (prevents stale hashes)
API / Security:
- Add api/_helpers.php: shared send_security_headers(), verify_origin(),
get_hmac_secret(), check_rate_limit(), read_json_locked(), write_json_locked()
- shorten.php: remove Access-Control-Allow-Origin:*, restrict to same-origin,
rate-limit 20 req/h per IP, atomic JSON read+lock, HMAC secret from file
- verify.php: rate-limit GET (30/min) and POST (10/h) per IP, atomic lock,
prevent overwriting existing proofs, origin check on POST
- node.php: fix rate limit from 1000 to 60 req/min, add security headers,
origin check
- check-short.php: add security headers, re-derive signature server-side
- s.php: use file-based HMAC secret via get_hmac_secret(), hash_equals()
for timing-safe comparison
Service Worker:
- sw.js: navigation requests (mode=navigate) never served from cache;
network-first with offline fallback to prevent stale invoice state
Documentation (honest claims):
- README: tagline "No backend" -> "No tracking"; new Architecture table
listing exactly what server sees for each feature; Security Model section
- index.html: meta description and footer updated from "No Backend" to
"Minimal Backend"
- i18n.js footer: already updated in previous commit
- Implement HMAC-SHA256 signatures on short URLs to detect server-side tampering
- Add client-side signature verification with hostname-derived secret
- New API endpoint: /api/check-short.php for integrity verification
- Update verify.php with privacy notice (addresses not stored)
- Update README to clarify minimal backend requirement (short URLs, rate caching, proof storage)
- Add toast warning when signature mismatch detected
- Support both old and new format in s.php for backward compatibility
- Update all i18n translations (EN, DE, FR, IT, ES, PT, RU)
Addresses security concern: Server compromise could previously result in address
substitution for short-linked invoices. Now client-side verification detects tampering.
- Add GBP, JPY, RUB, BRL currencies
- Auto-detect currency from browser locale (de-CH→CHF, ru→RUB, etc.)
- USD as default fallback
- Language toggle: globe icon only (compact on mobile), full names in dropdown
- Countdown text updates on language switch
- CoinGecko proxy supports dynamic currency list
- Full translations for all UI strings in 7 languages
- Language picker shows native names (Français, Italiano, Español, Português, Русский)
- Auto-detection via navigator.languages
- Cyrillic font subset for Russian (Inter, 19KB)
- Footer shared across all languages (untranslated links)
- Tor onion: mc6wfeaqc7oijgdcudrr5zsotmwok3jzk3tu2uezzyjisn7nzzjjizyd.onion
- Onion link in footer and README
- PDF "BEZAHLT" block shows TX hash + date in second line
- Address placeholder 8... (encourages subaddress usage)
- Complete English README with feature overview, tech stack, project structure
- Self-hosting instructions and security notes
- Accent color contrast fix (--accent-text for text on dark backgrounds)
- CoinGecko rates proxy: User-Agent header + 2min server-side cache
- Font preload eliminates layout shift (CLS 0)
- Dual accent colors: --accent for filled buttons, --accent-text for text on dark bg
- All WCAG AA contrast ratios met (buttons, links, badges, countdown)
- Language picker: position absolute instead of fixed
- CoinGecko rates proxied with 2min server-side cache (no CORS, no rate limit)
- English as default inline text (no empty→text shift)
- All visible text pre-rendered in HTML (no empty→text shift)
- English as default language (i18n fallback + HTML inline text)
- German auto-detected via navigator.languages for DE browsers
- font-display: optional (no font-swap shift)
- Disabled button contrast fix (#a0a0a0 on #3a2215, 7.2:1)
- CoinGecko rates proxied via /api/rates.php with User-Agent
- Self-host Inter and JetBrains Mono (woff2, 69KB total)
- Remove Google Fonts dependency entirely (no external requests)
- Service Worker pre-caches font files
- Eliminate font-swap layout shifts
- WCAG contrast fix: disabled button uses solid color instead of opacity
- Footer link always underlined for distinguishability
- Translated aria-labels via data-i18n-aria
- Dimmed QR with "Bezahlt" stamp on paid invoices
- Hide wallet/address buttons when invoice is paid
- Payment summary card with prominent amount display
- "Bezahlt" stamp over dimmed QR code with TX details below
- Hide wallet/address buttons when paid, show only PDF
- URI box removed (was technical noise)
- Smart countdown: "29 Tage, 23 Std." instead of ticking seconds
- Dynamic page title for shared invoices
- Font fallbacks with size-adjust to prevent layout shifts
- Async Google Fonts loading, proper preconnect hints
- Deferred script loading (defer attribute)
- Minified JS (app.min.js, i18n.min.js)
- WCAG contrast fixes for badges and disabled button
- Footer link always underlined for a11y
- Translated aria-labels via data-i18n-aria
- i18n onChange callback for dynamic content updates
- Result card fade-in animation, responsive QR on mobile
- Official Monero coin logo as SVG favicon
- Dynamic green dot badge on favicon when invoice is paid
- Paid status shown both in page content and browser tab
The proof was being stored under a newly generated short URL code
instead of the original invoice code. Now tracks invoiceCode from
the hash parameter (c=CODE) or from the first shortenUrl call.