Sanitize Copy keywords clipboard against spreadsheet formula injection (#348)
The Striking Distance 'Copy keywords' action wrote raw selected GSC query strings to the clipboard, bypassing the formula-injection sanitizer used by the CSV and Sheets export paths. GSC query strings are untrusted and can begin with =, +, -, @, tab, CR, or LF; pasting them into Sheets/Excel could execute them as formulas. Route the copy path through normalizeExportValue (same OWASP-recommended guard as CSV/Sheets exports) so dangerous leading characters are prefixed with a single quote.
This commit is contained in:
parent
7c64cd3c5c
commit
9b6c5640cd
@ -21,7 +21,12 @@ import {
|
|||||||
type Report,
|
type Report,
|
||||||
type SearchPerformanceTableRow,
|
type SearchPerformanceTableRow,
|
||||||
} from "@/client/features/search-performance/SearchPerformanceColumns";
|
} from "@/client/features/search-performance/SearchPerformanceColumns";
|
||||||
import { buildCsv, downloadCsv, type CsvValue } from "@/client/lib/csv";
|
import {
|
||||||
|
buildCsv,
|
||||||
|
downloadCsv,
|
||||||
|
normalizeExportValue,
|
||||||
|
type CsvValue,
|
||||||
|
} from "@/client/lib/csv";
|
||||||
import { getStandardErrorMessage } from "@/client/lib/error-messages";
|
import { getStandardErrorMessage } from "@/client/lib/error-messages";
|
||||||
import { exportTableToSheets } from "@/client/lib/exportToSheets";
|
import { exportTableToSheets } from "@/client/lib/exportToSheets";
|
||||||
import { captureClientEvent } from "@/client/lib/posthog";
|
import { captureClientEvent } from "@/client/lib/posthog";
|
||||||
@ -277,7 +282,12 @@ export function StrikingDistanceTable({
|
|||||||
|
|
||||||
const copyKeywords = async () => {
|
const copyKeywords = async () => {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(selectedQueries.join("\n"));
|
// Sanitize against spreadsheet formula injection: GSC query strings are
|
||||||
|
// untrusted and may begin with =, +, -, @, etc. See @/client/lib/csv.
|
||||||
|
const text = selectedQueries
|
||||||
|
.map((query) => normalizeExportValue(query))
|
||||||
|
.join("\n");
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
toast.success(
|
toast.success(
|
||||||
`Copied ${selectedQueries.length} ${selectedQueries.length === 1 ? "keyword" : "keywords"}`,
|
`Copied ${selectedQueries.length} ${selectedQueries.length === 1 ? "keyword" : "keywords"}`,
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user