Skip to main content

max / makeover

Harden server across 5 audit axes (ultra-fuzz Run #19 remediation) Drive every audit axis to A. Highlights: UX/theming: fix a real stored-XSS in custom pages — lightningcss does not escape `<`, so `content:"</style><script>"` reached the page; escape `<` to `\3c ` in the sanitized sheet. Drop non-hex base intents in theme-common. Security: replace the replayable HMAC password-reset link with a single-use DB token (migration 140), mirroring login_tokens; derive lockout `just_locked` from a CTE so re-locks notify; add an `aud` claim to SyncKit JWTs. Performance: contain CPU-layer panics so a crafted upload can't kill the scan pool (fail closed to HeldForReview); route >8 GB uploads to review instead of stranding them Pending; unify spool tempfile cleanup; background the admin shutdown fan-out; continuously delete expired tower_sessions rows; rate-limit the /health endpoints; prune password-reset tokens. Payments: resolve the recurring epoch-period bug structurally — the five subscription/billing writers now take the raw Stripe period and own the end>0 filter + conversion, so a handler cannot construct an epoch DateTime. Move webhook dedup to after successful processing (v1 and v2) so a crash can't strand an event. Ticket checkout amount mismatches; pass base price to promos. Storage: make custom-page publish transactional; mark draft previews no-store; correct the presign doc and clamp presign expiry; bind the draft-cleanup interval; add a (item_id, created_at) version index (migration 141). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-13 20:36 UTC
Commit: e023d979db624882d970a440a4734d11100e8344
Parent: dc7a253
1 file changed, +25 insertions, -3 deletions
M src/lib.rs +25 -3
@@ -258,10 +258,14 @@ impl SemanticTokens {
258 258 pub fn resolve(theme: &ThemeColors) -> SemanticTokens {
259 259 let mut intents: BTreeMap<String, String> = BTreeMap::new();
260 260
261 - // 1. Base intents (authored).
261 + // 1. Base intents (authored). Copy only values that parse as a hex color and
262 + // re-emit them in canonical `#rrggbb` form, so an authored value can never
263 + // carry arbitrary bytes into the emitted CSS (the resolved tokens are inlined
264 + // raw into a `<style>` block by the web server). A malformed value is skipped,
265 + // mirroring the skip-missing behavior for absent intents.
262 266 for (src, token) in BASE_INTENTS {
263 - if let Some(v) = theme.colors.get(*src) {
264 - intents.insert((*token).to_string(), v.clone());
267 + if let Some(rgb) = theme.colors.get(*src).and_then(|v| Rgb::from_hex(v)) {
268 + intents.insert((*token).to_string(), rgb.to_hex());
265 269 }
266 270 }
267 271
@@ -811,6 +815,24 @@ six = "#88c0d0"
811 815 }
812 816
813 817 #[test]
818 + fn resolve_drops_non_hex_base_intent() {
819 + // A base intent that isn't a hex color must never reach the resolved
820 + // token set (it would otherwise be inlined verbatim into a <style>
821 + // block). Skipped like a missing intent; valid siblings survive.
822 + let theme = parse_theme_str(
823 + "x",
824 + "[surface]\npage = \"</style><script>alert(1)</script>\"\n[content]\nprimary = \"#111111\"\n",
825 + false,
826 + )
827 + .unwrap();
828 + let t = resolve(&theme);
829 + assert!(t.hex("surface-page").is_none(), "non-hex base intent leaked");
830 + assert_eq!(t.hex("content").unwrap(), "#111111");
831 + // The injected markup appears in no resolved value.
832 + assert!(!t.intents.values().any(|v| v.contains('<')));
833 + }
834 +
835 + #[test]
814 836 fn resolve_skips_derived_when_source_missing() {
815 837 // No [action] => no action-derived tokens.
816 838 let theme = parse_theme_str(