Export includes soft-deleted entries; CSV cells not guarded against formula injection #94
Labels
No labels
bug
duplicate
enhancement
future
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/TeaLeaves#94
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Low · Confidence: High · Effort: XS · Category: security
Evidence
api/src/routes/export.ts:20-49(fetchAllEntries) and53-77(fetchMotifWithDetail) omitdeleted_at IS NULL.api/src/routes/export.ts:104-111-escapeCSVdoesn't neutralize leading= + - @.Problem
Owner exports include trashed rows, and CSV cells beginning with a formula character can execute if opened in a spreadsheet.
Impact
Self-targeted (owner exports own data). Low, but the soft-delete leak is a correctness inconsistency and CSV injection is a trivially-avoided footgun if the export is shared.
Fix
Add
deleted_at IS NULLto export queries; prefix formula-leading CSV cells with a single quote.Acceptance criteria
Filed from the 2026-07-15 codebase audit. Full report:
docs/.internal/report-2026-07-15.md(gitignored).Fixed in
763aa0b(v7.2.0 wave 1).Soft-delete — added
deleted_at IS NULLto all four export queries:fetchAllEntries, thefetchMotifWithDetailmotif lookup, its entries query, andfetchAllMotifs.One extra leak the issue didn't name:
fetchMotifWithDetail's connections query projectsLEFT(se.content, 120)/LEFT(te.content, 120)and matches onsource_entry_id = ANY(...) OR target_entry_id = ANY(...). A connection from a live entry to a trashed one therefore embedded the trashed entry's content in the export, even after the entries query was filtered. AddedAND se.deleted_at IS NULL AND te.deleted_at IS NULL.CSV formula injection —
escapeCSVnow prefixes a single quote when a cell starts with= + - @or a leading tab/CR (spreadsheets strip leading whitespace before parsing, so\t=cmd|…is the same attack). Applied before the quoting branch so the quotes wrap the escaped value:Acceptance criteria:
= + - @are prefixed so spreadsheets don't evaluate them.Known trade-off: this also prefixes genuine negative numbers (
-5→'-5). That's the standard OWASP guidance and the current CSV columns are text/timestamps, so it's not worth a type-aware exception here.CI run 191 green (241/241).