Skip to content

fix(security): external content marker sanitization bypass via invisible characters#2

Open
cubic-dev-ai[bot] wants to merge 1 commit into
mainfrom
fix/external-content-marker-sanitization-bypass
Open

fix(security): external content marker sanitization bypass via invisible characters#2
cubic-dev-ai[bot] wants to merge 1 commit into
mainfrom
fix/external-content-marker-sanitization-bypass

Conversation

@cubic-dev-ai

@cubic-dev-ai cubic-dev-ai Bot commented Mar 26, 2026

Copy link
Copy Markdown

Summary

  • Fix index mismatch in replaceMarkers where regex match indices from the folded (invisible-chars-stripped) string were applied to the original string, allowing attackers to bypass marker sanitization by prepending invisible Unicode characters (e.g. \u200B)
  • The function now performs replacements directly on the folded string, eliminating the index offset vulnerability entirely
  • Add test coverage for the invisible character bypass scenario

Root Cause

replaceMarkers called foldMarkerText(content) to strip invisible characters and normalize homoglyphs, then ran regex matching on the resulting shorter folded string. However, the match indices were used to slice() the original (longer) content string. An attacker injecting N invisible characters before a spoofed boundary marker would shift the replacement window by N characters, leaving the actual marker intact.

Fix

Perform regex replacements directly on the folded string instead of mapping indices back to the original. When a marker is detected, the folded string (with replacements applied) is returned, not the original.

Test plan

  • Existing 45 tests continue to pass
  • 3 new tests covering the bypass scenario:
    • Markers preceded by many zero-width spaces
    • Both start and end markers preceded by invisible chars
    • Markers with mixed invisible chars throughout
  • pnpm check (lint + format + type-check) passes
  • pnpm tsgo passes

🤖 Generated with Claude Code

…rkers

The replaceMarkers function computed regex match indices on the folded
(invisible-chars-stripped) string but applied them to the original string
via slice(). An attacker could prepend invisible characters (e.g. U+200B)
to shift indices and leave spoofed boundary markers intact.

Fix: perform replacements directly on the folded string instead of mapping
indices back to the original, eliminating the index mismatch entirely.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/security/external-content.ts">

<violation number="1" location="src/security/external-content.ts:208">
P2: Restore the check to return the original `content` when no markers are replaced. Without this, the function irreversibly strips invisible characters and normalizes homoglyphs across the entire message just because it contains the phrase 'external untrusted content'.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

}
output += content.slice(cursor);
return output;
return sanitized;

@cubic-dev-ai cubic-dev-ai Bot Mar 26, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Restore the check to return the original content when no markers are replaced. Without this, the function irreversibly strips invisible characters and normalizes homoglyphs across the entire message just because it contains the phrase 'external untrusted content'.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/security/external-content.ts, line 208:

<comment>Restore the check to return the original `content` when no markers are replaced. Without this, the function irreversibly strips invisible characters and normalizes homoglyphs across the entire message just because it contains the phrase 'external untrusted content'.</comment>

<file context>
@@ -191,53 +191,21 @@ function foldMarkerText(input: string): string {
-  }
-  output += content.slice(cursor);
-  return output;
+  return sanitized;
 }
 
</file context>

[internal] Confidence score: 10/10

[internal] Posted by: General AI Review Agent

Suggested change
return sanitized;
return sanitized === folded ? content : sanitized;
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants