- Sanitize event-sourced URLs before CSS url() interpolation in
ProfileCard banner and letter stationery background (closes H-1, H-2)
- Sanitize event-sourced font families at the parse layer and in letter
card/detail consumers that bypass resolveStationery (closes M-6)
- Export sanitizeCssString for broader reuse
- Route NWC wallet connection URIs and active pointer through a new
useSecureLocalStorage hook, storing in iOS Keychain / Android KeyStore
on native (closes M-1)
- Add removeItem to secureStorage
- Add Android backup/data-extraction rules that exclude WebView storage
and Capacitor secure-storage SharedPreferences so wallet credentials
don't leak via Google Auto Backup (closes M-5)
- Document that GOOGLE_PLAY_SERVICE_ACCOUNT_JSON must be base64-encoded
to match what the CI job expects (closes M-2)
Nostr events are untrusted user input. Any URL extracted from event tags
or metadata must be validated before use in any context — not just
navigable hrefs, but also img src, CSS url(), and style attributes.
Changes:
- Theme events (kind 16767/36767): validate background and font URLs
through sanitizeUrl() at parse time in themeEvent.ts
- Badge definitions (kind 30009): validate image and thumb URLs through
sanitizeUrl() at parse time in parseBadgeDefinition.ts
- Font family names: sanitize with an allowlist regex before
interpolation into CSS declarations in fontLoader.ts
- Profile fields: replace weak startsWith('http://') checks with
sanitizeUrl() in ProfileRightSidebar and ProfilePage
- Community descriptions: validate extracted URLs through sanitizeUrl()
in CommunityContent.tsx
- AGENTS.md: mandate unconditional URL sanitization for all
event-sourced URLs regardless of rendering context, document CSS
injection prevention guidelines
Add a shared sanitizeUrl() utility that validates URLs are well-formed
https: before they reach href attributes, window.open(), or openUrl().
Apply sanitization across all components that render untrusted URLs:
- CalendarEventDetailPage: r-tag links
- ZapstoreAppContent: url and repository tags
- ZapstoreReleaseContent: asset url tags passed to openUrl()
- AppHandlerContent: web handler tags and metadata.website
- NsiteCard: source tag
- GitRepoCard: web tag URLs passed to openUrl()
- FileMetadataContent: url tag used in download href
- ProfilePage: metadata.website (tighten weak startsWith check)
- useUserStatus: r-tag URL
Document sanitizeUrl usage in AGENTS.md for future agent use.
Extend useNostrPublish with an optional `prev` property on the event
template. For replaceable and addressable kinds, the hook automatically
manages published_at:
- First publish (no prev): set published_at equal to created_at
- Update (prev provided): preserve published_at from the old event
- Old event lacks published_at: don't fabricate one
- Caller already set published_at in tags: leave it alone
Callers pass `prev` when they have the old event from fetchFreshEvent,
giving the hook everything it needs without extra network requests.
Updated all 11 call sites that publish replaceable or addressable events.
Documents the prev convention in AGENTS.md.
Use nsyte CLI with NIP-46 nbunksec bunker credential to deploy
the web app to nsite on every default branch push. Downloads the
nsyte binary, builds the Vite app, and uploads to configured
Blossom servers and Nostr relays with SPA fallback routing.
Rename the checklist item from 'Inline embeds / quote posts' to
'Embedded note cards' with explicit file paths, explain that kinds
with tag-based media may need attachment indicator updates, and add
a note distinguishing EmbeddedNote components from the NoteCard
compact prop to prevent confusion.
The <a download> and <a target="_blank"> patterns don't work in
WKWebView. Add downloadTextFile() and openUrl() utilities in
src/lib/downloadFile.ts that use @capacitor/filesystem and
@capacitor/share on native platforms, falling back to standard
browser behavior on web.
Update all call sites: onboarding key download (InitialSyncGate,
SignupDialog), image lightbox buttons (ImageGallery, ProfilePage).
Document Capacitor compatibility constraints in AGENTS.md.
Quote posts and hover previews should always be compact (author + title/content).
Rendering a full NoteCard with action headers, buttons, and nested card components
inside a quote post or hover card is too much UI for an inline context.
- Remove NOTECARD_KINDS from EmbeddedNote.tsx and EmbeddedNaddr.tsx
- Remove NoteCard imports from both embed components
- Add tag-based metadata fallback (title/name/d, summary/description) to
EmbeddedNoteCard for non-text kinds with empty content
- Update AGENTS.md checklist to reflect that embeds no longer need per-kind registration