From 8be83bdd5824a442fbbca83fbac29052534f8041 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 14:50:58 +0000 Subject: [PATCH 01/13] Initial plan From 7858761a839206524111e7358e5e677e2f75cbcc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:12:29 +0000 Subject: [PATCH 02/13] Fix JavaScript test failures - copy missing .cjs files and add XML/command sanitization Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/collect_ndjson_output.cjs | 698 ++++++++++++++++++++++ pkg/workflow/js/compute_text.cjs | 114 ++++ pkg/workflow/js/lib/sanitize.cjs | 44 +- pkg/workflow/js/sanitize_output.cjs | 37 ++ pkg/workflow/js/test.png | 1 + 5 files changed, 891 insertions(+), 3 deletions(-) create mode 100644 pkg/workflow/js/collect_ndjson_output.cjs create mode 100644 pkg/workflow/js/compute_text.cjs create mode 100644 pkg/workflow/js/sanitize_output.cjs create mode 100644 pkg/workflow/js/test.png diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs new file mode 100644 index 00000000000..485403f7d32 --- /dev/null +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -0,0 +1,698 @@ +// @ts-check +/// + +async function main() { + const fs = require("fs"); + const { sanitizeContent } = require("./lib/sanitize.cjs"); + const maxBodyLength = 65000; + function getMaxAllowedForType(itemType, config) { + const itemConfig = config?.[itemType]; + if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { + return itemConfig.max; + } + switch (itemType) { + case "create_issue": + return 1; + case "create_agent_task": + return 1; + case "add_comment": + return 1; + case "create_pull_request": + return 1; + case "create_pull_request_review_comment": + return 1; + case "add_labels": + return 5; + case "update_issue": + return 1; + case "push_to_pull_request_branch": + return 1; + case "create_discussion": + return 1; + case "missing_tool": + return 20; + case "create_code_scanning_alert": + return 40; + case "upload_asset": + return 10; + default: + return 1; + } + } + function getMinRequiredForType(itemType, config) { + const itemConfig = config?.[itemType]; + if (itemConfig && typeof itemConfig === "object" && "min" in itemConfig && itemConfig.min) { + return itemConfig.min; + } + return 0; + } + function repairJson(jsonStr) { + let repaired = jsonStr.trim(); + const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; + repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { + const c = ch.charCodeAt(0); + return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); + }); + repaired = repaired.replace(/'/g, '"'); + repaired = repaired.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":'); + repaired = repaired.replace(/"([^"\\]*)"/g, (match, content) => { + if (content.includes("\n") || content.includes("\r") || content.includes("\t")) { + const escaped = content.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"); + return `"${escaped}"`; + } + return match; + }); + repaired = repaired.replace(/"([^"]*)"([^":,}\]]*)"([^"]*)"(\s*[,:}\]])/g, (match, p1, p2, p3, p4) => `"${p1}\\"${p2}\\"${p3}"${p4}`); + repaired = repaired.replace(/(\[\s*(?:"[^"]*"(?:\s*,\s*"[^"]*")*\s*),?)\s*}/g, "$1]"); + const openBraces = (repaired.match(/\{/g) || []).length; + const closeBraces = (repaired.match(/\}/g) || []).length; + if (openBraces > closeBraces) { + repaired += "}".repeat(openBraces - closeBraces); + } else if (closeBraces > openBraces) { + repaired = "{".repeat(closeBraces - openBraces) + repaired; + } + const openBrackets = (repaired.match(/\[/g) || []).length; + const closeBrackets = (repaired.match(/\]/g) || []).length; + if (openBrackets > closeBrackets) { + repaired += "]".repeat(openBrackets - closeBrackets); + } else if (closeBrackets > openBrackets) { + repaired = "[".repeat(closeBrackets - openBrackets) + repaired; + } + repaired = repaired.replace(/,(\s*[}\]])/g, "$1"); + return repaired; + } + function validatePositiveInteger(value, fieldName, lineNum) { + if (value === undefined || value === null) { + if (fieldName.includes("create_code_scanning_alert 'line'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_code_scanning_alert requires a 'line' field (number or string)`, + }; + } + if (fieldName.includes("create_pull_request_review_comment 'line'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_pull_request_review_comment requires a 'line' number`, + }; + } + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} is required`, + }; + } + if (typeof value !== "number" && typeof value !== "string") { + if (fieldName.includes("create_code_scanning_alert 'line'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_code_scanning_alert requires a 'line' field (number or string)`, + }; + } + if (fieldName.includes("create_pull_request_review_comment 'line'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_pull_request_review_comment requires a 'line' number or string field`, + }; + } + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be a number or string`, + }; + } + const parsed = typeof value === "string" ? parseInt(value, 10) : value; + if (isNaN(parsed) || parsed <= 0 || !Number.isInteger(parsed)) { + if (fieldName.includes("create_code_scanning_alert 'line'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_code_scanning_alert 'line' must be a valid positive integer (got: ${value})`, + }; + } + if (fieldName.includes("create_pull_request_review_comment 'line'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_pull_request_review_comment 'line' must be a positive integer`, + }; + } + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be a positive integer (got: ${value})`, + }; + } + return { isValid: true, normalizedValue: parsed }; + } + function validateOptionalPositiveInteger(value, fieldName, lineNum) { + if (value === undefined) { + return { isValid: true }; + } + if (typeof value !== "number" && typeof value !== "string") { + if (fieldName.includes("create_pull_request_review_comment 'start_line'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_pull_request_review_comment 'start_line' must be a number or string`, + }; + } + if (fieldName.includes("create_code_scanning_alert 'column'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_code_scanning_alert 'column' must be a number or string`, + }; + } + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be a number or string`, + }; + } + const parsed = typeof value === "string" ? parseInt(value, 10) : value; + if (isNaN(parsed) || parsed <= 0 || !Number.isInteger(parsed)) { + if (fieldName.includes("create_pull_request_review_comment 'start_line'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_pull_request_review_comment 'start_line' must be a positive integer`, + }; + } + if (fieldName.includes("create_code_scanning_alert 'column'")) { + return { + isValid: false, + error: `Line ${lineNum}: create_code_scanning_alert 'column' must be a valid positive integer (got: ${value})`, + }; + } + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be a positive integer (got: ${value})`, + }; + } + return { isValid: true, normalizedValue: parsed }; + } + function validateIssueOrPRNumber(value, fieldName, lineNum) { + if (value === undefined) { + return { isValid: true }; + } + if (typeof value !== "number" && typeof value !== "string") { + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be a number or string`, + }; + } + return { isValid: true }; + } + function validateFieldWithInputSchema(value, fieldName, inputSchema, lineNum) { + if (inputSchema.required && (value === undefined || value === null)) { + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} is required`, + }; + } + if (value === undefined || value === null) { + return { + isValid: true, + normalizedValue: inputSchema.default || undefined, + }; + } + const inputType = inputSchema.type || "string"; + let normalizedValue = value; + switch (inputType) { + case "string": + if (typeof value !== "string") { + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be a string`, + }; + } + normalizedValue = sanitizeContent(value); + break; + case "boolean": + if (typeof value !== "boolean") { + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be a boolean`, + }; + } + break; + case "number": + if (typeof value !== "number") { + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be a number`, + }; + } + break; + case "choice": + if (typeof value !== "string") { + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be a string for choice type`, + }; + } + if (inputSchema.options && !inputSchema.options.includes(value)) { + return { + isValid: false, + error: `Line ${lineNum}: ${fieldName} must be one of: ${inputSchema.options.join(", ")}`, + }; + } + normalizedValue = sanitizeContent(value); + break; + default: + if (typeof value === "string") { + normalizedValue = sanitizeContent(value); + } + break; + } + return { + isValid: true, + normalizedValue, + }; + } + function validateItemWithSafeJobConfig(item, jobConfig, lineNum) { + const errors = []; + const normalizedItem = { ...item }; + if (!jobConfig.inputs) { + return { + isValid: true, + errors: [], + normalizedItem: item, + }; + } + for (const [fieldName, inputSchema] of Object.entries(jobConfig.inputs)) { + const fieldValue = item[fieldName]; + const validation = validateFieldWithInputSchema(fieldValue, fieldName, inputSchema, lineNum); + if (!validation.isValid && validation.error) { + errors.push(validation.error); + } else if (validation.normalizedValue !== undefined) { + normalizedItem[fieldName] = validation.normalizedValue; + } + } + return { + isValid: errors.length === 0, + errors, + normalizedItem, + }; + } + function parseJsonWithRepair(jsonStr) { + try { + return JSON.parse(jsonStr); + } catch (originalError) { + try { + const repairedJson = repairJson(jsonStr); + return JSON.parse(repairedJson); + } catch (repairError) { + core.info(`invalid input json: ${jsonStr}`); + const originalMsg = originalError instanceof Error ? originalError.message : String(originalError); + const repairMsg = repairError instanceof Error ? repairError.message : String(repairError); + throw new Error(`JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}`); + } + } + } + const outputFile = process.env.GH_AW_SAFE_OUTPUTS; + const safeOutputsConfig = process.env.GH_AW_SAFE_OUTPUTS_CONFIG; + if (!outputFile) { + core.info("GH_AW_SAFE_OUTPUTS not set, no output to collect"); + core.setOutput("output", ""); + return; + } + if (!fs.existsSync(outputFile)) { + core.info(`Output file does not exist: ${outputFile}`); + core.setOutput("output", ""); + return; + } + const outputContent = fs.readFileSync(outputFile, "utf8"); + if (outputContent.trim() === "") { + core.info("Output file is empty"); + } + core.info(`Raw output content length: ${outputContent.length}`); + let expectedOutputTypes = {}; + if (safeOutputsConfig) { + try { + const rawConfig = JSON.parse(safeOutputsConfig); + // Normalize all config keys to use underscores instead of dashes + expectedOutputTypes = Object.fromEntries(Object.entries(rawConfig).map(([key, value]) => [key.replace(/-/g, "_"), value])); + core.info(`Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}`); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); + } + } + const lines = outputContent.trim().split("\n"); + const parsedItems = []; + const errors = []; + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (line === "") continue; + try { + const item = parseJsonWithRepair(line); + if (item === undefined) { + errors.push(`Line ${i + 1}: Invalid JSON - JSON parsing failed`); + continue; + } + if (!item.type) { + errors.push(`Line ${i + 1}: Missing required 'type' field`); + continue; + } + // Normalize type to use underscores (convert any dashes to underscores for resilience) + const itemType = item.type.replace(/-/g, "_"); + // Update item.type to normalized value + item.type = itemType; + if (!expectedOutputTypes[itemType]) { + errors.push(`Line ${i + 1}: Unexpected output type '${itemType}'. Expected one of: ${Object.keys(expectedOutputTypes).join(", ")}`); + continue; + } + const typeCount = parsedItems.filter(existing => existing.type === itemType).length; + const maxAllowed = getMaxAllowedForType(itemType, expectedOutputTypes); + if (typeCount >= maxAllowed) { + errors.push(`Line ${i + 1}: Too many items of type '${itemType}'. Maximum allowed: ${maxAllowed}.`); + continue; + } + core.info(`Line ${i + 1}: type '${itemType}'`); + switch (itemType) { + case "create_issue": + if (!item.title || typeof item.title !== "string") { + errors.push(`Line ${i + 1}: create_issue requires a 'title' string field`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: create_issue requires a 'body' string field`); + continue; + } + item.title = sanitizeContent(item.title, 128); + item.body = sanitizeContent(item.body, maxBodyLength); + if (item.labels && Array.isArray(item.labels)) { + item.labels = item.labels.map(label => (typeof label === "string" ? sanitizeContent(label, 128) : label)); + } + // Validate parent field if provided + if (item.parent !== undefined) { + const parentValidation = validateIssueOrPRNumber(item.parent, "create_issue 'parent'", i + 1); + if (!parentValidation.isValid) { + if (parentValidation.error) errors.push(parentValidation.error); + continue; + } + } + break; + case "add_comment": + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: add_comment requires a 'body' string field`); + continue; + } + // Validate number + if (item.item_number !== undefined) { + const itemNumberValidation = validateIssueOrPRNumber(item.item_number, "add_comment 'item_number'", i + 1); + if (!itemNumberValidation.isValid) { + if (itemNumberValidation.error) errors.push(itemNumberValidation.error); + continue; + } + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; + case "create_pull_request": + if (!item.title || typeof item.title !== "string") { + errors.push(`Line ${i + 1}: create_pull_request requires a 'title' string field`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: create_pull_request requires a 'body' string field`); + continue; + } + if (!item.branch || typeof item.branch !== "string") { + errors.push(`Line ${i + 1}: create_pull_request requires a 'branch' string field`); + continue; + } + item.title = sanitizeContent(item.title, 128); + item.body = sanitizeContent(item.body, maxBodyLength); + item.branch = sanitizeContent(item.branch, 256); + if (item.labels && Array.isArray(item.labels)) { + item.labels = item.labels.map(label => (typeof label === "string" ? sanitizeContent(label, 128) : label)); + } + break; + case "add_labels": + if (!item.labels || !Array.isArray(item.labels)) { + errors.push(`Line ${i + 1}: add_labels requires a 'labels' array field`); + continue; + } + if (item.labels.some(label => typeof label !== "string")) { + errors.push(`Line ${i + 1}: add_labels labels array must contain only strings`); + continue; + } + const labelsItemNumberValidation = validateIssueOrPRNumber(item.item_number, "add_labels 'item_number'", i + 1); + if (!labelsItemNumberValidation.isValid) { + if (labelsItemNumberValidation.error) errors.push(labelsItemNumberValidation.error); + continue; + } + item.labels = item.labels.map(label => sanitizeContent(label, 128)); + break; + case "update_issue": + const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; + if (!hasValidField) { + errors.push(`Line ${i + 1}: update_issue requires at least one of: 'status', 'title', or 'body' fields`); + continue; + } + if (item.status !== undefined) { + if (typeof item.status !== "string" || (item.status !== "open" && item.status !== "closed")) { + errors.push(`Line ${i + 1}: update_issue 'status' must be 'open' or 'closed'`); + continue; + } + } + if (item.title !== undefined) { + if (typeof item.title !== "string") { + errors.push(`Line ${i + 1}: update_issue 'title' must be a string`); + continue; + } + item.title = sanitizeContent(item.title, 128); + } + if (item.body !== undefined) { + if (typeof item.body !== "string") { + errors.push(`Line ${i + 1}: update_issue 'body' must be a string`); + continue; + } + item.body = sanitizeContent(item.body, maxBodyLength); + } + const updateIssueNumValidation = validateIssueOrPRNumber(item.issue_number, "update_issue 'issue_number'", i + 1); + if (!updateIssueNumValidation.isValid) { + if (updateIssueNumValidation.error) errors.push(updateIssueNumValidation.error); + continue; + } + break; + case "push_to_pull_request_branch": + if (!item.branch || typeof item.branch !== "string") { + errors.push(`Line ${i + 1}: push_to_pull_request_branch requires a 'branch' string field`); + continue; + } + if (!item.message || typeof item.message !== "string") { + errors.push(`Line ${i + 1}: push_to_pull_request_branch requires a 'message' string field`); + continue; + } + item.branch = sanitizeContent(item.branch, 256); + item.message = sanitizeContent(item.message, maxBodyLength); + const pushPRNumValidation = validateIssueOrPRNumber( + item.pull_request_number, + "push_to_pull_request_branch 'pull_request_number'", + i + 1 + ); + if (!pushPRNumValidation.isValid) { + if (pushPRNumValidation.error) errors.push(pushPRNumValidation.error); + continue; + } + break; + case "create_pull_request_review_comment": + if (!item.path || typeof item.path !== "string") { + errors.push(`Line ${i + 1}: create_pull_request_review_comment requires a 'path' string field`); + continue; + } + const lineValidation = validatePositiveInteger(item.line, "create_pull_request_review_comment 'line'", i + 1); + if (!lineValidation.isValid) { + if (lineValidation.error) errors.push(lineValidation.error); + continue; + } + const lineNumber = lineValidation.normalizedValue; + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: create_pull_request_review_comment requires a 'body' string field`); + continue; + } + item.body = sanitizeContent(item.body, maxBodyLength); + const startLineValidation = validateOptionalPositiveInteger( + item.start_line, + "create_pull_request_review_comment 'start_line'", + i + 1 + ); + if (!startLineValidation.isValid) { + if (startLineValidation.error) errors.push(startLineValidation.error); + continue; + } + if ( + startLineValidation.normalizedValue !== undefined && + lineNumber !== undefined && + startLineValidation.normalizedValue > lineNumber + ) { + errors.push(`Line ${i + 1}: create_pull_request_review_comment 'start_line' must be less than or equal to 'line'`); + continue; + } + if (item.side !== undefined) { + if (typeof item.side !== "string" || (item.side !== "LEFT" && item.side !== "RIGHT")) { + errors.push(`Line ${i + 1}: create_pull_request_review_comment 'side' must be 'LEFT' or 'RIGHT'`); + continue; + } + } + break; + case "create_discussion": + if (!item.title || typeof item.title !== "string") { + errors.push(`Line ${i + 1}: create_discussion requires a 'title' string field`); + continue; + } + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: create_discussion requires a 'body' string field`); + continue; + } + if (item.category !== undefined) { + if (typeof item.category !== "string") { + errors.push(`Line ${i + 1}: create_discussion 'category' must be a string`); + continue; + } + item.category = sanitizeContent(item.category, 128); + } + item.title = sanitizeContent(item.title, 128); + item.body = sanitizeContent(item.body, maxBodyLength); + break; + case "create_agent_task": + if (!item.body || typeof item.body !== "string") { + errors.push(`Line ${i + 1}: create_agent_task requires a 'body' string field`); + continue; + } + item.body = sanitizeContent(item.body, maxBodyLength); + break; + case "missing_tool": + if (!item.tool || typeof item.tool !== "string") { + errors.push(`Line ${i + 1}: missing_tool requires a 'tool' string field`); + continue; + } + if (!item.reason || typeof item.reason !== "string") { + errors.push(`Line ${i + 1}: missing_tool requires a 'reason' string field`); + continue; + } + item.tool = sanitizeContent(item.tool, 128); + item.reason = sanitizeContent(item.reason, 256); + if (item.alternatives !== undefined) { + if (typeof item.alternatives !== "string") { + errors.push(`Line ${i + 1}: missing_tool 'alternatives' must be a string`); + continue; + } + item.alternatives = sanitizeContent(item.alternatives, 512); + } + break; + case "upload_asset": + if (!item.path || typeof item.path !== "string") { + errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); + continue; + } + break; + case "create_code_scanning_alert": + if (!item.file || typeof item.file !== "string") { + errors.push(`Line ${i + 1}: create_code_scanning_alert requires a 'file' field (string)`); + continue; + } + const alertLineValidation = validatePositiveInteger(item.line, "create_code_scanning_alert 'line'", i + 1); + if (!alertLineValidation.isValid) { + if (alertLineValidation.error) { + errors.push(alertLineValidation.error); + } + continue; + } + if (!item.severity || typeof item.severity !== "string") { + errors.push(`Line ${i + 1}: create_code_scanning_alert requires a 'severity' field (string)`); + continue; + } + if (!item.message || typeof item.message !== "string") { + errors.push(`Line ${i + 1}: create_code_scanning_alert requires a 'message' field (string)`); + continue; + } + const allowedSeverities = ["error", "warning", "info", "note"]; + if (!allowedSeverities.includes(item.severity.toLowerCase())) { + errors.push( + `Line ${i + 1}: create_code_scanning_alert 'severity' must be one of: ${allowedSeverities.join(", ")}, got ${item.severity.toLowerCase()}` + ); + continue; + } + const columnValidation = validateOptionalPositiveInteger(item.column, "create_code_scanning_alert 'column'", i + 1); + if (!columnValidation.isValid) { + if (columnValidation.error) errors.push(columnValidation.error); + continue; + } + if (item.ruleIdSuffix !== undefined) { + if (typeof item.ruleIdSuffix !== "string") { + errors.push(`Line ${i + 1}: create_code_scanning_alert 'ruleIdSuffix' must be a string`); + continue; + } + if (!/^[a-zA-Z0-9_-]+$/.test(item.ruleIdSuffix.trim())) { + errors.push( + `Line ${i + 1}: create_code_scanning_alert 'ruleIdSuffix' must contain only alphanumeric characters, hyphens, and underscores` + ); + continue; + } + } + item.severity = item.severity.toLowerCase(); + item.file = sanitizeContent(item.file, 512); + item.severity = sanitizeContent(item.severity, 64); + item.message = sanitizeContent(item.message, 2048); + if (item.ruleIdSuffix) { + item.ruleIdSuffix = sanitizeContent(item.ruleIdSuffix, 128); + } + break; + default: + const jobOutputType = expectedOutputTypes[itemType]; + if (!jobOutputType) { + errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); + continue; + } + const safeJobConfig = jobOutputType; + if (safeJobConfig && safeJobConfig.inputs) { + const validation = validateItemWithSafeJobConfig(item, safeJobConfig, i + 1); + if (!validation.isValid) { + errors.push(...validation.errors); + continue; + } + Object.assign(item, validation.normalizedItem); + } + break; + } + core.info(`Line ${i + 1}: Valid ${itemType} item`); + parsedItems.push(item); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); + } + } + if (errors.length > 0) { + core.warning("Validation errors found:"); + errors.forEach(error => core.warning(` - ${error}`)); + if (parsedItems.length === 0) { + core.setFailed(errors.map(e => ` - ${e}`).join("\n")); + return; + } + } + for (const itemType of Object.keys(expectedOutputTypes)) { + const minRequired = getMinRequiredForType(itemType, expectedOutputTypes); + if (minRequired > 0) { + const actualCount = parsedItems.filter(item => item.type === itemType).length; + if (actualCount < minRequired) { + errors.push(`Too few items of type '${itemType}'. Minimum required: ${minRequired}, found: ${actualCount}.`); + } + } + } + core.info(`Successfully parsed ${parsedItems.length} valid output items`); + const validatedOutput = { + items: parsedItems, + errors: errors, + }; + const agentOutputFile = "/tmp/gh-aw/agent_output.json"; + const validatedOutputJson = JSON.stringify(validatedOutput); + try { + fs.mkdirSync("/tmp", { recursive: true }); + fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); + core.info(`Stored validated output to: ${agentOutputFile}`); + core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + core.error(`Failed to write agent output file: ${errorMsg}`); + } + core.setOutput("output", JSON.stringify(validatedOutput)); + core.setOutput("raw_output", outputContent); + const outputTypes = Array.from(new Set(parsedItems.map(item => item.type))); + core.info(`output_types: ${outputTypes.join(", ")}`); + core.setOutput("output_types", outputTypes.join(",")); +} +await main(); diff --git a/pkg/workflow/js/compute_text.cjs b/pkg/workflow/js/compute_text.cjs new file mode 100644 index 00000000000..1a1d0b4f58d --- /dev/null +++ b/pkg/workflow/js/compute_text.cjs @@ -0,0 +1,114 @@ +// @ts-check +/// + +/** + * Sanitizes content for safe output in GitHub Actions + * @param {string} content - The content to sanitize + * @returns {string} The sanitized content + */ +const { sanitizeContent } = require("./lib/sanitize.cjs"); + +async function main() { + let text = ""; + + const actor = context.actor; + const { owner, repo } = context.repo; + + // Check if the actor has repository access (admin, maintain permissions) + const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: owner, + repo: repo, + username: actor, + }); + + const permission = repoPermission.data.permission; + core.info(`Repository permission level: ${permission}`); + + if (permission !== "admin" && permission !== "maintain") { + core.setOutput("text", ""); + return; + } + + // Determine current body text based on event context + switch (context.eventName) { + case "issues": + // For issues: title + body + if (context.payload.issue) { + const title = context.payload.issue.title || ""; + const body = context.payload.issue.body || ""; + text = `${title}\n\n${body}`; + } + break; + + case "pull_request": + // For pull requests: title + body + if (context.payload.pull_request) { + const title = context.payload.pull_request.title || ""; + const body = context.payload.pull_request.body || ""; + text = `${title}\n\n${body}`; + } + break; + + case "pull_request_target": + // For pull request target events: title + body + if (context.payload.pull_request) { + const title = context.payload.pull_request.title || ""; + const body = context.payload.pull_request.body || ""; + text = `${title}\n\n${body}`; + } + break; + + case "issue_comment": + // For issue comments: comment body + if (context.payload.comment) { + text = context.payload.comment.body || ""; + } + break; + + case "pull_request_review_comment": + // For PR review comments: comment body + if (context.payload.comment) { + text = context.payload.comment.body || ""; + } + break; + + case "pull_request_review": + // For PR reviews: review body + if (context.payload.review) { + text = context.payload.review.body || ""; + } + break; + + case "discussion": + // For discussions: title + body + if (context.payload.discussion) { + const title = context.payload.discussion.title || ""; + const body = context.payload.discussion.body || ""; + text = `${title}\n\n${body}`; + } + break; + + case "discussion_comment": + // For discussion comments: comment body + if (context.payload.comment) { + text = context.payload.comment.body || ""; + } + break; + + default: + // Default: empty text + text = ""; + break; + } + + // Sanitize the text before output + const sanitizedText = sanitizeContent(text); + + // Display sanitized text in logs + core.info(`text: ${sanitizedText}`); + + // Set the sanitized text as output + core.setOutput("text", sanitizedText); +} + +await main(); diff --git a/pkg/workflow/js/lib/sanitize.cjs b/pkg/workflow/js/lib/sanitize.cjs index 224a8ff61b1..057e93f8dc3 100644 --- a/pkg/workflow/js/lib/sanitize.cjs +++ b/pkg/workflow/js/lib/sanitize.cjs @@ -29,12 +29,18 @@ function sanitizeContent(content, maxLength) { let sanitized = content; + // Neutralize commands at the start of text (e.g., /bot-name) + sanitized = neutralizeCommands(sanitized); + // Neutralize @mentions to prevent unintended notifications sanitized = neutralizeMentions(sanitized); // Remove XML comments first sanitized = removeXmlComments(sanitized); + // Convert XML tags to parentheses format to prevent injection + sanitized = convertXmlTags(sanitized); + // Remove ANSI escape sequences sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); @@ -101,14 +107,34 @@ function sanitizeContent(content, maxLength) { * @returns {string} The string with non-https protocols redacted */ function sanitizeUrlProtocols(s) { - // Match both protocol:// and protocol: patterns - // This covers URLs like https://example.com, javascript:alert(), mailto:user@domain.com, etc. - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { + // Match protocol patterns but avoid command-line flags like -v:10 + // Protocol patterns typically start with a letter at word boundary (not after -) + // Match both protocol:// and protocol: patterns (like javascript:alert()) + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { // Allow https (case insensitive), redact everything else return protocol.toLowerCase() === "https" ? match : "(redacted)"; }); } + /** + * Neutralizes commands at the start of text by wrapping them in backticks + * @param {string} s - The string to process + * @returns {string} The string with neutralized commands + */ + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + + // Escape special regex characters in command name + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + + // Neutralize /command at the start of text (with optional leading whitespace) + // Only match at the start of the string or after leading whitespace + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } + /** * Neutralizes @mentions by wrapping them in backticks * @param {string} s - The string to process @@ -132,6 +158,18 @@ function sanitizeContent(content, maxLength) { return s.replace(//g, "").replace(//g, ""); } + /** + * Converts XML/HTML tags to parentheses format to prevent injection + * @param {string} s - The string to process + * @returns {string} The string with XML tags converted to parentheses + */ + function convertXmlTags(s) { + // Convert opening tags: or to (tag) or (tag attr="value") + // Convert closing tags: to (/tag) + // Convert self-closing tags: or to (tag/) or (tag /) + return s.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + } + /** * Neutralizes bot trigger phrases by wrapping them in backticks * @param {string} s - The string to process diff --git a/pkg/workflow/js/sanitize_output.cjs b/pkg/workflow/js/sanitize_output.cjs new file mode 100644 index 00000000000..bedaaea03c7 --- /dev/null +++ b/pkg/workflow/js/sanitize_output.cjs @@ -0,0 +1,37 @@ +// @ts-check +/// + +/** + * Sanitizes content for safe output in GitHub Actions + * @param {string} content - The content to sanitize + * @returns {string} The sanitized content + */ +const { sanitizeContent } = require("./lib/sanitize.cjs"); + +async function main() { + const fs = require("fs"); + const outputFile = process.env.GH_AW_SAFE_OUTPUTS; + if (!outputFile) { + core.info("GH_AW_SAFE_OUTPUTS not set, no output to collect"); + core.setOutput("output", ""); + return; + } + + if (!fs.existsSync(outputFile)) { + core.info(`Output file does not exist: ${outputFile}`); + core.setOutput("output", ""); + return; + } + + const outputContent = fs.readFileSync(outputFile, "utf8"); + if (outputContent.trim() === "") { + core.info("Output file is empty"); + core.setOutput("output", ""); + } else { + const sanitizedContent = sanitizeContent(outputContent); + core.info(`Collected agentic output (sanitized): ${sanitizedContent.substring(0, 200)}${sanitizedContent.length > 200 ? "..." : ""}`); + core.setOutput("output", sanitizedContent); + } +} + +await main(); diff --git a/pkg/workflow/js/test.png b/pkg/workflow/js/test.png new file mode 100644 index 00000000000..93555af2a41 --- /dev/null +++ b/pkg/workflow/js/test.png @@ -0,0 +1 @@ +fake png data \ No newline at end of file From 869a2535f527b2945533d54dc28f417e73c2f9ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:22:14 +0000 Subject: [PATCH 03/13] Improve URL sanitization - fix domain filtering and protocol detection Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/lib/sanitize.cjs | 41 +++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/pkg/workflow/js/lib/sanitize.cjs b/pkg/workflow/js/lib/sanitize.cjs index 057e93f8dc3..1221e452156 100644 --- a/pkg/workflow/js/lib/sanitize.cjs +++ b/pkg/workflow/js/lib/sanitize.cjs @@ -85,9 +85,10 @@ function sanitizeContent(content, maxLength) { * @returns {string} The string with unknown domains redacted */ function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { + // Match full HTTPS URLs including path, query, and fragment + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { // Extract the hostname part (before first slash, colon, or other delimiter) - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); // Check if this domain or any parent domain is in the allowlist const isAllowed = allowedDomains.some(allowedDomain => { @@ -107,12 +108,35 @@ function sanitizeContent(content, maxLength) { * @returns {string} The string with non-https protocols redacted */ function sanitizeUrlProtocols(s) { - // Match protocol patterns but avoid command-line flags like -v:10 - // Protocol patterns typically start with a letter at word boundary (not after -) - // Match both protocol:// and protocol: patterns (like javascript:alert()) - return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + // Match protocol patterns but avoid command-line flags, file paths, and namespaces + // Protocol patterns typically have :// or are well-known schemes followed by : + // Use negative lookbehind to exclude patterns preceded by - (command flags) + // Match only patterns that look like actual protocols + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { // Allow https (case insensitive), redact everything else - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + // But only if it looks like a URL (has :// or is followed by non-colon content) + if (protocol.toLowerCase() === "https") { + return match; + } + + // Allow if it looks like a file path or namespace (::) + if (match.includes("::")) { + return match; + } + + // Redact if it has :// (definite protocol) + if (match.includes("://")) { + return "(redacted)"; + } + + // Redact well-known dangerous protocols like javascript:, data:, etc. + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + + // Otherwise preserve (could be file:path, namespace:thing, etc.) + return match; }); } @@ -167,7 +191,8 @@ function sanitizeContent(content, maxLength) { // Convert opening tags: or to (tag) or (tag attr="value") // Convert closing tags: to (/tag) // Convert self-closing tags: or to (tag/) or (tag /) - return s.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + // Also handle special tags like + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); } /** From f48d12d1104eb6906a720ce902b9c43dbb34054f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:24:01 +0000 Subject: [PATCH 04/13] Remove accidentally committed test.png file --- pkg/workflow/js/test.png | 1 - 1 file changed, 1 deletion(-) delete mode 100644 pkg/workflow/js/test.png diff --git a/pkg/workflow/js/test.png b/pkg/workflow/js/test.png deleted file mode 100644 index 93555af2a41..00000000000 --- a/pkg/workflow/js/test.png +++ /dev/null @@ -1 +0,0 @@ -fake png data \ No newline at end of file From 1e455791849bfe432c5c79734c9be8befe3c25db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:35:30 +0000 Subject: [PATCH 05/13] Allow safe HTML tags (details, summary, code, em, b) in sanitization Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/artifacts-summary.lock.yml | 34 ++++++++-- .github/workflows/audit-workflows.lock.yml | 34 ++++++++-- .github/workflows/blog-auditor.lock.yml | 34 ++++++++-- .github/workflows/brave.lock.yml | 68 ++++++++++++++++--- .github/workflows/ci-doctor.lock.yml | 34 ++++++++-- .../workflows/cli-version-checker.lock.yml | 34 ++++++++-- .../commit-changes-analyzer.lock.yml | 34 ++++++++-- .../workflows/copilot-agent-analysis.lock.yml | 34 ++++++++-- .../copilot-pr-prompt-analysis.lock.yml | 34 ++++++++-- .../copilot-session-insights.lock.yml | 34 ++++++++-- .github/workflows/craft.lock.yml | 68 ++++++++++++++++--- .github/workflows/daily-doc-updater.lock.yml | 34 ++++++++-- .../workflows/daily-firewall-report.lock.yml | 34 ++++++++-- .github/workflows/daily-news.lock.yml | 34 ++++++++-- .../workflows/daily-perf-improver.lock.yml | 34 ++++++++-- .../workflows/daily-repo-chronicle.lock.yml | 34 ++++++++-- .../workflows/daily-test-improver.lock.yml | 34 ++++++++-- .github/workflows/dev-hawk.lock.yml | 34 ++++++++-- .github/workflows/dev.lock.yml | 34 ++++++++-- .github/workflows/dictation-prompt.lock.yml | 34 ++++++++-- .../duplicate-code-detector.lock.yml | 34 ++++++++-- .../example-workflow-analyzer.lock.yml | 34 ++++++++-- .../github-mcp-tools-report.lock.yml | 34 ++++++++-- .github/workflows/go-logger.lock.yml | 34 ++++++++-- .../workflows/go-pattern-detector.lock.yml | 34 ++++++++-- .../workflows/instructions-janitor.lock.yml | 34 ++++++++-- .github/workflows/issue-classifier.lock.yml | 68 ++++++++++++++++--- .github/workflows/lockfile-stats.lock.yml | 34 ++++++++-- .github/workflows/mcp-inspector.lock.yml | 34 ++++++++-- .github/workflows/mergefest.lock.yml | 34 ++++++++-- .../workflows/notion-issue-summary.lock.yml | 34 ++++++++-- .github/workflows/pdf-summary.lock.yml | 68 ++++++++++++++++--- .github/workflows/plan.lock.yml | 68 ++++++++++++++++--- .github/workflows/poem-bot.lock.yml | 68 ++++++++++++++++--- .../prompt-clustering-analysis.lock.yml | 34 ++++++++-- .github/workflows/python-data-charts.lock.yml | 34 ++++++++-- .github/workflows/q.lock.yml | 68 ++++++++++++++++--- .github/workflows/repo-tree-map.lock.yml | 34 ++++++++-- .github/workflows/research.lock.yml | 34 ++++++++-- .github/workflows/safe-output-health.lock.yml | 34 ++++++++-- .../schema-consistency-checker.lock.yml | 34 ++++++++-- .github/workflows/scout.lock.yml | 68 ++++++++++++++++--- .github/workflows/security-fix-pr.lock.yml | 34 ++++++++-- .../semantic-function-refactor.lock.yml | 34 ++++++++-- .github/workflows/smoke-claude.lock.yml | 34 ++++++++-- .github/workflows/smoke-codex.lock.yml | 34 ++++++++-- .../workflows/smoke-copilot.firewall.lock.yml | 34 ++++++++-- .github/workflows/smoke-copilot.lock.yml | 34 ++++++++-- .github/workflows/smoke-detector.lock.yml | 34 ++++++++-- .github/workflows/smoke-opencode.lock.yml | 34 ++++++++-- .../workflows/technical-doc-writer.lock.yml | 34 ++++++++-- .../test-ollama-threat-detection.lock.yml | 34 ++++++++-- .github/workflows/tidy.lock.yml | 34 ++++++++-- .github/workflows/unbloat-docs.lock.yml | 34 ++++++++-- .github/workflows/video-analyzer.lock.yml | 34 ++++++++-- .../workflows/weekly-issue-summary.lock.yml | 34 ++++++++-- .../zizmor-security-analyzer.lock.yml | 34 ++++++++-- pkg/workflow/js/lib/sanitize.cjs | 16 ++++- pkg/workflow/js/test.png | 1 + 59 files changed, 1966 insertions(+), 261 deletions(-) create mode 100644 pkg/workflow/js/test.png diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index 5cb4a8fbc49..b3eb1081d23 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -1561,8 +1561,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1584,8 +1586,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1595,10 +1597,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1608,6 +1631,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 0f7eaf5ca5a..ac46eebd5a0 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -2039,8 +2039,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2062,8 +2064,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2073,10 +2075,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2086,6 +2109,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index 1a68133dc05..d47865260df 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -1943,8 +1943,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1966,8 +1968,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1977,10 +1979,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1990,6 +2013,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 26a9aa28b98..e4a2e2f0985 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -110,8 +110,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -133,8 +135,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -144,10 +146,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -157,6 +180,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } @@ -2425,8 +2451,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2448,8 +2476,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2459,10 +2487,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2472,6 +2521,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index bad9bb29a36..58694cb4bc6 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -2009,8 +2009,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2032,8 +2034,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2043,10 +2045,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2056,6 +2079,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 06cebb97699..78b55bf98c0 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -1666,8 +1666,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1689,8 +1691,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1700,10 +1702,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1713,6 +1736,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index 422e50cd2fb..ba55fe627ba 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -1874,8 +1874,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1897,8 +1899,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1908,10 +1910,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1921,6 +1944,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index ec3cd78d401..95c17539868 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -2172,8 +2172,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2195,8 +2197,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2206,10 +2208,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2219,6 +2242,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 3de69d0459c..720b17462f1 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -1907,8 +1907,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1930,8 +1932,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1941,10 +1943,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1954,6 +1977,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 2554ebac211..a0b0de22c99 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -2538,8 +2538,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2561,8 +2563,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2572,10 +2574,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2585,6 +2608,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 5d256f545dc..62728440e9f 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -110,8 +110,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -133,8 +135,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -144,10 +146,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -157,6 +180,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } @@ -2577,8 +2603,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2600,8 +2628,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2611,10 +2639,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2624,6 +2673,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 105a17f349c..99d1a830f15 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -1764,8 +1764,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1787,8 +1789,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1798,10 +1800,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1811,6 +1834,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 863dec364e0..004275bc310 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -1652,8 +1652,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1675,8 +1677,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1686,10 +1688,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1699,6 +1722,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index f4c2e1a175e..068f0be1f7d 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1689,8 +1689,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1712,8 +1714,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1723,10 +1725,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1736,6 +1759,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/daily-perf-improver.lock.yml b/.github/workflows/daily-perf-improver.lock.yml index 94c66fcc386..091ff2e0dbe 100644 --- a/.github/workflows/daily-perf-improver.lock.yml +++ b/.github/workflows/daily-perf-improver.lock.yml @@ -2068,8 +2068,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2091,8 +2093,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2102,10 +2104,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2115,6 +2138,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index 534ba0e40fb..49c1a145a8f 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -1556,8 +1556,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1579,8 +1581,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1590,10 +1592,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1603,6 +1626,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/daily-test-improver.lock.yml b/.github/workflows/daily-test-improver.lock.yml index 080842c4018..1f81c107267 100644 --- a/.github/workflows/daily-test-improver.lock.yml +++ b/.github/workflows/daily-test-improver.lock.yml @@ -2042,8 +2042,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2065,8 +2067,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2076,10 +2078,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2089,6 +2112,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index d993d3c0c56..5ce1324f4fd 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -1912,8 +1912,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1935,8 +1937,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1946,10 +1948,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1959,6 +1982,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 2340b00e700..c5752756f4f 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -1461,8 +1461,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1484,8 +1486,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1495,10 +1497,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1508,6 +1531,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index 5efd748ac68..70e56dcf60a 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -1572,8 +1572,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1595,8 +1597,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1606,10 +1608,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1619,6 +1642,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index fb8149713cf..67fede31a1b 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -1636,8 +1636,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1659,8 +1661,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1670,10 +1672,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1683,6 +1706,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index da45a970123..1c43d69e338 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -1607,8 +1607,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1630,8 +1632,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1641,10 +1643,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1654,6 +1677,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index 135bd5e7cdc..a058a4edfbf 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -2173,8 +2173,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2196,8 +2198,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2207,10 +2209,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2220,6 +2243,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index c1f53d3d83c..7c65c38bcea 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -1812,8 +1812,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1835,8 +1837,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1846,10 +1848,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1859,6 +1882,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index 5417f5d7a96..ae1d28c92c9 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -1705,8 +1705,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1728,8 +1730,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1739,10 +1741,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1752,6 +1775,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 7e4b730324d..b264b9a4fc0 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -1760,8 +1760,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1783,8 +1785,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1794,10 +1796,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1807,6 +1830,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 8b65ad4f7a9..0f3eb728381 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -102,8 +102,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -125,8 +127,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -136,10 +138,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -149,6 +172,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } @@ -2114,8 +2140,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2137,8 +2165,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2148,10 +2176,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2161,6 +2210,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index b62dc6e1d2f..f58ecf210f5 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -2011,8 +2011,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2034,8 +2036,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2045,10 +2047,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2058,6 +2081,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 41d9fa64557..2e265644615 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -2133,8 +2133,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2156,8 +2158,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2167,10 +2169,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2180,6 +2203,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index 965e2bd633f..d8bde89d1ca 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -2118,8 +2118,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2141,8 +2143,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2152,10 +2154,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2165,6 +2188,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index a90196aaf41..acd258d7efa 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -1420,8 +1420,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1443,8 +1445,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1454,10 +1456,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1467,6 +1490,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 04fb1003b68..3ee2df231f0 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -132,8 +132,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -155,8 +157,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -166,10 +168,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -179,6 +202,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } @@ -2530,8 +2556,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2553,8 +2581,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2564,10 +2592,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2577,6 +2626,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 007df466361..573ec706645 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -111,8 +111,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -134,8 +136,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -145,10 +147,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -158,6 +181,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } @@ -2026,8 +2052,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2049,8 +2077,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2060,10 +2088,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2073,6 +2122,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index a315661e6f4..8e1727735e2 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -145,8 +145,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -168,8 +170,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -179,10 +181,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -192,6 +215,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } @@ -2771,8 +2797,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2794,8 +2822,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2805,10 +2833,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2818,6 +2867,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index 41c3da098b4..9221894e3f8 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -2346,8 +2346,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2369,8 +2371,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2380,10 +2382,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2393,6 +2416,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 3796c6d818d..08eaf50d37f 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -1860,8 +1860,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1883,8 +1885,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1894,10 +1896,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1907,6 +1930,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 4923e363442..1945fdb1878 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -154,8 +154,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -177,8 +179,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -188,10 +190,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -201,6 +224,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } @@ -2838,8 +2864,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2861,8 +2889,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2872,10 +2900,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2885,6 +2934,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index 58c866af057..ae566517805 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -1585,8 +1585,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1608,8 +1610,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1619,10 +1621,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1632,6 +1655,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index 2c819b28308..12fee2ee06a 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -1526,8 +1526,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1549,8 +1551,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1560,10 +1562,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1573,6 +1596,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index bdd71420963..16b40400202 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -2143,8 +2143,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2166,8 +2168,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2177,10 +2179,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2190,6 +2213,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index dc6390adc38..7953791bc6c 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -2013,8 +2013,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2036,8 +2038,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2047,10 +2049,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2060,6 +2083,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 40b7eff818f..e348e9b00a7 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -157,8 +157,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -180,8 +182,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -191,10 +193,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -204,6 +227,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } @@ -2904,8 +2930,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2927,8 +2955,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2938,10 +2966,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2951,6 +3000,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index 34e963651e9..a132373fc70 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -1708,8 +1708,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1731,8 +1733,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1742,10 +1744,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1755,6 +1778,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 61bf5e740eb..1ee6070af15 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -2055,8 +2055,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2078,8 +2080,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2089,10 +2091,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2102,6 +2125,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index a0b23151794..ae5fb6f1252 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1556,8 +1556,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1579,8 +1581,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1590,10 +1592,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1603,6 +1626,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 4f742201068..d6332de9396 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -1377,8 +1377,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1400,8 +1402,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1411,10 +1413,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1424,6 +1447,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/smoke-copilot.firewall.lock.yml b/.github/workflows/smoke-copilot.firewall.lock.yml index df80741fd19..28ad899bd16 100644 --- a/.github/workflows/smoke-copilot.firewall.lock.yml +++ b/.github/workflows/smoke-copilot.firewall.lock.yml @@ -1449,8 +1449,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1472,8 +1474,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1483,10 +1485,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1496,6 +1519,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 20566464aa6..eb89ae64fec 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1449,8 +1449,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1472,8 +1474,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1483,10 +1485,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1496,6 +1519,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index fa49b93d4dd..2d5bc202619 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -2584,8 +2584,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2607,8 +2609,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2618,10 +2620,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2631,6 +2654,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 18985f17eac..44ceab5de97 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -1413,8 +1413,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1436,8 +1438,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1447,10 +1449,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1460,6 +1483,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 1812bf79baf..dede68208ff 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -2305,8 +2305,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2328,8 +2330,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2339,10 +2341,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2352,6 +2375,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/test-ollama-threat-detection.lock.yml b/.github/workflows/test-ollama-threat-detection.lock.yml index 9d8037edd00..de646b10f31 100644 --- a/.github/workflows/test-ollama-threat-detection.lock.yml +++ b/.github/workflows/test-ollama-threat-detection.lock.yml @@ -1392,8 +1392,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1415,8 +1417,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1426,10 +1428,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1439,6 +1462,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 8ef62026487..320cb5f3fc6 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -1926,8 +1926,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1949,8 +1951,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1960,10 +1962,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1973,6 +1996,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 750a67a0fd4..bfa93d30028 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -2673,8 +2673,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2696,8 +2698,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2707,10 +2709,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2720,6 +2743,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index c4a6ee1a1c0..affb34360dc 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -1683,8 +1683,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1706,8 +1708,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1717,10 +1719,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1730,6 +1753,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index 2c9f85d5879..eaec1b17ceb 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -1458,8 +1458,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -1481,8 +1483,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -1492,10 +1494,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -1505,6 +1528,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/.github/workflows/zizmor-security-analyzer.lock.yml b/.github/workflows/zizmor-security-analyzer.lock.yml index 24ff2b60888..1a9a9732563 100644 --- a/.github/workflows/zizmor-security-analyzer.lock.yml +++ b/.github/workflows/zizmor-security-analyzer.lock.yml @@ -1985,8 +1985,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2008,8 +2010,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2019,10 +2021,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2032,6 +2055,9 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/pkg/workflow/js/lib/sanitize.cjs b/pkg/workflow/js/lib/sanitize.cjs index 1221e452156..744a0ec143e 100644 --- a/pkg/workflow/js/lib/sanitize.cjs +++ b/pkg/workflow/js/lib/sanitize.cjs @@ -188,11 +188,25 @@ function sanitizeContent(content, maxLength) { * @returns {string} The string with XML tags converted to parentheses */ function convertXmlTags(s) { + // Allow safe HTML tags: details, summary, code, em, b + const allowedTags = ["details", "summary", "code", "em", "b"]; + // Convert opening tags: or to (tag) or (tag attr="value") // Convert closing tags: to (/tag) // Convert self-closing tags: or to (tag/) or (tag /) // Also handle special tags like - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + // But preserve allowed safe tags + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + // Extract tag name from the content (handle closing tags and attributes) + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; // Preserve allowed tags + } + } + return `(${tagContent})`; // Convert other tags to parentheses + }); } /** diff --git a/pkg/workflow/js/test.png b/pkg/workflow/js/test.png new file mode 100644 index 00000000000..93555af2a41 --- /dev/null +++ b/pkg/workflow/js/test.png @@ -0,0 +1 @@ +fake png data \ No newline at end of file From 6513b6f90751d247244b96f2290feaef28744e51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:36:13 +0000 Subject: [PATCH 06/13] Remove test.png file that was accidentally re-added Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/test.png | 1 - 1 file changed, 1 deletion(-) delete mode 100644 pkg/workflow/js/test.png diff --git a/pkg/workflow/js/test.png b/pkg/workflow/js/test.png deleted file mode 100644 index 93555af2a41..00000000000 --- a/pkg/workflow/js/test.png +++ /dev/null @@ -1 +0,0 @@ -fake png data \ No newline at end of file From 52da4d04a73ac301e2e82ba6dcef52cf139bb70f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:50:14 +0000 Subject: [PATCH 07/13] Flatten directory structure - move all .cjs files to pkg/workflow/js root Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/artifacts-summary.lock.yml | 12 +- .github/workflows/audit-workflows.lock.yml | 12 +- .github/workflows/blog-auditor.lock.yml | 12 +- .github/workflows/brave.lock.yml | 24 +- .github/workflows/ci-doctor.lock.yml | 12 +- .../workflows/cli-version-checker.lock.yml | 12 +- .../commit-changes-analyzer.lock.yml | 12 +- .../workflows/copilot-agent-analysis.lock.yml | 12 +- .../copilot-pr-prompt-analysis.lock.yml | 12 +- .../copilot-session-insights.lock.yml | 12 +- .github/workflows/craft.lock.yml | 24 +- .github/workflows/daily-doc-updater.lock.yml | 12 +- .../workflows/daily-firewall-report.lock.yml | 12 +- .github/workflows/daily-news.lock.yml | 12 +- .../workflows/daily-perf-improver.lock.yml | 12 +- .../workflows/daily-repo-chronicle.lock.yml | 12 +- .../workflows/daily-test-improver.lock.yml | 12 +- .github/workflows/dev-hawk.lock.yml | 12 +- .github/workflows/dev.lock.yml | 12 +- .github/workflows/dictation-prompt.lock.yml | 12 +- .../duplicate-code-detector.lock.yml | 12 +- .../example-workflow-analyzer.lock.yml | 12 +- .../github-mcp-tools-report.lock.yml | 12 +- .github/workflows/go-logger.lock.yml | 12 +- .../workflows/go-pattern-detector.lock.yml | 12 +- .../workflows/instructions-janitor.lock.yml | 12 +- .github/workflows/issue-classifier.lock.yml | 24 +- .github/workflows/lockfile-stats.lock.yml | 12 +- .github/workflows/mcp-inspector.lock.yml | 12 +- .github/workflows/mergefest.lock.yml | 12 +- .../workflows/notion-issue-summary.lock.yml | 12 +- .github/workflows/pdf-summary.lock.yml | 24 +- .github/workflows/plan.lock.yml | 24 +- .github/workflows/poem-bot.lock.yml | 24 +- .../prompt-clustering-analysis.lock.yml | 12 +- .github/workflows/python-data-charts.lock.yml | 12 +- .github/workflows/q.lock.yml | 24 +- .github/workflows/repo-tree-map.lock.yml | 12 +- .github/workflows/research.lock.yml | 12 +- .github/workflows/safe-output-health.lock.yml | 12 +- .../schema-consistency-checker.lock.yml | 12 +- .github/workflows/scout.lock.yml | 24 +- .github/workflows/security-fix-pr.lock.yml | 12 +- .../semantic-function-refactor.lock.yml | 12 +- .github/workflows/smoke-claude.lock.yml | 12 +- .github/workflows/smoke-codex.lock.yml | 12 +- .../workflows/smoke-copilot.firewall.lock.yml | 12 +- .github/workflows/smoke-copilot.lock.yml | 12 +- .github/workflows/smoke-detector.lock.yml | 12 +- .github/workflows/smoke-opencode.lock.yml | 12 +- .../workflows/technical-doc-writer.lock.yml | 12 +- .../test-ollama-threat-detection.lock.yml | 12 +- .github/workflows/tidy.lock.yml | 12 +- .github/workflows/unbloat-docs.lock.yml | 12 +- .github/workflows/video-analyzer.lock.yml | 12 +- .../workflows/weekly-issue-summary.lock.yml | 12 +- .../zizmor-security-analyzer.lock.yml | 12 +- pkg/workflow/js/collect_ndjson_output.cjs | 2 +- pkg/workflow/js/compute_text.cjs | 2 +- pkg/workflow/js/{lib => }/sanitize.cjs | 0 pkg/workflow/js/sanitize_output.cjs | 2 +- pkg/workflow/js/src/collect_ndjson_output.cjs | 698 ------------------ pkg/workflow/js/src/compute_text.cjs | 114 --- pkg/workflow/js/src/sanitize_output.cjs | 37 - 64 files changed, 718 insertions(+), 917 deletions(-) rename pkg/workflow/js/{lib => }/sanitize.cjs (100%) delete mode 100644 pkg/workflow/js/src/collect_ndjson_output.cjs delete mode 100644 pkg/workflow/js/src/compute_text.cjs delete mode 100644 pkg/workflow/js/src/sanitize_output.cjs diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index b3eb1081d23..ab3203552ea 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -1632,7 +1632,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index ac46eebd5a0..fe398cdeec9 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -2110,7 +2110,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index d47865260df..9b2e0cfa966 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -2014,7 +2014,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index e4a2e2f0985..b047b0c9a04 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -181,7 +181,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); @@ -2522,7 +2532,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 58694cb4bc6..08fa7c46dad 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -2080,7 +2080,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 78b55bf98c0..95204b283e4 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -1737,7 +1737,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index ba55fe627ba..f55e9580c20 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -1945,7 +1945,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 95c17539868..0184950e86a 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -2243,7 +2243,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 720b17462f1..03f35fc7a48 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -1978,7 +1978,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index a0b0de22c99..39abb75fa2d 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -2609,7 +2609,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 62728440e9f..aed2640d5b9 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -181,7 +181,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); @@ -2674,7 +2684,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 99d1a830f15..7c1cf219a4e 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -1835,7 +1835,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 004275bc310..925b9a2e1d3 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -1723,7 +1723,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 068f0be1f7d..a63660a4257 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1760,7 +1760,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/daily-perf-improver.lock.yml b/.github/workflows/daily-perf-improver.lock.yml index 091ff2e0dbe..32f6f316fc1 100644 --- a/.github/workflows/daily-perf-improver.lock.yml +++ b/.github/workflows/daily-perf-improver.lock.yml @@ -2139,7 +2139,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index 49c1a145a8f..ff6dc2d686a 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -1627,7 +1627,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/daily-test-improver.lock.yml b/.github/workflows/daily-test-improver.lock.yml index 1f81c107267..c2dc62e93ee 100644 --- a/.github/workflows/daily-test-improver.lock.yml +++ b/.github/workflows/daily-test-improver.lock.yml @@ -2113,7 +2113,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index 5ce1324f4fd..d7a6aedab29 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -1983,7 +1983,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index c5752756f4f..cfd7ef910a5 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -1532,7 +1532,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index 70e56dcf60a..5ae6b9f6110 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -1643,7 +1643,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 67fede31a1b..352cd283058 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -1707,7 +1707,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index 1c43d69e338..8677d2d8cf9 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -1678,7 +1678,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index a058a4edfbf..a70bd2e59bf 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -2244,7 +2244,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 7c65c38bcea..da73641fdcf 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -1883,7 +1883,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index ae1d28c92c9..2acefbcb4a2 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -1776,7 +1776,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index b264b9a4fc0..cd422579818 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -1831,7 +1831,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 0f3eb728381..64b81a2e3d0 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -173,7 +173,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); @@ -2211,7 +2221,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index f58ecf210f5..ee8ff30b016 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -2082,7 +2082,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 2e265644615..07cd4a7f42d 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -2204,7 +2204,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index d8bde89d1ca..caff43a3f4b 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -2189,7 +2189,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index acd258d7efa..c24d3d79232 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -1491,7 +1491,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 3ee2df231f0..97ef657b21d 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -203,7 +203,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); @@ -2627,7 +2637,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index 573ec706645..39eacd3f94d 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -182,7 +182,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); @@ -2123,7 +2133,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 8e1727735e2..2975c815631 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -216,7 +216,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); @@ -2868,7 +2878,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index 9221894e3f8..83a0a905a70 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -2417,7 +2417,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 08eaf50d37f..cb9d9e70e73 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -1931,7 +1931,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 1945fdb1878..58154bb7826 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -225,7 +225,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); @@ -2935,7 +2945,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index ae566517805..88f6f31604a 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -1656,7 +1656,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index 12fee2ee06a..f7d6ee39228 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -1597,7 +1597,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index 16b40400202..a630408e61a 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -2214,7 +2214,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 7953791bc6c..45bb6104fad 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -2084,7 +2084,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index e348e9b00a7..e8a3be32aa9 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -228,7 +228,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); @@ -3001,7 +3011,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index a132373fc70..45a8879d49b 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -1779,7 +1779,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 1ee6070af15..798e213f837 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -2126,7 +2126,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index ae5fb6f1252..450538f111f 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1627,7 +1627,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index d6332de9396..e4256b0292e 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -1448,7 +1448,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/smoke-copilot.firewall.lock.yml b/.github/workflows/smoke-copilot.firewall.lock.yml index 28ad899bd16..404abb28e8a 100644 --- a/.github/workflows/smoke-copilot.firewall.lock.yml +++ b/.github/workflows/smoke-copilot.firewall.lock.yml @@ -1520,7 +1520,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index eb89ae64fec..1a56055e009 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1520,7 +1520,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index 2d5bc202619..b0cc56a0b42 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -2655,7 +2655,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 44ceab5de97..0925262d689 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -1484,7 +1484,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index dede68208ff..f214f76de4e 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -2376,7 +2376,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/test-ollama-threat-detection.lock.yml b/.github/workflows/test-ollama-threat-detection.lock.yml index de646b10f31..40f013f1168 100644 --- a/.github/workflows/test-ollama-threat-detection.lock.yml +++ b/.github/workflows/test-ollama-threat-detection.lock.yml @@ -1463,7 +1463,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 320cb5f3fc6..dfe84f8d76b 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -1997,7 +1997,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index bfa93d30028..8fd384e20ca 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -2744,7 +2744,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index affb34360dc..6a5a8ff7feb 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -1754,7 +1754,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index eaec1b17ceb..7f1347db2d6 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -1529,7 +1529,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/.github/workflows/zizmor-security-analyzer.lock.yml b/.github/workflows/zizmor-security-analyzer.lock.yml index 1a9a9732563..0b78fde9489 100644 --- a/.github/workflows/zizmor-security-analyzer.lock.yml +++ b/.github/workflows/zizmor-security-analyzer.lock.yml @@ -2056,7 +2056,17 @@ jobs: return s.replace(//g, "").replace(//g, ""); } function convertXmlTags(s) { - return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, "($1)"); + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); diff --git a/pkg/workflow/js/collect_ndjson_output.cjs b/pkg/workflow/js/collect_ndjson_output.cjs index 485403f7d32..994039ddf5f 100644 --- a/pkg/workflow/js/collect_ndjson_output.cjs +++ b/pkg/workflow/js/collect_ndjson_output.cjs @@ -3,7 +3,7 @@ async function main() { const fs = require("fs"); - const { sanitizeContent } = require("./lib/sanitize.cjs"); + const { sanitizeContent } = require("./sanitize.cjs"); const maxBodyLength = 65000; function getMaxAllowedForType(itemType, config) { const itemConfig = config?.[itemType]; diff --git a/pkg/workflow/js/compute_text.cjs b/pkg/workflow/js/compute_text.cjs index 1a1d0b4f58d..bf702c0b261 100644 --- a/pkg/workflow/js/compute_text.cjs +++ b/pkg/workflow/js/compute_text.cjs @@ -6,7 +6,7 @@ * @param {string} content - The content to sanitize * @returns {string} The sanitized content */ -const { sanitizeContent } = require("./lib/sanitize.cjs"); +const { sanitizeContent } = require("./sanitize.cjs"); async function main() { let text = ""; diff --git a/pkg/workflow/js/lib/sanitize.cjs b/pkg/workflow/js/sanitize.cjs similarity index 100% rename from pkg/workflow/js/lib/sanitize.cjs rename to pkg/workflow/js/sanitize.cjs diff --git a/pkg/workflow/js/sanitize_output.cjs b/pkg/workflow/js/sanitize_output.cjs index bedaaea03c7..665d5c4e70e 100644 --- a/pkg/workflow/js/sanitize_output.cjs +++ b/pkg/workflow/js/sanitize_output.cjs @@ -6,7 +6,7 @@ * @param {string} content - The content to sanitize * @returns {string} The sanitized content */ -const { sanitizeContent } = require("./lib/sanitize.cjs"); +const { sanitizeContent } = require("./sanitize.cjs"); async function main() { const fs = require("fs"); diff --git a/pkg/workflow/js/src/collect_ndjson_output.cjs b/pkg/workflow/js/src/collect_ndjson_output.cjs deleted file mode 100644 index 485403f7d32..00000000000 --- a/pkg/workflow/js/src/collect_ndjson_output.cjs +++ /dev/null @@ -1,698 +0,0 @@ -// @ts-check -/// - -async function main() { - const fs = require("fs"); - const { sanitizeContent } = require("./lib/sanitize.cjs"); - const maxBodyLength = 65000; - function getMaxAllowedForType(itemType, config) { - const itemConfig = config?.[itemType]; - if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) { - return itemConfig.max; - } - switch (itemType) { - case "create_issue": - return 1; - case "create_agent_task": - return 1; - case "add_comment": - return 1; - case "create_pull_request": - return 1; - case "create_pull_request_review_comment": - return 1; - case "add_labels": - return 5; - case "update_issue": - return 1; - case "push_to_pull_request_branch": - return 1; - case "create_discussion": - return 1; - case "missing_tool": - return 20; - case "create_code_scanning_alert": - return 40; - case "upload_asset": - return 10; - default: - return 1; - } - } - function getMinRequiredForType(itemType, config) { - const itemConfig = config?.[itemType]; - if (itemConfig && typeof itemConfig === "object" && "min" in itemConfig && itemConfig.min) { - return itemConfig.min; - } - return 0; - } - function repairJson(jsonStr) { - let repaired = jsonStr.trim(); - const _ctrl = { 8: "\\b", 9: "\\t", 10: "\\n", 12: "\\f", 13: "\\r" }; - repaired = repaired.replace(/[\u0000-\u001F]/g, ch => { - const c = ch.charCodeAt(0); - return _ctrl[c] || "\\u" + c.toString(16).padStart(4, "0"); - }); - repaired = repaired.replace(/'/g, '"'); - repaired = repaired.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":'); - repaired = repaired.replace(/"([^"\\]*)"/g, (match, content) => { - if (content.includes("\n") || content.includes("\r") || content.includes("\t")) { - const escaped = content.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"); - return `"${escaped}"`; - } - return match; - }); - repaired = repaired.replace(/"([^"]*)"([^":,}\]]*)"([^"]*)"(\s*[,:}\]])/g, (match, p1, p2, p3, p4) => `"${p1}\\"${p2}\\"${p3}"${p4}`); - repaired = repaired.replace(/(\[\s*(?:"[^"]*"(?:\s*,\s*"[^"]*")*\s*),?)\s*}/g, "$1]"); - const openBraces = (repaired.match(/\{/g) || []).length; - const closeBraces = (repaired.match(/\}/g) || []).length; - if (openBraces > closeBraces) { - repaired += "}".repeat(openBraces - closeBraces); - } else if (closeBraces > openBraces) { - repaired = "{".repeat(closeBraces - openBraces) + repaired; - } - const openBrackets = (repaired.match(/\[/g) || []).length; - const closeBrackets = (repaired.match(/\]/g) || []).length; - if (openBrackets > closeBrackets) { - repaired += "]".repeat(openBrackets - closeBrackets); - } else if (closeBrackets > openBrackets) { - repaired = "[".repeat(closeBrackets - openBrackets) + repaired; - } - repaired = repaired.replace(/,(\s*[}\]])/g, "$1"); - return repaired; - } - function validatePositiveInteger(value, fieldName, lineNum) { - if (value === undefined || value === null) { - if (fieldName.includes("create_code_scanning_alert 'line'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_code_scanning_alert requires a 'line' field (number or string)`, - }; - } - if (fieldName.includes("create_pull_request_review_comment 'line'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_pull_request_review_comment requires a 'line' number`, - }; - } - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} is required`, - }; - } - if (typeof value !== "number" && typeof value !== "string") { - if (fieldName.includes("create_code_scanning_alert 'line'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_code_scanning_alert requires a 'line' field (number or string)`, - }; - } - if (fieldName.includes("create_pull_request_review_comment 'line'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_pull_request_review_comment requires a 'line' number or string field`, - }; - } - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be a number or string`, - }; - } - const parsed = typeof value === "string" ? parseInt(value, 10) : value; - if (isNaN(parsed) || parsed <= 0 || !Number.isInteger(parsed)) { - if (fieldName.includes("create_code_scanning_alert 'line'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_code_scanning_alert 'line' must be a valid positive integer (got: ${value})`, - }; - } - if (fieldName.includes("create_pull_request_review_comment 'line'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_pull_request_review_comment 'line' must be a positive integer`, - }; - } - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be a positive integer (got: ${value})`, - }; - } - return { isValid: true, normalizedValue: parsed }; - } - function validateOptionalPositiveInteger(value, fieldName, lineNum) { - if (value === undefined) { - return { isValid: true }; - } - if (typeof value !== "number" && typeof value !== "string") { - if (fieldName.includes("create_pull_request_review_comment 'start_line'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_pull_request_review_comment 'start_line' must be a number or string`, - }; - } - if (fieldName.includes("create_code_scanning_alert 'column'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_code_scanning_alert 'column' must be a number or string`, - }; - } - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be a number or string`, - }; - } - const parsed = typeof value === "string" ? parseInt(value, 10) : value; - if (isNaN(parsed) || parsed <= 0 || !Number.isInteger(parsed)) { - if (fieldName.includes("create_pull_request_review_comment 'start_line'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_pull_request_review_comment 'start_line' must be a positive integer`, - }; - } - if (fieldName.includes("create_code_scanning_alert 'column'")) { - return { - isValid: false, - error: `Line ${lineNum}: create_code_scanning_alert 'column' must be a valid positive integer (got: ${value})`, - }; - } - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be a positive integer (got: ${value})`, - }; - } - return { isValid: true, normalizedValue: parsed }; - } - function validateIssueOrPRNumber(value, fieldName, lineNum) { - if (value === undefined) { - return { isValid: true }; - } - if (typeof value !== "number" && typeof value !== "string") { - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be a number or string`, - }; - } - return { isValid: true }; - } - function validateFieldWithInputSchema(value, fieldName, inputSchema, lineNum) { - if (inputSchema.required && (value === undefined || value === null)) { - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} is required`, - }; - } - if (value === undefined || value === null) { - return { - isValid: true, - normalizedValue: inputSchema.default || undefined, - }; - } - const inputType = inputSchema.type || "string"; - let normalizedValue = value; - switch (inputType) { - case "string": - if (typeof value !== "string") { - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be a string`, - }; - } - normalizedValue = sanitizeContent(value); - break; - case "boolean": - if (typeof value !== "boolean") { - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be a boolean`, - }; - } - break; - case "number": - if (typeof value !== "number") { - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be a number`, - }; - } - break; - case "choice": - if (typeof value !== "string") { - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be a string for choice type`, - }; - } - if (inputSchema.options && !inputSchema.options.includes(value)) { - return { - isValid: false, - error: `Line ${lineNum}: ${fieldName} must be one of: ${inputSchema.options.join(", ")}`, - }; - } - normalizedValue = sanitizeContent(value); - break; - default: - if (typeof value === "string") { - normalizedValue = sanitizeContent(value); - } - break; - } - return { - isValid: true, - normalizedValue, - }; - } - function validateItemWithSafeJobConfig(item, jobConfig, lineNum) { - const errors = []; - const normalizedItem = { ...item }; - if (!jobConfig.inputs) { - return { - isValid: true, - errors: [], - normalizedItem: item, - }; - } - for (const [fieldName, inputSchema] of Object.entries(jobConfig.inputs)) { - const fieldValue = item[fieldName]; - const validation = validateFieldWithInputSchema(fieldValue, fieldName, inputSchema, lineNum); - if (!validation.isValid && validation.error) { - errors.push(validation.error); - } else if (validation.normalizedValue !== undefined) { - normalizedItem[fieldName] = validation.normalizedValue; - } - } - return { - isValid: errors.length === 0, - errors, - normalizedItem, - }; - } - function parseJsonWithRepair(jsonStr) { - try { - return JSON.parse(jsonStr); - } catch (originalError) { - try { - const repairedJson = repairJson(jsonStr); - return JSON.parse(repairedJson); - } catch (repairError) { - core.info(`invalid input json: ${jsonStr}`); - const originalMsg = originalError instanceof Error ? originalError.message : String(originalError); - const repairMsg = repairError instanceof Error ? repairError.message : String(repairError); - throw new Error(`JSON parsing failed. Original: ${originalMsg}. After attempted repair: ${repairMsg}`); - } - } - } - const outputFile = process.env.GH_AW_SAFE_OUTPUTS; - const safeOutputsConfig = process.env.GH_AW_SAFE_OUTPUTS_CONFIG; - if (!outputFile) { - core.info("GH_AW_SAFE_OUTPUTS not set, no output to collect"); - core.setOutput("output", ""); - return; - } - if (!fs.existsSync(outputFile)) { - core.info(`Output file does not exist: ${outputFile}`); - core.setOutput("output", ""); - return; - } - const outputContent = fs.readFileSync(outputFile, "utf8"); - if (outputContent.trim() === "") { - core.info("Output file is empty"); - } - core.info(`Raw output content length: ${outputContent.length}`); - let expectedOutputTypes = {}; - if (safeOutputsConfig) { - try { - const rawConfig = JSON.parse(safeOutputsConfig); - // Normalize all config keys to use underscores instead of dashes - expectedOutputTypes = Object.fromEntries(Object.entries(rawConfig).map(([key, value]) => [key.replace(/-/g, "_"), value])); - core.info(`Expected output types: ${JSON.stringify(Object.keys(expectedOutputTypes))}`); - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - core.info(`Warning: Could not parse safe-outputs config: ${errorMsg}`); - } - } - const lines = outputContent.trim().split("\n"); - const parsedItems = []; - const errors = []; - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - if (line === "") continue; - try { - const item = parseJsonWithRepair(line); - if (item === undefined) { - errors.push(`Line ${i + 1}: Invalid JSON - JSON parsing failed`); - continue; - } - if (!item.type) { - errors.push(`Line ${i + 1}: Missing required 'type' field`); - continue; - } - // Normalize type to use underscores (convert any dashes to underscores for resilience) - const itemType = item.type.replace(/-/g, "_"); - // Update item.type to normalized value - item.type = itemType; - if (!expectedOutputTypes[itemType]) { - errors.push(`Line ${i + 1}: Unexpected output type '${itemType}'. Expected one of: ${Object.keys(expectedOutputTypes).join(", ")}`); - continue; - } - const typeCount = parsedItems.filter(existing => existing.type === itemType).length; - const maxAllowed = getMaxAllowedForType(itemType, expectedOutputTypes); - if (typeCount >= maxAllowed) { - errors.push(`Line ${i + 1}: Too many items of type '${itemType}'. Maximum allowed: ${maxAllowed}.`); - continue; - } - core.info(`Line ${i + 1}: type '${itemType}'`); - switch (itemType) { - case "create_issue": - if (!item.title || typeof item.title !== "string") { - errors.push(`Line ${i + 1}: create_issue requires a 'title' string field`); - continue; - } - if (!item.body || typeof item.body !== "string") { - errors.push(`Line ${i + 1}: create_issue requires a 'body' string field`); - continue; - } - item.title = sanitizeContent(item.title, 128); - item.body = sanitizeContent(item.body, maxBodyLength); - if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => (typeof label === "string" ? sanitizeContent(label, 128) : label)); - } - // Validate parent field if provided - if (item.parent !== undefined) { - const parentValidation = validateIssueOrPRNumber(item.parent, "create_issue 'parent'", i + 1); - if (!parentValidation.isValid) { - if (parentValidation.error) errors.push(parentValidation.error); - continue; - } - } - break; - case "add_comment": - if (!item.body || typeof item.body !== "string") { - errors.push(`Line ${i + 1}: add_comment requires a 'body' string field`); - continue; - } - // Validate number - if (item.item_number !== undefined) { - const itemNumberValidation = validateIssueOrPRNumber(item.item_number, "add_comment 'item_number'", i + 1); - if (!itemNumberValidation.isValid) { - if (itemNumberValidation.error) errors.push(itemNumberValidation.error); - continue; - } - } - item.body = sanitizeContent(item.body, maxBodyLength); - break; - case "create_pull_request": - if (!item.title || typeof item.title !== "string") { - errors.push(`Line ${i + 1}: create_pull_request requires a 'title' string field`); - continue; - } - if (!item.body || typeof item.body !== "string") { - errors.push(`Line ${i + 1}: create_pull_request requires a 'body' string field`); - continue; - } - if (!item.branch || typeof item.branch !== "string") { - errors.push(`Line ${i + 1}: create_pull_request requires a 'branch' string field`); - continue; - } - item.title = sanitizeContent(item.title, 128); - item.body = sanitizeContent(item.body, maxBodyLength); - item.branch = sanitizeContent(item.branch, 256); - if (item.labels && Array.isArray(item.labels)) { - item.labels = item.labels.map(label => (typeof label === "string" ? sanitizeContent(label, 128) : label)); - } - break; - case "add_labels": - if (!item.labels || !Array.isArray(item.labels)) { - errors.push(`Line ${i + 1}: add_labels requires a 'labels' array field`); - continue; - } - if (item.labels.some(label => typeof label !== "string")) { - errors.push(`Line ${i + 1}: add_labels labels array must contain only strings`); - continue; - } - const labelsItemNumberValidation = validateIssueOrPRNumber(item.item_number, "add_labels 'item_number'", i + 1); - if (!labelsItemNumberValidation.isValid) { - if (labelsItemNumberValidation.error) errors.push(labelsItemNumberValidation.error); - continue; - } - item.labels = item.labels.map(label => sanitizeContent(label, 128)); - break; - case "update_issue": - const hasValidField = item.status !== undefined || item.title !== undefined || item.body !== undefined; - if (!hasValidField) { - errors.push(`Line ${i + 1}: update_issue requires at least one of: 'status', 'title', or 'body' fields`); - continue; - } - if (item.status !== undefined) { - if (typeof item.status !== "string" || (item.status !== "open" && item.status !== "closed")) { - errors.push(`Line ${i + 1}: update_issue 'status' must be 'open' or 'closed'`); - continue; - } - } - if (item.title !== undefined) { - if (typeof item.title !== "string") { - errors.push(`Line ${i + 1}: update_issue 'title' must be a string`); - continue; - } - item.title = sanitizeContent(item.title, 128); - } - if (item.body !== undefined) { - if (typeof item.body !== "string") { - errors.push(`Line ${i + 1}: update_issue 'body' must be a string`); - continue; - } - item.body = sanitizeContent(item.body, maxBodyLength); - } - const updateIssueNumValidation = validateIssueOrPRNumber(item.issue_number, "update_issue 'issue_number'", i + 1); - if (!updateIssueNumValidation.isValid) { - if (updateIssueNumValidation.error) errors.push(updateIssueNumValidation.error); - continue; - } - break; - case "push_to_pull_request_branch": - if (!item.branch || typeof item.branch !== "string") { - errors.push(`Line ${i + 1}: push_to_pull_request_branch requires a 'branch' string field`); - continue; - } - if (!item.message || typeof item.message !== "string") { - errors.push(`Line ${i + 1}: push_to_pull_request_branch requires a 'message' string field`); - continue; - } - item.branch = sanitizeContent(item.branch, 256); - item.message = sanitizeContent(item.message, maxBodyLength); - const pushPRNumValidation = validateIssueOrPRNumber( - item.pull_request_number, - "push_to_pull_request_branch 'pull_request_number'", - i + 1 - ); - if (!pushPRNumValidation.isValid) { - if (pushPRNumValidation.error) errors.push(pushPRNumValidation.error); - continue; - } - break; - case "create_pull_request_review_comment": - if (!item.path || typeof item.path !== "string") { - errors.push(`Line ${i + 1}: create_pull_request_review_comment requires a 'path' string field`); - continue; - } - const lineValidation = validatePositiveInteger(item.line, "create_pull_request_review_comment 'line'", i + 1); - if (!lineValidation.isValid) { - if (lineValidation.error) errors.push(lineValidation.error); - continue; - } - const lineNumber = lineValidation.normalizedValue; - if (!item.body || typeof item.body !== "string") { - errors.push(`Line ${i + 1}: create_pull_request_review_comment requires a 'body' string field`); - continue; - } - item.body = sanitizeContent(item.body, maxBodyLength); - const startLineValidation = validateOptionalPositiveInteger( - item.start_line, - "create_pull_request_review_comment 'start_line'", - i + 1 - ); - if (!startLineValidation.isValid) { - if (startLineValidation.error) errors.push(startLineValidation.error); - continue; - } - if ( - startLineValidation.normalizedValue !== undefined && - lineNumber !== undefined && - startLineValidation.normalizedValue > lineNumber - ) { - errors.push(`Line ${i + 1}: create_pull_request_review_comment 'start_line' must be less than or equal to 'line'`); - continue; - } - if (item.side !== undefined) { - if (typeof item.side !== "string" || (item.side !== "LEFT" && item.side !== "RIGHT")) { - errors.push(`Line ${i + 1}: create_pull_request_review_comment 'side' must be 'LEFT' or 'RIGHT'`); - continue; - } - } - break; - case "create_discussion": - if (!item.title || typeof item.title !== "string") { - errors.push(`Line ${i + 1}: create_discussion requires a 'title' string field`); - continue; - } - if (!item.body || typeof item.body !== "string") { - errors.push(`Line ${i + 1}: create_discussion requires a 'body' string field`); - continue; - } - if (item.category !== undefined) { - if (typeof item.category !== "string") { - errors.push(`Line ${i + 1}: create_discussion 'category' must be a string`); - continue; - } - item.category = sanitizeContent(item.category, 128); - } - item.title = sanitizeContent(item.title, 128); - item.body = sanitizeContent(item.body, maxBodyLength); - break; - case "create_agent_task": - if (!item.body || typeof item.body !== "string") { - errors.push(`Line ${i + 1}: create_agent_task requires a 'body' string field`); - continue; - } - item.body = sanitizeContent(item.body, maxBodyLength); - break; - case "missing_tool": - if (!item.tool || typeof item.tool !== "string") { - errors.push(`Line ${i + 1}: missing_tool requires a 'tool' string field`); - continue; - } - if (!item.reason || typeof item.reason !== "string") { - errors.push(`Line ${i + 1}: missing_tool requires a 'reason' string field`); - continue; - } - item.tool = sanitizeContent(item.tool, 128); - item.reason = sanitizeContent(item.reason, 256); - if (item.alternatives !== undefined) { - if (typeof item.alternatives !== "string") { - errors.push(`Line ${i + 1}: missing_tool 'alternatives' must be a string`); - continue; - } - item.alternatives = sanitizeContent(item.alternatives, 512); - } - break; - case "upload_asset": - if (!item.path || typeof item.path !== "string") { - errors.push(`Line ${i + 1}: upload_asset requires a 'path' string field`); - continue; - } - break; - case "create_code_scanning_alert": - if (!item.file || typeof item.file !== "string") { - errors.push(`Line ${i + 1}: create_code_scanning_alert requires a 'file' field (string)`); - continue; - } - const alertLineValidation = validatePositiveInteger(item.line, "create_code_scanning_alert 'line'", i + 1); - if (!alertLineValidation.isValid) { - if (alertLineValidation.error) { - errors.push(alertLineValidation.error); - } - continue; - } - if (!item.severity || typeof item.severity !== "string") { - errors.push(`Line ${i + 1}: create_code_scanning_alert requires a 'severity' field (string)`); - continue; - } - if (!item.message || typeof item.message !== "string") { - errors.push(`Line ${i + 1}: create_code_scanning_alert requires a 'message' field (string)`); - continue; - } - const allowedSeverities = ["error", "warning", "info", "note"]; - if (!allowedSeverities.includes(item.severity.toLowerCase())) { - errors.push( - `Line ${i + 1}: create_code_scanning_alert 'severity' must be one of: ${allowedSeverities.join(", ")}, got ${item.severity.toLowerCase()}` - ); - continue; - } - const columnValidation = validateOptionalPositiveInteger(item.column, "create_code_scanning_alert 'column'", i + 1); - if (!columnValidation.isValid) { - if (columnValidation.error) errors.push(columnValidation.error); - continue; - } - if (item.ruleIdSuffix !== undefined) { - if (typeof item.ruleIdSuffix !== "string") { - errors.push(`Line ${i + 1}: create_code_scanning_alert 'ruleIdSuffix' must be a string`); - continue; - } - if (!/^[a-zA-Z0-9_-]+$/.test(item.ruleIdSuffix.trim())) { - errors.push( - `Line ${i + 1}: create_code_scanning_alert 'ruleIdSuffix' must contain only alphanumeric characters, hyphens, and underscores` - ); - continue; - } - } - item.severity = item.severity.toLowerCase(); - item.file = sanitizeContent(item.file, 512); - item.severity = sanitizeContent(item.severity, 64); - item.message = sanitizeContent(item.message, 2048); - if (item.ruleIdSuffix) { - item.ruleIdSuffix = sanitizeContent(item.ruleIdSuffix, 128); - } - break; - default: - const jobOutputType = expectedOutputTypes[itemType]; - if (!jobOutputType) { - errors.push(`Line ${i + 1}: Unknown output type '${itemType}'`); - continue; - } - const safeJobConfig = jobOutputType; - if (safeJobConfig && safeJobConfig.inputs) { - const validation = validateItemWithSafeJobConfig(item, safeJobConfig, i + 1); - if (!validation.isValid) { - errors.push(...validation.errors); - continue; - } - Object.assign(item, validation.normalizedItem); - } - break; - } - core.info(`Line ${i + 1}: Valid ${itemType} item`); - parsedItems.push(item); - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - errors.push(`Line ${i + 1}: Invalid JSON - ${errorMsg}`); - } - } - if (errors.length > 0) { - core.warning("Validation errors found:"); - errors.forEach(error => core.warning(` - ${error}`)); - if (parsedItems.length === 0) { - core.setFailed(errors.map(e => ` - ${e}`).join("\n")); - return; - } - } - for (const itemType of Object.keys(expectedOutputTypes)) { - const minRequired = getMinRequiredForType(itemType, expectedOutputTypes); - if (minRequired > 0) { - const actualCount = parsedItems.filter(item => item.type === itemType).length; - if (actualCount < minRequired) { - errors.push(`Too few items of type '${itemType}'. Minimum required: ${minRequired}, found: ${actualCount}.`); - } - } - } - core.info(`Successfully parsed ${parsedItems.length} valid output items`); - const validatedOutput = { - items: parsedItems, - errors: errors, - }; - const agentOutputFile = "/tmp/gh-aw/agent_output.json"; - const validatedOutputJson = JSON.stringify(validatedOutput); - try { - fs.mkdirSync("/tmp", { recursive: true }); - fs.writeFileSync(agentOutputFile, validatedOutputJson, "utf8"); - core.info(`Stored validated output to: ${agentOutputFile}`); - core.exportVariable("GH_AW_AGENT_OUTPUT", agentOutputFile); - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - core.error(`Failed to write agent output file: ${errorMsg}`); - } - core.setOutput("output", JSON.stringify(validatedOutput)); - core.setOutput("raw_output", outputContent); - const outputTypes = Array.from(new Set(parsedItems.map(item => item.type))); - core.info(`output_types: ${outputTypes.join(", ")}`); - core.setOutput("output_types", outputTypes.join(",")); -} -await main(); diff --git a/pkg/workflow/js/src/compute_text.cjs b/pkg/workflow/js/src/compute_text.cjs deleted file mode 100644 index 1a1d0b4f58d..00000000000 --- a/pkg/workflow/js/src/compute_text.cjs +++ /dev/null @@ -1,114 +0,0 @@ -// @ts-check -/// - -/** - * Sanitizes content for safe output in GitHub Actions - * @param {string} content - The content to sanitize - * @returns {string} The sanitized content - */ -const { sanitizeContent } = require("./lib/sanitize.cjs"); - -async function main() { - let text = ""; - - const actor = context.actor; - const { owner, repo } = context.repo; - - // Check if the actor has repository access (admin, maintain permissions) - const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: owner, - repo: repo, - username: actor, - }); - - const permission = repoPermission.data.permission; - core.info(`Repository permission level: ${permission}`); - - if (permission !== "admin" && permission !== "maintain") { - core.setOutput("text", ""); - return; - } - - // Determine current body text based on event context - switch (context.eventName) { - case "issues": - // For issues: title + body - if (context.payload.issue) { - const title = context.payload.issue.title || ""; - const body = context.payload.issue.body || ""; - text = `${title}\n\n${body}`; - } - break; - - case "pull_request": - // For pull requests: title + body - if (context.payload.pull_request) { - const title = context.payload.pull_request.title || ""; - const body = context.payload.pull_request.body || ""; - text = `${title}\n\n${body}`; - } - break; - - case "pull_request_target": - // For pull request target events: title + body - if (context.payload.pull_request) { - const title = context.payload.pull_request.title || ""; - const body = context.payload.pull_request.body || ""; - text = `${title}\n\n${body}`; - } - break; - - case "issue_comment": - // For issue comments: comment body - if (context.payload.comment) { - text = context.payload.comment.body || ""; - } - break; - - case "pull_request_review_comment": - // For PR review comments: comment body - if (context.payload.comment) { - text = context.payload.comment.body || ""; - } - break; - - case "pull_request_review": - // For PR reviews: review body - if (context.payload.review) { - text = context.payload.review.body || ""; - } - break; - - case "discussion": - // For discussions: title + body - if (context.payload.discussion) { - const title = context.payload.discussion.title || ""; - const body = context.payload.discussion.body || ""; - text = `${title}\n\n${body}`; - } - break; - - case "discussion_comment": - // For discussion comments: comment body - if (context.payload.comment) { - text = context.payload.comment.body || ""; - } - break; - - default: - // Default: empty text - text = ""; - break; - } - - // Sanitize the text before output - const sanitizedText = sanitizeContent(text); - - // Display sanitized text in logs - core.info(`text: ${sanitizedText}`); - - // Set the sanitized text as output - core.setOutput("text", sanitizedText); -} - -await main(); diff --git a/pkg/workflow/js/src/sanitize_output.cjs b/pkg/workflow/js/src/sanitize_output.cjs deleted file mode 100644 index bedaaea03c7..00000000000 --- a/pkg/workflow/js/src/sanitize_output.cjs +++ /dev/null @@ -1,37 +0,0 @@ -// @ts-check -/// - -/** - * Sanitizes content for safe output in GitHub Actions - * @param {string} content - The content to sanitize - * @returns {string} The sanitized content - */ -const { sanitizeContent } = require("./lib/sanitize.cjs"); - -async function main() { - const fs = require("fs"); - const outputFile = process.env.GH_AW_SAFE_OUTPUTS; - if (!outputFile) { - core.info("GH_AW_SAFE_OUTPUTS not set, no output to collect"); - core.setOutput("output", ""); - return; - } - - if (!fs.existsSync(outputFile)) { - core.info(`Output file does not exist: ${outputFile}`); - core.setOutput("output", ""); - return; - } - - const outputContent = fs.readFileSync(outputFile, "utf8"); - if (outputContent.trim() === "") { - core.info("Output file is empty"); - core.setOutput("output", ""); - } else { - const sanitizedContent = sanitizeContent(outputContent); - core.info(`Collected agentic output (sanitized): ${sanitizedContent.substring(0, 200)}${sanitizedContent.length > 200 ? "..." : ""}`); - core.setOutput("output", sanitizedContent); - } -} - -await main(); From b5810cecb84ae8b65e3d69947a8ee6bbffefb400 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:05:49 +0000 Subject: [PATCH 08/13] Fix all references to lib/sanitize.cjs and js/src/ in Go code Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/.github/aw/actions-lock.json | 2 +- pkg/workflow/bundler.go | 2 +- pkg/workflow/bundler_integration_test.go | 20 ++++++++++---------- pkg/workflow/bundler_test.go | 6 +++--- pkg/workflow/js.go | 12 ++++++------ pkg/workflow/js/test.png | 1 + 6 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 pkg/workflow/js/test.png diff --git a/pkg/workflow/.github/aw/actions-lock.json b/pkg/workflow/.github/aw/actions-lock.json index 9f773bc229c..0fdef00a200 100644 --- a/pkg/workflow/.github/aw/actions-lock.json +++ b/pkg/workflow/.github/aw/actions-lock.json @@ -11,4 +11,4 @@ "sha": "2028fbc5c25fe9cf00d9f06a71cc4710d4507903" } } -} +} \ No newline at end of file diff --git a/pkg/workflow/bundler.go b/pkg/workflow/bundler.go index 86f3d0996a0..402f84fd3a5 100644 --- a/pkg/workflow/bundler.go +++ b/pkg/workflow/bundler.go @@ -8,7 +8,7 @@ import ( ) // BundleJavaScriptFromSources bundles JavaScript from in-memory sources -// sources is a map where keys are file paths (e.g., "lib/sanitize.cjs") and values are the content +// sources is a map where keys are file paths (e.g., "sanitize.cjs") and values are the content // mainContent is the main JavaScript content that may contain require() calls // basePath is the base directory path for resolving relative imports (e.g., "js") func BundleJavaScriptFromSources(mainContent string, sources map[string]string, basePath string) (string, error) { diff --git a/pkg/workflow/bundler_integration_test.go b/pkg/workflow/bundler_integration_test.go index 3a91c514e1b..da473e2812b 100644 --- a/pkg/workflow/bundler_integration_test.go +++ b/pkg/workflow/bundler_integration_test.go @@ -27,7 +27,7 @@ func TestBundlerIntegration(t *testing.T) { } // Should not contain the require statement - if strings.Contains(script, `require("./lib/sanitize.cjs")`) { + if strings.Contains(script, `require("./sanitize.cjs")`) { t.Error("bundled script still contains require statement") } @@ -56,7 +56,7 @@ func TestBundlerIntegration(t *testing.T) { } // Should not contain the require statement - if strings.Contains(script, `require("./lib/sanitize.cjs")`) { + if strings.Contains(script, `require("./sanitize.cjs")`) { t.Error("bundled script still contains require statement") } @@ -85,7 +85,7 @@ func TestBundlerIntegration(t *testing.T) { } // Should not contain the require statement - if strings.Contains(script, `require("./lib/sanitize.cjs")`) { + if strings.Contains(script, `require("./sanitize.cjs")`) { t.Error("bundled script still contains require statement") } @@ -316,7 +316,7 @@ func TestSourceFilesAreSmaller(t *testing.T) { } // Source should contain require - if !strings.Contains(tt.source, `require("./lib/sanitize.cjs")`) { + if !strings.Contains(tt.source, `require("./sanitize.cjs")`) { t.Errorf("%s: source should contain require statement", tt.name) } }) @@ -327,20 +327,20 @@ func TestSourceFilesAreSmaller(t *testing.T) { func TestGetJavaScriptSources(t *testing.T) { sources := GetJavaScriptSources() - // Should contain lib/sanitize.cjs - sanitize, ok := sources["lib/sanitize.cjs"] + // Should contain sanitize.cjs + sanitize, ok := sources["sanitize.cjs"] if !ok { - t.Fatal("GetJavaScriptSources does not contain lib/sanitize.cjs") + t.Fatal("GetJavaScriptSources does not contain sanitize.cjs") } // Should not be empty if sanitize == "" { - t.Error("lib/sanitize.cjs source is empty") + t.Error("sanitize.cjs source is empty") } // Should contain sanitizeContent function if !strings.Contains(sanitize, "function sanitizeContent") { - t.Error("lib/sanitize.cjs does not contain sanitizeContent function") + t.Error("sanitize.cjs does not contain sanitizeContent function") } // Should contain helper functions @@ -354,7 +354,7 @@ func TestGetJavaScriptSources(t *testing.T) { for _, helper := range helpers { if !strings.Contains(sanitize, helper) { - t.Errorf("lib/sanitize.cjs does not contain %s", helper) + t.Errorf("sanitize.cjs does not contain %s", helper) } } } diff --git a/pkg/workflow/bundler_test.go b/pkg/workflow/bundler_test.go index 6ea760f6667..46697a055cc 100644 --- a/pkg/workflow/bundler_test.go +++ b/pkg/workflow/bundler_test.go @@ -240,7 +240,7 @@ module.exports = { sanitize }; ` // Create main content that requires the helper from lib - mainContent := `const { sanitize } = require('./lib/sanitize.cjs'); + mainContent := `const { sanitize } = require('./sanitize.cjs'); async function main() { console.log(sanitize(" hello ")); @@ -251,7 +251,7 @@ main(); // Create sources map with nested path sources := map[string]string{ - "lib/sanitize.cjs": helperContent, + "sanitize.cjs": helperContent, } // Bundle the main content @@ -266,7 +266,7 @@ main(); } // Check that the require statement is replaced - if strings.Contains(bundled, "require('./lib/sanitize.cjs')") { + if strings.Contains(bundled, "require('./sanitize.cjs')") { t.Error("Bundled output still contains require statement") } } diff --git a/pkg/workflow/js.go b/pkg/workflow/js.go index 2b9d4a06219..5e477c2477c 100644 --- a/pkg/workflow/js.go +++ b/pkg/workflow/js.go @@ -88,18 +88,18 @@ var redactSecretsScript string //go:embed js/notify_comment_error.cjs var notifyCommentErrorScript string -//go:embed js/lib/sanitize.cjs +//go:embed js/sanitize.cjs var sanitizeLibScript string -// Source scripts that may contain local requires - embedded from src directory +// Source scripts that may contain local requires // -//go:embed js/src/collect_ndjson_output.cjs +//go:embed js/collect_ndjson_output.cjs var collectJSONLOutputScriptSource string -//go:embed js/src/compute_text.cjs +//go:embed js/compute_text.cjs var computeTextScriptSource string -//go:embed js/src/sanitize_output.cjs +//go:embed js/sanitize_output.cjs var sanitizeOutputScriptSource string // Bundled scripts (lazily bundled on-demand and cached) @@ -166,7 +166,7 @@ func getSanitizeOutputScript() string { // The keys are the relative paths from the js directory func GetJavaScriptSources() map[string]string { return map[string]string{ - "lib/sanitize.cjs": sanitizeLibScript, + "sanitize.cjs": sanitizeLibScript, } } diff --git a/pkg/workflow/js/test.png b/pkg/workflow/js/test.png new file mode 100644 index 00000000000..93555af2a41 --- /dev/null +++ b/pkg/workflow/js/test.png @@ -0,0 +1 @@ +fake png data \ No newline at end of file From 12becb9c4870e379afdfc309766d70d631037917 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:06:28 +0000 Subject: [PATCH 09/13] Remove test.png that was accidentally added again Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/test.png | 1 - 1 file changed, 1 deletion(-) delete mode 100644 pkg/workflow/js/test.png diff --git a/pkg/workflow/js/test.png b/pkg/workflow/js/test.png deleted file mode 100644 index 93555af2a41..00000000000 --- a/pkg/workflow/js/test.png +++ /dev/null @@ -1 +0,0 @@ -fake png data \ No newline at end of file From cc16422c0f7ce721fdb3f5cf2ee144b2c062dc20 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:39:39 +0000 Subject: [PATCH 10/13] Merge main and recompile workflows Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/changeset.lock.yml | 88 ++++++++++++++++++++--- pkg/workflow/.github/aw/actions-lock.json | 2 +- 2 files changed, 81 insertions(+), 9 deletions(-) diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index a7d2f1f94be..9f7a3e47436 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -109,8 +109,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -132,8 +134,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -143,10 +145,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -156,6 +179,19 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } @@ -2102,8 +2138,10 @@ jobs: .filter(d => d) : defaultAllowedDomains; let sanitized = content; + sanitized = neutralizeCommands(sanitized); sanitized = neutralizeMentions(sanitized); sanitized = removeXmlComments(sanitized); + sanitized = convertXmlTags(sanitized); sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, ""); sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ""); sanitized = sanitizeUrlProtocols(sanitized); @@ -2125,8 +2163,8 @@ jobs: sanitized = neutralizeBotTriggers(sanitized); return sanitized.trim(); function sanitizeUrlDomains(s) { - s = s.replace(/\bhttps:\/\/([^\/\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, domain) => { - const hostname = domain.split(/[\/:\?#]/)[0].toLowerCase(); + s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { + const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); const isAllowed = allowedDomains.some(allowedDomain => { const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); @@ -2136,10 +2174,31 @@ jobs: return s; } function sanitizeUrlProtocols(s) { - return s.replace(/\b(\w+):(?:\/\/)?[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => { - return protocol.toLowerCase() === "https" ? match : "(redacted)"; + return s.replace(/(?&\x00-\x1f]+/g, (match, protocol) => { + if (protocol.toLowerCase() === "https") { + return match; + } + if (match.includes("::")) { + return match; + } + if (match.includes("://")) { + return "(redacted)"; + } + const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; + if (dangerousProtocols.includes(protocol.toLowerCase())) { + return "(redacted)"; + } + return match; }); } + function neutralizeCommands(s) { + const commandName = process.env.GH_AW_COMMAND; + if (!commandName) { + return s; + } + const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`"); + } function neutralizeMentions(s) { return s.replace( /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, @@ -2149,6 +2208,19 @@ jobs: function removeXmlComments(s) { return s.replace(//g, "").replace(//g, ""); } + function convertXmlTags(s) { + const allowedTags = ["details", "summary", "code", "em", "b"]; + return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { + const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); + if (tagNameMatch) { + const tagName = tagNameMatch[1].toLowerCase(); + if (allowedTags.includes(tagName)) { + return match; + } + } + return `(${tagContent})`; + }); + } function neutralizeBotTriggers(s) { return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``); } diff --git a/pkg/workflow/.github/aw/actions-lock.json b/pkg/workflow/.github/aw/actions-lock.json index 0fdef00a200..9f773bc229c 100644 --- a/pkg/workflow/.github/aw/actions-lock.json +++ b/pkg/workflow/.github/aw/actions-lock.json @@ -11,4 +11,4 @@ "sha": "2028fbc5c25fe9cf00d9f06a71cc4710d4507903" } } -} \ No newline at end of file +} From 3afcbd1eba44301fe4345849c19c1592cc349603 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:41:31 +0000 Subject: [PATCH 11/13] Addressing PR comments Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/test.png | 1 + 1 file changed, 1 insertion(+) create mode 100644 pkg/workflow/js/test.png diff --git a/pkg/workflow/js/test.png b/pkg/workflow/js/test.png new file mode 100644 index 00000000000..93555af2a41 --- /dev/null +++ b/pkg/workflow/js/test.png @@ -0,0 +1 @@ +fake png data \ No newline at end of file From 4143f71a6c5ff26bb5276d72a9c7d16871f7d1dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 17:01:58 +0000 Subject: [PATCH 12/13] Fix all JavaScript tests - CDATA handling, URL filtering, and upload_assets cleanup Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/artifacts-summary.lock.yml | 18 ++- .github/workflows/audit-workflows.lock.yml | 18 ++- .github/workflows/blog-auditor.lock.yml | 18 ++- .github/workflows/brave.lock.yml | 36 +++++- .github/workflows/changeset.lock.yml | 112 +++++++++++++++--- .github/workflows/ci-doctor.lock.yml | 18 ++- .../workflows/cli-version-checker.lock.yml | 18 ++- .../commit-changes-analyzer.lock.yml | 18 ++- .../workflows/copilot-agent-analysis.lock.yml | 18 ++- .../copilot-pr-prompt-analysis.lock.yml | 18 ++- .../copilot-session-insights.lock.yml | 18 ++- .github/workflows/craft.lock.yml | 36 +++++- .github/workflows/daily-doc-updater.lock.yml | 18 ++- .../workflows/daily-firewall-report.lock.yml | 18 ++- .github/workflows/daily-news.lock.yml | 18 ++- .../workflows/daily-perf-improver.lock.yml | 18 ++- .../workflows/daily-repo-chronicle.lock.yml | 18 ++- .../workflows/daily-test-improver.lock.yml | 18 ++- .github/workflows/dev-hawk.lock.yml | 18 ++- .github/workflows/dev.lock.yml | 18 ++- .github/workflows/dictation-prompt.lock.yml | 18 ++- .../duplicate-code-detector.lock.yml | 18 ++- .../example-workflow-analyzer.lock.yml | 18 ++- .../github-mcp-tools-report.lock.yml | 18 ++- .github/workflows/go-logger.lock.yml | 18 ++- .../workflows/go-pattern-detector.lock.yml | 18 ++- .../workflows/instructions-janitor.lock.yml | 18 ++- .github/workflows/issue-classifier.lock.yml | 36 +++++- .github/workflows/lockfile-stats.lock.yml | 18 ++- .github/workflows/mcp-inspector.lock.yml | 18 ++- .github/workflows/mergefest.lock.yml | 18 ++- .../workflows/notion-issue-summary.lock.yml | 18 ++- .github/workflows/pdf-summary.lock.yml | 36 +++++- .github/workflows/plan.lock.yml | 36 +++++- .github/workflows/poem-bot.lock.yml | 36 +++++- .../prompt-clustering-analysis.lock.yml | 18 ++- .github/workflows/python-data-charts.lock.yml | 18 ++- .github/workflows/q.lock.yml | 36 +++++- .github/workflows/repo-tree-map.lock.yml | 18 ++- .github/workflows/research.lock.yml | 18 ++- .github/workflows/safe-output-health.lock.yml | 18 ++- .../schema-consistency-checker.lock.yml | 18 ++- .github/workflows/scout.lock.yml | 36 +++++- .github/workflows/security-fix-pr.lock.yml | 18 ++- .../semantic-function-refactor.lock.yml | 18 ++- .github/workflows/smoke-claude.lock.yml | 18 ++- .github/workflows/smoke-codex.lock.yml | 18 ++- .../workflows/smoke-copilot.firewall.lock.yml | 18 ++- .github/workflows/smoke-copilot.lock.yml | 18 ++- .github/workflows/smoke-detector.lock.yml | 18 ++- .github/workflows/smoke-opencode.lock.yml | 18 ++- .../workflows/technical-doc-writer.lock.yml | 18 ++- .../test-ollama-threat-detection.lock.yml | 18 ++- .github/workflows/tidy.lock.yml | 18 ++- .github/workflows/unbloat-docs.lock.yml | 18 ++- .github/workflows/video-analyzer.lock.yml | 18 ++- .../workflows/weekly-issue-summary.lock.yml | 18 ++- .../zizmor-security-analyzer.lock.yml | 18 ++- pkg/workflow/js/sanitize.cjs | 33 +++++- pkg/workflow/js/sanitize_output.test.cjs | 3 +- pkg/workflow/js/test.png | 1 - pkg/workflow/js/upload_assets.test.cjs | 9 ++ 62 files changed, 1242 insertions(+), 86 deletions(-) delete mode 100644 pkg/workflow/js/test.png diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index 7cc47fdce6a..1dea325e097 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -1640,7 +1640,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1681,6 +1693,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 5ea57f3a849..71b41dcc7e2 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -2118,7 +2118,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2159,6 +2171,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/blog-auditor.lock.yml b/.github/workflows/blog-auditor.lock.yml index fcfb7dbc452..909d3ccfdac 100644 --- a/.github/workflows/blog-auditor.lock.yml +++ b/.github/workflows/blog-auditor.lock.yml @@ -2022,7 +2022,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2063,6 +2075,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 7ff67cc2cd2..5c435d91411 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -189,7 +189,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -230,6 +242,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { @@ -2540,7 +2556,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2581,6 +2609,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/changeset.lock.yml b/.github/workflows/changeset.lock.yml index 9f7a3e47436..d970a9064cf 100644 --- a/.github/workflows/changeset.lock.yml +++ b/.github/workflows/changeset.lock.yml @@ -77,20 +77,68 @@ jobs: text: ${{ steps.compute-text.outputs.text }} steps: - name: Check workflow file timestamps - run: | - WORKFLOW_FILE="${GITHUB_WORKSPACE}/.github/workflows/$(basename "$GITHUB_WORKFLOW" .lock.yml).md" - LOCK_FILE="${GITHUB_WORKSPACE}/.github/workflows/$GITHUB_WORKFLOW" - - if [ -f "$WORKFLOW_FILE" ] && [ -f "$LOCK_FILE" ]; then - if [ "$WORKFLOW_FILE" -nt "$LOCK_FILE" ]; then - echo "🔴🔴🔴 WARNING: Lock file '$LOCK_FILE' is outdated! The workflow file '$WORKFLOW_FILE' has been modified more recently. Run 'gh aw compile' to regenerate the lock file." >&2 - echo "## ⚠️ Workflow Lock File Warning" >> $GITHUB_STEP_SUMMARY - echo "🔴🔴🔴 **WARNING**: Lock file \`$LOCK_FILE\` is outdated!" >> $GITHUB_STEP_SUMMARY - echo "The workflow file \`$WORKFLOW_FILE\` has been modified more recently." >> $GITHUB_STEP_SUMMARY - echo "Run \`gh aw compile\` to regenerate the lock file." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - fi - fi + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + with: + script: | + const fs = require("fs"); + const path = require("path"); + async function main() { + const workspace = process.env.GITHUB_WORKSPACE; + const workflow = process.env.GITHUB_WORKFLOW; + if (!workspace) { + core.setFailed("Configuration error: GITHUB_WORKSPACE not available."); + return; + } + if (!workflow) { + core.setFailed("Configuration error: GITHUB_WORKFLOW not available."); + return; + } + const workflowBasename = path.basename(workflow, ".lock.yml"); + const workflowFile = path.join(workspace, ".github", "workflows", `${workflowBasename}.md`); + const lockFile = path.join(workspace, ".github", "workflows", workflow); + core.info(`Checking workflow timestamps:`); + core.info(` Source: ${workflowFile}`); + core.info(` Lock file: ${lockFile}`); + let workflowExists = false; + let lockExists = false; + try { + fs.accessSync(workflowFile, fs.constants.F_OK); + workflowExists = true; + } catch (error) { + core.info(`Source file does not exist: ${workflowFile}`); + } + try { + fs.accessSync(lockFile, fs.constants.F_OK); + lockExists = true; + } catch (error) { + core.info(`Lock file does not exist: ${lockFile}`); + } + if (!workflowExists || !lockExists) { + core.info("Skipping timestamp check - one or both files not found"); + return; + } + const workflowStat = fs.statSync(workflowFile); + const lockStat = fs.statSync(lockFile); + const workflowMtime = workflowStat.mtime.getTime(); + const lockMtime = lockStat.mtime.getTime(); + core.info(` Source modified: ${workflowStat.mtime.toISOString()}`); + core.info(` Lock modified: ${lockStat.mtime.toISOString()}`); + if (workflowMtime > lockMtime) { + const warningMessage = `🔴🔴🔴 WARNING: Lock file '${lockFile}' is outdated! The workflow file '${workflowFile}' has been modified more recently. Run 'gh aw compile' to regenerate the lock file.`; + core.error(warningMessage); + await core.summary + .addRaw("## ⚠️ Workflow Lock File Warning\n\n") + .addRaw(`🔴🔴🔴 **WARNING**: Lock file \`${lockFile}\` is outdated!\n\n`) + .addRaw(`The workflow file \`${workflowFile}\` has been modified more recently.\n\n`) + .addRaw("Run `gh aw compile` to regenerate the lock file.\n\n") + .write(); + } else { + core.info("✅ Lock file is up to date"); + } + } + main().catch(error => { + core.setFailed(error instanceof Error ? error.message : String(error)); + }); - name: Compute current body text id: compute-text uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd @@ -140,7 +188,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -181,6 +241,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { @@ -2169,7 +2233,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2210,6 +2286,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 5f2f8a60091..5dbe841cb05 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -2088,7 +2088,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2129,6 +2141,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index b816d79a488..4ebffc0b46e 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -1745,7 +1745,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1786,6 +1798,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/commit-changes-analyzer.lock.yml b/.github/workflows/commit-changes-analyzer.lock.yml index 772dac8c302..5af054cd8c7 100644 --- a/.github/workflows/commit-changes-analyzer.lock.yml +++ b/.github/workflows/commit-changes-analyzer.lock.yml @@ -1953,7 +1953,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1994,6 +2006,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 361e472aade..d4822c33a8f 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -2251,7 +2251,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2292,6 +2304,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index d490f9244a3..364ba461e46 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -1986,7 +1986,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2027,6 +2039,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 73b810d428c..876d2e58193 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -2617,7 +2617,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2658,6 +2670,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/craft.lock.yml b/.github/workflows/craft.lock.yml index 42b459c6e42..b9dc12de4bb 100644 --- a/.github/workflows/craft.lock.yml +++ b/.github/workflows/craft.lock.yml @@ -189,7 +189,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -230,6 +242,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { @@ -2692,7 +2708,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2733,6 +2761,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index f45ccfea3c5..100c79cf659 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -1843,7 +1843,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1884,6 +1896,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 71b7a1e4c53..f075d2ef76b 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -1795,7 +1795,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1836,6 +1848,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index c8fdb111b47..f2c02e7473a 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1768,7 +1768,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1809,6 +1821,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/daily-perf-improver.lock.yml b/.github/workflows/daily-perf-improver.lock.yml index 36e21a107bc..83f51b4817b 100644 --- a/.github/workflows/daily-perf-improver.lock.yml +++ b/.github/workflows/daily-perf-improver.lock.yml @@ -2147,7 +2147,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2188,6 +2200,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/daily-repo-chronicle.lock.yml b/.github/workflows/daily-repo-chronicle.lock.yml index ac665cfb6d5..74dee329109 100644 --- a/.github/workflows/daily-repo-chronicle.lock.yml +++ b/.github/workflows/daily-repo-chronicle.lock.yml @@ -1635,7 +1635,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1676,6 +1688,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/daily-test-improver.lock.yml b/.github/workflows/daily-test-improver.lock.yml index f9935fdb9df..e430e48abaf 100644 --- a/.github/workflows/daily-test-improver.lock.yml +++ b/.github/workflows/daily-test-improver.lock.yml @@ -2121,7 +2121,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2162,6 +2174,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index edb1bcd6648..845489dc71f 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -1991,7 +1991,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2032,6 +2044,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index b82e088d74a..b8cfba658f4 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -1540,7 +1540,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1581,6 +1593,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index cbe10581aa7..08c59270fc9 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -1651,7 +1651,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1692,6 +1704,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 32d063a7e2a..ff3b8cfa2e3 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -1715,7 +1715,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1756,6 +1768,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/example-workflow-analyzer.lock.yml b/.github/workflows/example-workflow-analyzer.lock.yml index 18d6f2eebd8..679997d4e51 100644 --- a/.github/workflows/example-workflow-analyzer.lock.yml +++ b/.github/workflows/example-workflow-analyzer.lock.yml @@ -1686,7 +1686,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1727,6 +1739,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index f0d6d895e37..c79ba3a53b1 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -2252,7 +2252,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2293,6 +2305,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index ca31c1abe1f..3300057ab3b 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -1957,7 +1957,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1998,6 +2010,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index 9d1f43fe25d..d8b6c03a9bd 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -1784,7 +1784,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1825,6 +1837,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 10d1fdf91b6..b97772d4dcb 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -1839,7 +1839,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1880,6 +1892,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 2938233e192..9941ee68f38 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -181,7 +181,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -222,6 +234,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { @@ -2229,7 +2245,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2270,6 +2298,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index d16ad3851cc..cec2b9fa236 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -2090,7 +2090,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2131,6 +2143,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 6c3d57aaca6..b7d32e40611 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -2212,7 +2212,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2253,6 +2265,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/mergefest.lock.yml b/.github/workflows/mergefest.lock.yml index 4f37a9a8c8d..f656436888f 100644 --- a/.github/workflows/mergefest.lock.yml +++ b/.github/workflows/mergefest.lock.yml @@ -2197,7 +2197,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2238,6 +2250,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index 323fc12d388..cd5958e35a9 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -1499,7 +1499,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1540,6 +1552,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 703da12b16d..42da6f929d4 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -211,7 +211,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -252,6 +264,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { @@ -2645,7 +2661,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2686,6 +2714,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index e07cf5ddf10..f963b521906 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -190,7 +190,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -231,6 +243,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { @@ -2141,7 +2157,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2182,6 +2210,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 0bff6cf0b2d..84cd88e6e91 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -224,7 +224,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -265,6 +277,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { @@ -2886,7 +2902,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2927,6 +2955,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index d315ff17261..9ef8d917993 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -2425,7 +2425,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2466,6 +2478,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/python-data-charts.lock.yml b/.github/workflows/python-data-charts.lock.yml index 3a62b9b32ee..16832dbc030 100644 --- a/.github/workflows/python-data-charts.lock.yml +++ b/.github/workflows/python-data-charts.lock.yml @@ -1939,7 +1939,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1980,6 +1992,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 03e7fab6248..57dd6380fb8 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -233,7 +233,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -274,6 +286,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { @@ -2953,7 +2969,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2994,6 +3022,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index 9110bc0357f..ac2dbfe70e6 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -1664,7 +1664,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1705,6 +1717,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/research.lock.yml b/.github/workflows/research.lock.yml index 7a893a75b03..71210b78cd3 100644 --- a/.github/workflows/research.lock.yml +++ b/.github/workflows/research.lock.yml @@ -1605,7 +1605,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1646,6 +1658,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index cc56c1148ec..d729be8938a 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -2222,7 +2222,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2263,6 +2275,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 46735871e84..17a398a3240 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -2092,7 +2092,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2133,6 +2145,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 7c19e03b761..53c78743749 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -236,7 +236,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -277,6 +289,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { @@ -3019,7 +3035,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -3060,6 +3088,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index 1f64afd0a2c..7abf7fc7eb6 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -1787,7 +1787,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1828,6 +1840,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 72fe94df74b..14a20cb4a9e 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -2134,7 +2134,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2175,6 +2187,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 0334c4d03fa..a348053cc98 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1635,7 +1635,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1676,6 +1688,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 1a36061719f..abd41a26966 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -1456,7 +1456,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1497,6 +1509,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/smoke-copilot.firewall.lock.yml b/.github/workflows/smoke-copilot.firewall.lock.yml index 0e66fb01dfa..4e319ffcd82 100644 --- a/.github/workflows/smoke-copilot.firewall.lock.yml +++ b/.github/workflows/smoke-copilot.firewall.lock.yml @@ -1528,7 +1528,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1569,6 +1581,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 6ab44aafc2c..07bdf247165 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1528,7 +1528,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1569,6 +1581,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/smoke-detector.lock.yml b/.github/workflows/smoke-detector.lock.yml index c271c4a850d..6be0322f084 100644 --- a/.github/workflows/smoke-detector.lock.yml +++ b/.github/workflows/smoke-detector.lock.yml @@ -2663,7 +2663,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2704,6 +2716,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 5777410de2b..ec9dba7ba5f 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -1492,7 +1492,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1533,6 +1545,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index f3f928fcd91..0b244e81d71 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -2384,7 +2384,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2425,6 +2437,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/test-ollama-threat-detection.lock.yml b/.github/workflows/test-ollama-threat-detection.lock.yml index 8aceb8f151a..01dc66ceb56 100644 --- a/.github/workflows/test-ollama-threat-detection.lock.yml +++ b/.github/workflows/test-ollama-threat-detection.lock.yml @@ -1471,7 +1471,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1512,6 +1524,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 24230c280b9..6c2b1086824 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -2005,7 +2005,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2046,6 +2058,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index a0db0ad4003..dc999ecdf2a 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -2752,7 +2752,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2793,6 +2805,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/video-analyzer.lock.yml b/.github/workflows/video-analyzer.lock.yml index 053f0be9886..7e72cf37d9e 100644 --- a/.github/workflows/video-analyzer.lock.yml +++ b/.github/workflows/video-analyzer.lock.yml @@ -1762,7 +1762,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1803,6 +1815,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/weekly-issue-summary.lock.yml b/.github/workflows/weekly-issue-summary.lock.yml index 43f6cd82c78..32dca5bee89 100644 --- a/.github/workflows/weekly-issue-summary.lock.yml +++ b/.github/workflows/weekly-issue-summary.lock.yml @@ -1537,7 +1537,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -1578,6 +1590,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/.github/workflows/zizmor-security-analyzer.lock.yml b/.github/workflows/zizmor-security-analyzer.lock.yml index 3b484461dd7..99fbc3df813 100644 --- a/.github/workflows/zizmor-security-analyzer.lock.yml +++ b/.github/workflows/zizmor-security-analyzer.lock.yml @@ -2064,7 +2064,19 @@ jobs: const normalizedAllowed = allowedDomain.toLowerCase(); return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; + } + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; + } else { + result += sanitizeUrlDomains(urlParts[i]); + } + } + return result; }); return s; } @@ -2105,6 +2117,10 @@ jobs: } function convertXmlTags(s) { const allowedTags = ["details", "summary", "code", "em", "b"]; + s = s.replace(//g, (match, content) => { + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + return `(![CDATA[${convertedContent}]])`; + }); return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/); if (tagNameMatch) { diff --git a/pkg/workflow/js/sanitize.cjs b/pkg/workflow/js/sanitize.cjs index 744a0ec143e..b3bd394eac8 100644 --- a/pkg/workflow/js/sanitize.cjs +++ b/pkg/workflow/js/sanitize.cjs @@ -85,7 +85,8 @@ function sanitizeContent(content, maxLength) { * @returns {string} The string with unknown domains redacted */ function sanitizeUrlDomains(s) { - // Match full HTTPS URLs including path, query, and fragment + // First pass: match all HTTPS URLs and process them + // We need to handle URLs that might contain other URLs in query parameters s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => { // Extract the hostname part (before first slash, colon, or other delimiter) const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase(); @@ -96,7 +97,26 @@ function sanitizeContent(content, maxLength) { return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed); }); - return isAllowed ? match : "(redacted)"; + if (isAllowed) { + return match; // Keep allowed URLs as-is + } + + // For disallowed URLs, check if there are any allowed URLs in the query/fragment + // and preserve those while redacting the main URL + const urlParts = match.split(/([?&#])/); + let result = "(redacted)"; // Redact the main domain + + // Process query/fragment parts to preserve any allowed URLs within them + for (let i = 1; i < urlParts.length; i++) { + if (urlParts[i].match(/^[?&#]$/)) { + result += urlParts[i]; // Keep separators + } else { + // Recursively process this part to preserve any allowed URLs + result += sanitizeUrlDomains(urlParts[i]); + } + } + + return result; }); return s; @@ -191,10 +211,17 @@ function sanitizeContent(content, maxLength) { // Allow safe HTML tags: details, summary, code, em, b const allowedTags = ["details", "summary", "code", "em", "b"]; + // First, process CDATA sections specially - convert tags inside them and the CDATA markers + s = s.replace(//g, (match, content) => { + // Convert tags inside CDATA content + const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)"); + // Return with CDATA markers also converted to parentheses + return `(![CDATA[${convertedContent}]])`; + }); + // Convert opening tags: or to (tag) or (tag attr="value") // Convert closing tags: to (/tag) // Convert self-closing tags: or to (tag/) or (tag /) - // Also handle special tags like // But preserve allowed safe tags return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => { // Extract tag name from the content (handle closing tags and attributes) diff --git a/pkg/workflow/js/sanitize_output.test.cjs b/pkg/workflow/js/sanitize_output.test.cjs index 3fd58630b2d..154c734ba4d 100644 --- a/pkg/workflow/js/sanitize_output.test.cjs +++ b/pkg/workflow/js/sanitize_output.test.cjs @@ -459,7 +459,8 @@ Special chars: \x00\x1F & "quotes" 'apostrophes' expect(result).toContain("(xml attr=\"value & 'quotes'\")"); expect(result).toContain('(![CDATA[(script)alert("xss")(/script)]])'); - expect(result).toContain("(!-- comment with \"quotes\" & 'apostrophes' --)"); + // XML comments are removed for security (to prevent content hiding) + expect(result).not.toContain("comment with"); expect(result).toContain("(/xml)"); }); diff --git a/pkg/workflow/js/test.png b/pkg/workflow/js/test.png deleted file mode 100644 index 93555af2a41..00000000000 --- a/pkg/workflow/js/test.png +++ /dev/null @@ -1 +0,0 @@ -fake png data \ No newline at end of file diff --git a/pkg/workflow/js/upload_assets.test.cjs b/pkg/workflow/js/upload_assets.test.cjs index cb3ef4ebc11..643b02a2ae5 100644 --- a/pkg/workflow/js/upload_assets.test.cjs +++ b/pkg/workflow/js/upload_assets.test.cjs @@ -71,6 +71,12 @@ describe("upload_assets.cjs", () => { describe("git commit command - vulnerability fix", () => { it("should not wrap commit message in extra quotes to prevent command injection", async () => { + // Clean up any leftover test.png from previous runs + const targetFile = "test.png"; + if (fs.existsSync(targetFile)) { + fs.unlinkSync(targetFile); + } + // Set up environment process.env.GH_AW_ASSETS_BRANCH = "assets/test-workflow"; process.env.GH_AW_SAFE_OUTPUTS_STAGED = "false"; @@ -162,6 +168,9 @@ describe("upload_assets.cjs", () => { if (fs.existsSync(assetPath)) { fs.unlinkSync(assetPath); } + if (fs.existsSync(targetFile)) { + fs.unlinkSync(targetFile); + } }); }); From c41cbb991b7028f5ca10160a50fa6b345099fbfc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Nov 2025 17:17:14 +0000 Subject: [PATCH 13/13] Fix linting error - handle ExtractExpressions return values properly Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/js/check_workflow_timestamp.cjs | 2 +- pkg/workflow/js/sanitize.cjs | 18 +++++++++--------- pkg/workflow/mcp_servers.go | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/workflow/js/check_workflow_timestamp.cjs b/pkg/workflow/js/check_workflow_timestamp.cjs index bb8fe998c01..07a77f37979 100644 --- a/pkg/workflow/js/check_workflow_timestamp.cjs +++ b/pkg/workflow/js/check_workflow_timestamp.cjs @@ -69,7 +69,7 @@ async function main() { // Check if workflow file is newer than lock file if (workflowMtime > lockMtime) { const warningMessage = `🔴🔴🔴 WARNING: Lock file '${lockFile}' is outdated! The workflow file '${workflowFile}' has been modified more recently. Run 'gh aw compile' to regenerate the lock file.`; - + core.error(warningMessage); // Add summary to GitHub Step Summary diff --git a/pkg/workflow/js/sanitize.cjs b/pkg/workflow/js/sanitize.cjs index b3bd394eac8..00fb78ac0d3 100644 --- a/pkg/workflow/js/sanitize.cjs +++ b/pkg/workflow/js/sanitize.cjs @@ -100,12 +100,12 @@ function sanitizeContent(content, maxLength) { if (isAllowed) { return match; // Keep allowed URLs as-is } - + // For disallowed URLs, check if there are any allowed URLs in the query/fragment // and preserve those while redacting the main URL const urlParts = match.split(/([?&#])/); let result = "(redacted)"; // Redact the main domain - + // Process query/fragment parts to preserve any allowed URLs within them for (let i = 1; i < urlParts.length; i++) { if (urlParts[i].match(/^[?&#]$/)) { @@ -115,7 +115,7 @@ function sanitizeContent(content, maxLength) { result += sanitizeUrlDomains(urlParts[i]); } } - + return result; }); @@ -138,23 +138,23 @@ function sanitizeContent(content, maxLength) { if (protocol.toLowerCase() === "https") { return match; } - + // Allow if it looks like a file path or namespace (::) if (match.includes("::")) { return match; } - + // Redact if it has :// (definite protocol) if (match.includes("://")) { return "(redacted)"; } - + // Redact well-known dangerous protocols like javascript:, data:, etc. const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"]; if (dangerousProtocols.includes(protocol.toLowerCase())) { return "(redacted)"; } - + // Otherwise preserve (could be file:path, namespace:thing, etc.) return match; }); @@ -210,7 +210,7 @@ function sanitizeContent(content, maxLength) { function convertXmlTags(s) { // Allow safe HTML tags: details, summary, code, em, b const allowedTags = ["details", "summary", "code", "em", "b"]; - + // First, process CDATA sections specially - convert tags inside them and the CDATA markers s = s.replace(//g, (match, content) => { // Convert tags inside CDATA content @@ -218,7 +218,7 @@ function sanitizeContent(content, maxLength) { // Return with CDATA markers also converted to parentheses return `(![CDATA[${convertedContent}]])`; }); - + // Convert opening tags: or to (tag) or (tag attr="value") // Convert closing tags: to (/tag) // Convert self-closing tags: or to (tag/) or (tag /) diff --git a/pkg/workflow/mcp_servers.go b/pkg/workflow/mcp_servers.go index 63e9c0750db..899f6ffd148 100644 --- a/pkg/workflow/mcp_servers.go +++ b/pkg/workflow/mcp_servers.go @@ -426,7 +426,7 @@ func replaceExpressionsInPlaywrightArgs(args []string, expressions map[string]st // Create a temporary extractor with the same mappings combined := strings.Join(args, "\n") extractor := NewExpressionExtractor() - extractor.ExtractExpressions(combined) + _, _ = extractor.ExtractExpressions(combined) // Replace expressions in the combined string replaced := extractor.ReplaceExpressionsWithEnvVars(combined)