feat(document): remove the visible Originaltext line from DocumentDate

DocumentDate rendered an "Originaltext: <raw>" secondary line for
UNKNOWN/SEASON/APPROX dates, gated by a showRaw prop. Drop the visible
line, the showRaw prop, the showRawLine derived, and the now-unused
date_original_label message import. The raw prop stays — it still feeds
the SEASON word in formatDocumentDate, which only ever maps a fixed
German season token (never emits raw text), so no XSS surface remains.

Update both DocumentRow call sites to drop the now-gone showRaw={false}
and the comment that justified it. Remove the two DocumentDate tests
that asserted on the deleted DOM sink (the UNKNOWN secondary line and
its XSS-escaping); the DAY/MONTH coverage stays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-01 20:06:12 +02:00
parent bf90427bfa
commit 4944918692
3 changed files with 2 additions and 39 deletions

View File

@@ -1,30 +1,20 @@
<script lang="ts"> <script lang="ts">
import { formatDocumentDate, type DatePrecision } from '$lib/shared/utils/documentDate'; import { formatDocumentDate, type DatePrecision } from '$lib/shared/utils/documentDate';
import { getLocale } from '$lib/paraglide/runtime.js'; import { getLocale } from '$lib/paraglide/runtime.js';
import { m } from '$lib/paraglide/messages.js';
type Props = { type Props = {
iso?: string | null; iso?: string | null;
precision?: DatePrecision | null; precision?: DatePrecision | null;
end?: string | null; end?: string | null;
/** Verbatim import cell — used only to derive the SEASON word, never displayed. */
raw?: string | null; raw?: string | null;
/** Show the verbatim "Originaltext: …" secondary line when raw is present. */
showRaw?: boolean;
}; };
let { iso = null, precision = null, end = null, raw = null, showRaw = true }: Props = $props(); let { iso = null, precision = null, end = null, raw = null }: Props = $props();
const effectivePrecision = $derived<DatePrecision>(precision ?? (iso ? 'DAY' : 'UNKNOWN')); const effectivePrecision = $derived<DatePrecision>(precision ?? (iso ? 'DAY' : 'UNKNOWN'));
const label = $derived(formatDocumentDate(iso, effectivePrecision, end, raw, getLocale())); const label = $derived(formatDocumentDate(iso, effectivePrecision, end, raw, getLocale()));
const isUnknown = $derived(effectivePrecision === 'UNKNOWN' || !iso); const isUnknown = $derived(effectivePrecision === 'UNKNOWN' || !iso);
// Only show the verbatim raw line where it adds information the label can't: the
// season word's source, or the original cell behind an "unknown"/approx date.
const showRawLine = $derived(
showRaw &&
!!raw &&
raw.trim().length > 0 &&
(isUnknown || effectivePrecision === 'SEASON' || effectivePrecision === 'APPROX')
);
</script> </script>
<span class="inline-flex flex-col"> <span class="inline-flex flex-col">
@@ -61,10 +51,4 @@ const showRawLine = $derived(
{:else} {:else}
<span>{label}</span> <span>{label}</span>
{/if} {/if}
{#if showRawLine}
<!-- Visible secondary line (WCAG 1.4.13 — not tooltip-only). raw is untrusted
verbatim spreadsheet text; rendered via default Svelte interpolation, which
HTML-escapes it (never {@html}; CWE-79). -->
<span class="font-sans text-xs text-ink-2">{m.date_original_label()} {raw}</span>
{/if}
</span> </span>

View File

@@ -17,19 +17,4 @@ describe('DocumentDate', () => {
render(DocumentDate, { props: { iso: '1916-06-01', precision: 'MONTH', raw: 'Juni 1916' } }); render(DocumentDate, { props: { iso: '1916-06-01', precision: 'MONTH', raw: 'Juni 1916' } });
await expect.element(page.getByText('Juni 1916')).toBeInTheDocument(); await expect.element(page.getByText('Juni 1916')).toBeInTheDocument();
}); });
it('shows the verbatim raw cell as a visible secondary line for UNKNOWN (not tooltip-only)', async () => {
render(DocumentDate, { props: { iso: null, precision: 'UNKNOWN', raw: 'Sommer?' } });
// Real, visible text — not hidden behind a title attribute.
await expect.element(page.getByText('Datum unbekannt')).toBeInTheDocument();
await expect.element(page.getByText(/Sommer\?/)).toBeVisible();
});
it('renders a malicious raw value as inert escaped text (no element injected)', async () => {
const malicious = '<img src=x onerror="alert(1)">';
render(DocumentDate, { props: { iso: null, precision: 'UNKNOWN', raw: malicious } });
// The payload appears as literal text, and no <img> is created in the DOM.
await expect.element(page.getByText(/<img/)).toBeInTheDocument();
expect(document.querySelector('img')).toBeNull();
});
}); });

View File

@@ -164,15 +164,10 @@ function safeTagColor(color: string | null | undefined): string {
<!-- Mobile-only metadata --> <!-- Mobile-only metadata -->
<div class="mt-3 grid grid-cols-2 gap-x-4 gap-y-1 font-sans text-xs text-ink-2 sm:hidden"> <div class="mt-3 grid grid-cols-2 gap-x-4 gap-y-1 font-sans text-xs text-ink-2 sm:hidden">
<div> <div>
<!-- Product decision (#666): raw provenance (meta_date_raw) is shown on the
document DETAIL page, never in list/search rows — list rows surface only the
honest label to keep scan-rows compact. showRaw={false} enforces this; the
DocumentListItem payload also intentionally omits metaDateRaw. -->
<DocumentDate <DocumentDate
iso={doc.documentDate} iso={doc.documentDate}
precision={doc.metaDatePrecision} precision={doc.metaDatePrecision}
end={doc.metaDateEnd} end={doc.metaDateEnd}
showRaw={false}
/> />
</div> </div>
<div class="flex items-start gap-2"> <div class="flex items-start gap-2">
@@ -194,7 +189,6 @@ function safeTagColor(color: string | null | undefined): string {
iso={doc.documentDate} iso={doc.documentDate}
precision={doc.metaDatePrecision} precision={doc.metaDatePrecision}
end={doc.metaDateEnd} end={doc.metaDateEnd}
showRaw={false}
/> />
</div> </div>
<div> <div>