Second hardening pass: atomic data writes, bible-passage limits, webhook SSRF guard, note sanitizing, CI gates; v2.10.1
Publish Container / build (push) Successful in 15m46s
Publish Container / build (push) Successful in 15m46s
- Atomic temp-file + rename for all JSON data writes (no truncated files on crash) - /api/bible-passage: rate limit, range caps (60 verses), bounded cache - Admin webhooks: https-only, block private/internal/Tailscale addresses (checked at registration and delivery), no redirects - Resend webhook token compare is timing-safe - DOMPurify on rendered study notes and broadcast preview - CI runs lint + tests before build - Permissions-Policy and HSTS (production) headers; JSON error handler for /api - Tests for URL safety and atomic writes Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a796964b9
commit
2a64f29a00
18 files changed
+209
-17
No files matched your search
+2
-1
@@ -1,3 +1,4 @@
|
||||
import DOMPurify from 'dompurify'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'
|
||||
import { arrayMove, SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable'
|
||||
@@ -6868,7 +6869,7 @@ const [dashboardNow, setDashboardNow] = useState(() => new Date())
|
||||
<details style={{ marginBottom: '1rem' }}>
|
||||
<summary style={{ cursor: 'pointer', fontSize: '0.85rem', opacity: 0.7 }}>Preview rendered HTML</summary>
|
||||
<div style={{ marginTop: '0.75rem', padding: '1rem', background: 'var(--admin-card-bg)', borderRadius: '6px', border: '1px solid var(--admin-border)', fontSize: '0.9rem' }}
|
||||
dangerouslySetInnerHTML={{ __html: broadcastBody.replace(/\{\{name\}\}/g, 'Nate (preview)') }} />
|
||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(broadcastBody.replace(/\{\{name\}\}/g, 'Nate (preview)')) }} />
|
||||
</details>
|
||||
)}
|
||||
{bcAudience?.segmentConfigured === false && <p className="admin-save-error">RESEND_SEGMENT_ID isn’t set on the server, so there’s no Resend segment to send to.</p>}
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useMemo, useRef, useState, useCallback } from 'react'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
import type { SiteContent, StudyProgram, StudySection } from './content'
|
||||
import { DEFAULT_COLOSSIANS_STUDY_SECTIONS } from './legacyColossiansData'
|
||||
@@ -2791,7 +2792,7 @@ export function ColossiansStudyNotesPage({ content }: Props) {
|
||||
html?.trim() ? (
|
||||
<div key={key} style={{ marginBottom: '1.25rem' }}>
|
||||
<p className="study-module-focus" style={{ marginBottom: '0.5rem' }}>{ANCHOR_LABELS[key] ?? key}</p>
|
||||
<div className="study-note-rich-content" dangerouslySetInnerHTML={{ __html: html }} />
|
||||
<div className="study-note-rich-content" dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }} />
|
||||
</div>
|
||||
) : null
|
||||
))
|
||||
|
||||
Reference in new issue
Block a user