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)