fix: extract sanitizeError to shared module and apply to ask-user-questions (#1033)

Closes a security gap where ask-user-questions errorResult() could leak
tokens in error messages. The sanitizeError function and TOKEN_PATTERNS
are now in shared/sanitize.ts, imported by both manager.ts and
ask-user-questions.ts.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-17 18:30:05 -06:00 committed by GitHub
parent 4407c24522
commit edda01e438
5 changed files with 24 additions and 20 deletions

View file

@ -10,6 +10,7 @@
*/
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
import { sanitizeError } from "./shared/sanitize.js";
import { Text } from "@gsd/pi-tui";
import { Type } from "@sinclair/typebox";
import {
@ -80,7 +81,7 @@ function errorResult(
questions: Question[] = [],
): { content: { type: "text"; text: string }[]; details: AskUserQuestionsDetails } {
return {
content: [{ type: "text", text: message }],
content: [{ type: "text", text: sanitizeError(message) }],
details: { questions, response: null, cancelled: true },
};
}

View file

@ -5,7 +5,7 @@ import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { parseSlackReply, parseDiscordResponse, formatForDiscord, formatForSlack, parseSlackReactionResponse, formatForTelegram, parseTelegramResponse } from "../../remote-questions/format.ts";
import { resolveRemoteConfig, isValidChannelId } from "../../remote-questions/config.ts";
import { sanitizeError } from "../../remote-questions/manager.ts";
import { sanitizeError } from "../../shared/sanitize.ts";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

View file

@ -9,6 +9,7 @@ import { DiscordAdapter } from "./discord-adapter.js";
import { SlackAdapter } from "./slack-adapter.js";
import { TelegramAdapter } from "./telegram-adapter.js";
import { createPromptRecord, writePromptRecord, markPromptAnswered, markPromptDispatched, markPromptStatus, updatePromptRecord } from "./store.js";
import { sanitizeError } from "../shared/sanitize.js";
interface ToolResult {
content: Array<{ type: "text"; text: string }>;
@ -177,23 +178,6 @@ function formatForTool(answer: RemoteAnswer): Record<string, { answers: string[]
return out;
}
// Strip token-like strings from error messages before surfacing
const TOKEN_PATTERNS = [
/xoxb-[A-Za-z0-9\-]+/g, // Slack bot tokens
/xoxp-[A-Za-z0-9\-]+/g, // Slack user tokens
/xoxa-[A-Za-z0-9\-]+/g, // Slack app tokens
/\d{8,10}:[A-Za-z0-9_-]{35}/g, // Telegram bot tokens
/[A-Za-z0-9_\-.]{20,}/g, // Long opaque secrets (Discord tokens, etc.)
];
export function sanitizeError(msg: string): string {
let sanitized = msg;
for (const pattern of TOKEN_PATTERNS) {
sanitized = sanitized.replace(pattern, "[REDACTED]");
}
return sanitized;
}
function errorResult(message: string, channel: string): ToolResult {
return {
content: [{ type: "text", text: sanitizeError(message) }],

View file

@ -9,7 +9,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { getGlobalGSDPreferencesPath, loadEffectiveGSDPreferences } from "../gsd/preferences.js";
import { getRemoteConfigStatus, isValidChannelId, resolveRemoteConfig } from "./config.js";
import { sanitizeError } from "./manager.js";
import { sanitizeError } from "../shared/sanitize.js";
import { getLatestPromptSummary } from "./status.js";
export async function handleRemote(

View file

@ -0,0 +1,19 @@
/**
* Sanitize error messages by redacting token-like strings before surfacing.
*/
const TOKEN_PATTERNS = [
/xoxb-[A-Za-z0-9\-]+/g, // Slack bot tokens
/xoxp-[A-Za-z0-9\-]+/g, // Slack user tokens
/xoxa-[A-Za-z0-9\-]+/g, // Slack app tokens
/\d{8,10}:[A-Za-z0-9_-]{35}/g, // Telegram bot tokens
/[A-Za-z0-9_\-.]{20,}/g, // Long opaque secrets (Discord tokens, etc.)
];
export function sanitizeError(msg: string): string {
let sanitized = msg;
for (const pattern of TOKEN_PATTERNS) {
sanitized = sanitized.replace(pattern, "[REDACTED]");
}
return sanitized;
}