feat(DIS-102): sanitizeForDiscord strips paths and tokens before Discord send #46

Merged
dev merged 4 commits from phase-1/sanitize-for-discord into main 2026-04-10 08:05:44 +00:00
2 changed files with 38 additions and 5 deletions
Showing only changes of commit 714e0c01e5 - Show all commits

View file

@ -107,30 +107,43 @@ export async function routeMessage(
message.author.username message.author.username
); );
// Sanitize the full response once, then use the sanitized version
// for both Discord output and conversation history storage.
// This prevents a feedback loop where raw paths in stored history
// get fed back to the agent as context, causing it to reproduce them.
const sanitizeOpts = {
repoRoot: workspace.workspace_path,
home: os.homedir(),
};
const sanitizedResponse = sanitizeForDiscord(response, sanitizeOpts);
// Split and send response // Split and send response
const chunks = splitMessage(response); const chunks = splitMessage(sanitizedResponse);
let firstMsgId: string | null = null; let firstMsgId: string | null = null;
for (const chunk of chunks) { for (const chunk of chunks) {
const sent = await channel.send(sanitizeForDiscord(chunk, { home: os.homedir() })); const sent = await channel.send(chunk);
if (!firstMsgId) { if (!firstMsgId) {
firstMsgId = sent.id; firstMsgId = sent.id;
} }
} }
// Store assistant response (use first message id for dedup) // Store sanitized assistant response (use first message id for dedup)
if (firstMsgId) { if (firstMsgId) {
db.addConversation( db.addConversation(
workspace.id, workspace.id,
firstMsgId, firstMsgId,
"assistant", "assistant",
response, sanitizedResponse,
null null
); );
} }
} catch (error) { } catch (error) {
const msg = error instanceof Error ? error.message : "Unbekannter Fehler"; const msg = error instanceof Error ? error.message : "Unbekannter Fehler";
const safeMsg = sanitizeForDiscord(`Fehler bei der Verarbeitung: ${msg}`, { home: os.homedir() }); const safeMsg = sanitizeForDiscord(`Fehler bei der Verarbeitung: ${msg}`, {
repoRoot: workspace.workspace_path,
home: os.homedir(),
});
await channel.send(safeMsg).catch(() => {}); await channel.send(safeMsg).catch(() => {});
} finally { } finally {
clearInterval(typingInterval); clearInterval(typingInterval);

View file

@ -64,4 +64,24 @@ describe("sanitizeForDiscord", () => {
}); });
expect(result).toBe("<disclaw>/src/index.ts and ~/other.ts"); expect(result).toBe("<disclaw>/src/index.ts and ~/other.ts");
}); });
// Windows-style backslash paths are sanitized
it("replaces Windows backslash paths", () => {
const text = "File at C:\\Users\\dev\\.disclaw\\workspaces\\agent-x\\src\\main.ts";
const result = sanitizeForDiscord(text, {
repoRoot: "C:\\Users\\dev\\.disclaw\\workspaces\\agent-x",
home: "C:\\Users\\dev",
});
expect(result).toBe("File at <disclaw>\\src\\main.ts");
});
// Mixed slash styles (common on Windows when tools output forward slashes)
it("replaces forward-slash variants of Windows paths", () => {
const text = "File at C:/Users/dev/.disclaw/workspaces/agent-x/src/main.ts";
const result = sanitizeForDiscord(text, {
repoRoot: "C:\\Users\\dev\\.disclaw\\workspaces\\agent-x",
home: "C:\\Users\\dev",
});
expect(result).toBe("File at <disclaw>/src/main.ts");
});
}); });