Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/github/utils/sanitizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ export function sanitizeContent(content: string): string {
return content;
}

export function sanitizeOutgoingCommentContent(content: string): string {
content = stripInvisibleCharacters(content);
content = redactGitHubTokens(content);
return content;
}

export function redactGitHubTokens(content: string): string {
// GitHub Personal Access Tokens (classic): ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (40 chars)
content = content.replace(
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/github-comment-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { z } from "zod";
import { GITHUB_API_URL } from "../github/api/config";
import { Octokit } from "@octokit/rest";
import { updateClaudeComment } from "../github/operations/comments/update-claude-comment";
import { sanitizeContent } from "../github/utils/sanitizer";
import { sanitizeOutgoingCommentContent } from "../github/utils/sanitizer";

// Get repository information from environment variables
const REPO_OWNER = process.env.REPO_OWNER;
Expand Down Expand Up @@ -55,7 +55,7 @@ server.tool(
const isPullRequestReviewComment =
eventName === "pull_request_review_comment";

const sanitizedBody = sanitizeContent(body);
const sanitizedBody = sanitizeOutgoingCommentContent(body);

const result = await updateClaudeComment(octokit, {
owner,
Expand Down
6 changes: 3 additions & 3 deletions src/mcp/github-inline-comment-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { appendFileSync } from "fs";
import { z } from "zod";
import { createOctokit } from "../github/api/client";
import { sanitizeContent } from "../github/utils/sanitizer";
import { sanitizeOutgoingCommentContent } from "../github/utils/sanitizer";

// Get repository and PR information from environment variables
const REPO_OWNER = process.env.REPO_OWNER;
Expand Down Expand Up @@ -97,8 +97,8 @@ server.tool(
const repo = REPO_NAME;
const pull_number = parseInt(PR_NUMBER, 10);

// Sanitize the comment body to remove any potential GitHub tokens
const sanitizedBody = sanitizeContent(body);
// Preserve review suggestions exactly while still redacting accidental tokens.
const sanitizedBody = sanitizeOutgoingCommentContent(body);

// Validate that either line or both startLine and line are provided
if (!line && !startLine) {
Expand Down
25 changes: 25 additions & 0 deletions test/sanitizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
stripHiddenAttributes,
normalizeHtmlEntities,
sanitizeContent,
sanitizeOutgoingCommentContent,
stripHtmlComments,
redactGitHubTokens,
} from "../src/github/utils/sanitizer";
Expand Down Expand Up @@ -243,6 +244,30 @@ describe("sanitizeContent", () => {
});
});

describe("sanitizeOutgoingCommentContent", () => {
it("preserves suggestion blocks with quoted attributes", () => {
const body = `**Drop the trailing full-stop.**

\`\`\`suggestion
<Tooltip title="We'll do the thing" placement="top" class="inline-flex items-center">
\`\`\`
`;

expect(sanitizeOutgoingCommentContent(body)).toBe(body);
});

it("redacts tokens without stripping visible suggestion text", () => {
const token = "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";
const body = `\`\`\`suggestion
<Tooltip title="We'll do the thing" data-token="${token}">
\`\`\``;

expect(sanitizeOutgoingCommentContent(body)).toBe(`\`\`\`suggestion
<Tooltip title="We'll do the thing" data-token="[REDACTED_GITHUB_TOKEN]">
\`\`\``);
});
});

describe("redactGitHubTokens", () => {
it("should redact personal access tokens (ghp_)", () => {
const token = "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";
Expand Down