Skip to content
Merged
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
8 changes: 4 additions & 4 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1205,11 +1205,11 @@ The shared GitHub HTTP client uses an explicit 10-second submission timeout by d

### Heuristic source code guard (not a security boundary)

The description, context, and optional tool invocation context fields pass through `SourceCodeDetector` before storage and optional GitHub submission. This heuristic rejects common pasted code patterns (multi-line blocks, fenced code, import runs, function definitions) but intentionally allows short inline code examples so gap descriptions remain useful. It is **not a security boundary** — a determined agent could bypass it. The guard is a best-effort filter to catch accidental code inclusion, not a guarantee that no code-like text will ever be transmitted.
The description, context, and optional tool invocation context fields pass through `SourceCodeDetector` before storage and optional GitHub submission. This heuristic rejects common pasted code patterns (multi-line blocks, backtick or tilde fenced code, import runs, function definitions) but intentionally allows short inline code examples so gap descriptions remain useful. Rejections return a bounded `source_code_rejection` object with the rejected field name and stable `reason_code`; they do not echo the rejected text. It is **not a security boundary** — a determined agent could bypass it. The guard is a best-effort filter to catch accidental code inclusion, not a guarantee that no code-like text will ever be transmitted.

### SourceCodeDetector design

`SourceCodeDetector` uses five independent heuristics to reject text that looks like pasted source code. Each heuristic is implemented as a clearly named private method with detailed comments explaining what it detects and why. The class is designed for readability: anyone reviewing the open-source code can verify the detection logic and confirm that no source code passes through.
`SourceCodeDetector` uses six independent heuristics to reject text that looks like pasted source code. Each heuristic is implemented as a clearly named private method with detailed comments explaining what it detects and why, and maps to a stable reason code such as `statement-ending`, `indented-code-lines`, `block-structure`, `repeated-imports`, `function-definition`, or `fenced-code-block`. The class is designed for readability: anyone reviewing the open-source code can verify the detection logic and confirm that no source code passes through.

The detector intentionally allows short inline code examples (e.g. `` `const foo = () => {}` ``) and only rejects multi-line code blocks. False negatives (missing some code) are acceptable; false positives (rejecting valid descriptions) are not.

Expand Down Expand Up @@ -3435,11 +3435,11 @@ upstream Issue を作成する前に、`GitHubIssueReporter` は同じ SHA256

### ヒューリスティックなソースコードガード(セキュリティ境界ではない)

description、context、および任意の tool invocation context フィールドは、保存およびオプションの GitHub 送信前に `SourceCodeDetector` を通過する。このヒューリスティックは一般的なコードコピペパターン(複数行ブロック、フェンスドコード、import の連打、関数定義)を拒否するが、ギャップの説明として有用な短いインラインコード例は意図的に許容する。これは**セキュリティ境界ではない** — 意図的に回避しようとするエージェントは回避できる。このガードはコードの誤混入を防ぐベストエフォートのフィルタであり、コード的テキストが一切送信されないことの保証ではない。
description、context、および任意の tool invocation context フィールドは、保存およびオプションの GitHub 送信前に `SourceCodeDetector` を通過する。このヒューリスティックは一般的なコードコピペパターン(複数行ブロック、バッククォートまたはチルダのフェンスドコード、import の連打、関数定義)を拒否するが、ギャップの説明として有用な短いインラインコード例は意図的に許容する。拒否時は、拒否対象フィールド名と安定した `reason_code` を持つ上限付きの `source_code_rejection` object を返し、拒否された本文は反映しない。これは**セキュリティ境界ではない** — 意図的に回避しようとするエージェントは回避できる。このガードはコードの誤混入を防ぐベストエフォートのフィルタであり、コード的テキストが一切送信されないことの保証ではない。

### SourceCodeDetector の設計

`SourceCodeDetector` は5つの独立したヒューリスティックを使って、コピペされたソースコードに見えるテキストを拒否する。各ヒューリスティックは明確な名前の private メソッドとして実装され、何を検出し、なぜそれがソースコードの兆候なのかを詳細なコメントで説明している。可読性を重視して設計されており、オープンソースのコードをレビューする誰もが検出ロジックを検証し、ソースコードが通過しないことを確認できる。
`SourceCodeDetector` は6つの独立したヒューリスティックを使って、コピペされたソースコードに見えるテキストを拒否する。各ヒューリスティックは明確な名前の private メソッドとして実装され、何を検出し、なぜそれがソースコードの兆候なのかを詳細なコメントで説明している。また、`statement-ending`、`indented-code-lines`、`block-structure`、`repeated-imports`、`function-definition`、`fenced-code-block` のような安定した理由コードに対応する。可読性を重視して設計されており、オープンソースのコードをレビューする誰もが検出ロジックを検証し、ソースコードが通過しないことを確認できる。

短いインラインコード例(例: `` `const foo = () => {}` ``)は意図的に許容し、複数行のコードブロックのみを拒否する。偽陰性(一部のコードの見逃し)は許容する。偽陽性(有効な説明の拒否)は許容しない。

Expand Down
4 changes: 2 additions & 2 deletions USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2343,7 +2343,7 @@ The MCP `tools/list` response includes an `examples` array for every registered
| `backfill_fold` | Upgrade folded-name keys in an existing DB without reparsing source files |
| `suggest_improvement` | Submit structured improvement suggestions or error reports |

`suggest_improvement` always stores accepted suggestions locally. Its response includes `submitted_to_github` and `github_submission_reason` so clients can distinguish `submitted`, `token_not_configured`, `repo_not_configured`, `network_error`, and `api_error`; failed GitHub attempts also include `github_submission_error`.
`suggest_improvement` always stores accepted suggestions locally. Its response includes `submitted_to_github` and `github_submission_reason` so clients can distinguish `submitted`, `token_not_configured`, `repo_not_configured`, `network_error`, and `api_error`; failed GitHub attempts also include `github_submission_error`. If the source-code guard rejects `description`, `context`, or `toolInvocationContext`, the error `structuredContent` includes `source_code_rejection.field` and `source_code_rejection.reason_code` without echoing the rejected text.

The MCP `index` tool returns a `diagnostics` object when non-fatal indexing problems occur. It includes category counts and up to 50 bounded items for recoverable indexing errors and skipped file-size measurements; item paths are project-relative when possible, and messages are redacted and bounded so permission or path failures can be acted on without leaking local absolute paths or token-shaped values.

Expand Down Expand Up @@ -4876,7 +4876,7 @@ OpenAI Codex CLI (`codex.json` または `~/.codex/config.json`):
| `backfill_fold` | 既存 DB の folded-name key をソース再解析なしで更新 |
| `suggest_improvement` | 構造化された改善提案またはエラー報告を送信 |

`suggest_improvement` は受理した提案を常にローカル保存します。応答には `submitted_to_github` と `github_submission_reason` が含まれ、クライアントは `submitted`、`token_not_configured`、`repo_not_configured`、`network_error`、`api_error` を区別できます。GitHub 送信に失敗した場合は `github_submission_error` も含まれます。
`suggest_improvement` は受理した提案を常にローカル保存します。応答には `submitted_to_github` と `github_submission_reason` が含まれ、クライアントは `submitted`、`token_not_configured`、`repo_not_configured`、`network_error`、`api_error` を区別できます。GitHub 送信に失敗した場合は `github_submission_error` も含まれます。ソースコードガードが `description`、`context`、または `toolInvocationContext` を拒否した場合、エラーの `structuredContent` には拒否された本文を反映せずに `source_code_rejection.field` と `source_code_rejection.reason_code` が含まれます。

MCP の `index` tool は、致命的ではない indexing 問題が発生した場合に `diagnostics` object を返します。recoverable な indexing error と file-size 測定 skip について category count と最大 50 件の bounded item を含み、path は可能な限り project-relative、message は redaction と上限適用済みなので、local absolute path や token 風の値を漏らさず permission / path 問題を判断できます。

Expand Down
18 changes: 18 additions & 0 deletions changelog.d/unreleased/3830.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
category: fixed
issues:
- 3830
affected:
- src/CodeIndex/Cli/SourceCodeDetector.cs
- src/CodeIndex/Mcp/McpToolHandlers.cs
- USER_GUIDE.md
- DEVELOPER_GUIDE.md
---

## English

- **Source-code guard rejections now report stable reasons without echoing source text (#3830)** — `suggest_improvement` source-code guard errors now include a bounded `source_code_rejection.reason_code`, and the detector recognizes tilde Markdown fences in addition to backtick fences.

## 日本語

- **ソースコードガードの拒否がソース本文を反映せず安定した理由を返すようになりました (#3830)** — `suggest_improvement` のソースコードガードエラーは上限付きの `source_code_rejection.reason_code` を含むようになり、detector はバッククォート fence に加えてチルダの Markdown fence も認識します。
101 changes: 81 additions & 20 deletions src/CodeIndex/Cli/SourceCodeDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@

namespace CodeIndex.Cli;

/// <summary>
/// Source-code detection result with a stable reason code for diagnostics.
/// 診断用の安定した理由コードを持つソースコード検出結果。
/// </summary>
public readonly record struct SourceCodeDetectionResult(bool ContainsSourceCode, string? ReasonCode)
{
public static SourceCodeDetectionResult NoSourceCode { get; } = new(false, null);

public static SourceCodeDetectionResult Detected(string reasonCode) => new(true, reasonCode);
}

/// <summary>
/// Detects whether a text string likely contains source code.
/// テキスト文字列にソースコードが含まれている可能性があるかを検出する。
Expand Down Expand Up @@ -87,6 +98,13 @@ public static class SourceCodeDetector
/// </summary>
private const int ConsecutiveImportThreshold = 3;

public const string ReasonStatementEnding = "statement-ending";
public const string ReasonIndentedCodeLines = "indented-code-lines";
public const string ReasonBlockStructure = "block-structure";
public const string ReasonRepeatedImports = "repeated-imports";
public const string ReasonFunctionDefinition = "function-definition";
public const string ReasonFencedCodeBlock = "fenced-code-block";

// ---------------------------------------------------------------
// Public API / 公開API
// ---------------------------------------------------------------
Expand All @@ -101,35 +119,43 @@ public static class SourceCodeDetector
/// </summary>
/// <param name="text">The text to inspect / 検査するテキスト</param>
/// <returns>true if source code is likely present / ソースコードが含まれる可能性がある場合 true</returns>
public static bool ContainsSourceCode(string text)
public static bool ContainsSourceCode(string? text) => Detect(text).ContainsSourceCode;

/// <summary>
/// Returns a detection result with a stable reason code when source code is likely present.
/// ソースコードが含まれる可能性がある場合、安定した理由コード付きの検出結果を返す。
/// </summary>
/// <param name="text">The text to inspect / 検査するテキスト</param>
/// <returns>Detection result / 検出結果</returns>
public static SourceCodeDetectionResult Detect(string? text)
{
if (string.IsNullOrWhiteSpace(text))
return false;
return SourceCodeDetectionResult.NoSourceCode;

// Run each heuristic independently.
// Any single match is sufficient to flag the text.
// 各ヒューリスティックを独立して実行する。
// 1つでもマッチすればテキストをフラグする。

if (HasCodeStatementPattern(text))
return true;
return SourceCodeDetectionResult.Detected(ReasonStatementEnding);

if (HasConsecutiveCodeLines(text))
return true;
return SourceCodeDetectionResult.Detected(ReasonIndentedCodeLines);

if (HasBlockStructure(text))
return true;
return SourceCodeDetectionResult.Detected(ReasonBlockStructure);

if (HasRepeatedImports(text))
return true;
return SourceCodeDetectionResult.Detected(ReasonRepeatedImports);

if (HasMultiLineFunctionDefinition(text))
return true;
return SourceCodeDetectionResult.Detected(ReasonFunctionDefinition);

if (HasFencedCodeBlock(text))
return true;
return SourceCodeDetectionResult.Detected(ReasonFencedCodeBlock);

return false;
return SourceCodeDetectionResult.NoSourceCode;
}

// ---------------------------------------------------------------
Expand Down Expand Up @@ -522,43 +548,78 @@ private static bool IsFunctionDefinitionLine(string trimmedLine)
/// </summary>
private static bool HasFencedCodeBlock(string text)
{
bool inFence = false;
char activeMarker = '\0';
int activeMarkerLength = 0;
int contentLines = 0;

foreach (var rawLine in EnumerateLines(text))
{
var trimmed = rawLine.Trim();

// Check for fence delimiter (``` with optional language tag)
// フェンス区切り(```+任意の言語タグ)を検査
if (trimmed.StartsWith("```", StringComparison.Ordinal))
// Check for fence delimiter (``` or ~~~ with optional language tag)
// フェンス区切り(``` または ~~~+任意の言語タグ)を検査
if (TryReadMarkdownFence(rawLine, out var marker, out var markerLength))
{
if (!inFence)
if (activeMarker == '\0')
{
// Opening fence / 開始フェンス
inFence = true;
activeMarker = marker;
activeMarkerLength = markerLength;
contentLines = 0;
}
else
else if (marker == activeMarker && markerLength >= activeMarkerLength)
{
// Closing fence — if there was at least 1 content line,
// this is a fenced code block.
// 閉じフェンス — 内容行が1行以上あれば、
// フェンスドコードブロックである。
if (contentLines >= 1)
return true;
inFence = false;
activeMarker = '\0';
activeMarkerLength = 0;
}
else
{
contentLines++;
}
continue;
}

if (inFence && trimmed.Length > 0)
if (activeMarker != '\0' && rawLine.Trim().Length > 0)
contentLines++;
}

return false;
}

private static bool TryReadMarkdownFence(string line, out char marker, out int markerLength)
{
marker = '\0';
markerLength = 0;
var index = 0;
while (index < line.Length && line[index] is ' ' or '\t')
index++;
if (index >= line.Length)
return false;

var candidate = line[index];
if (candidate is not ('`' or '~'))
return false;

while (index < line.Length && line[index] == candidate)
{
markerLength++;
index++;
}

if (markerLength < 3)
{
markerLength = 0;
return false;
}

marker = candidate;
return true;
}

private static IEnumerable<string> EnumerateLines(string text)
{
var start = 0;
Expand Down
Loading
Loading