Skip to content

Commit b0ba3fc

Browse files
authored
fix: close XPIA steganographic channel in allowedAliases sanitization path (#28055)
1 parent 66b4cd8 commit b0ba3fc

2 files changed

Lines changed: 31 additions & 4 deletions

File tree

actions/setup/js/sanitize_content.cjs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@ function sanitizeContent(content, maxLengthOrOptions) {
9696
// preventing the full <!--...--> pattern from being matched.
9797
sanitized = applyToNonCodeRegions(sanitized, removeXmlComments);
9898

99-
// Remove markdown link titles — a steganographic injection channel analogous to HTML comments.
100-
// Quoted title text ([text](url "TITLE") and [ref]: url "TITLE") is invisible in GitHub's
101-
// rendered markdown (shown only as hover-tooltips) but reaches the AI model verbatim.
102-
// Must run before mention neutralization for the same ordering reason as removeXmlComments.
99+
// Neutralize markdown link titles as a hidden/steganographic injection channel analogous to
100+
// HTML comments: inline-link titles are made visible in link text, while reference-style
101+
// titles are stripped. Quoted title text ([text](url "TITLE") and [ref]: url "TITLE") is
102+
// invisible in GitHub's rendered markdown (shown only as hover-tooltips) but reaches the AI
103+
// model verbatim. Must run before mention neutralization for the same ordering reason as
104+
// removeXmlComments.
103105
sanitized = applyToNonCodeRegions(sanitized, neutralizeMarkdownLinkTitles);
104106

105107
// Neutralize @mentions with selective filtering (custom logic for allowed aliases)

actions/setup/js/sanitize_content.test.cjs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,31 @@ describe("sanitize_content.cjs", () => {
378378
const result = sanitizeContent('[text](https://github.com "@exploituser inject payload")');
379379
expect(result).toBe("[text (`@exploituser` inject payload)](https://github.com)");
380380
});
381+
382+
it("should neutralize markdown link titles when allowedAliases is specified (XPIA regression)", () => {
383+
// Regression: neutralizeMarkdownLinkTitles must run in the allowedAliases branch too.
384+
// Previously the title was passed through unchanged when allowedAliases were provided.
385+
// The title is moved into the visible link text (no longer steganographic), not stripped.
386+
const result = sanitizeContent('[Result](https://github.com "XPIA: inject")', { allowedAliases: ["author"] });
387+
expect(result).toBe("[Result (XPIA: inject)](https://github.com)");
388+
});
389+
390+
it("should strip reference-style link titles when allowedAliases is specified", () => {
391+
const result = sanitizeContent('[x][ref]\n\n[ref]: https://github.com "hidden payload"', {
392+
allowedAliases: ["author"],
393+
});
394+
expect(result).not.toContain("hidden payload");
395+
expect(result).toBe("[x][ref]\n\n[ref]: https://github.com");
396+
});
397+
398+
it("should neutralize link title @mentions via allowedAliases path without exposing the title steganographically", () => {
399+
// The title @mention must be moved into visible link text and then selectively filtered.
400+
// The allowed alias should remain un-neutralized after being moved to visible text.
401+
const result = sanitizeContent('[text](https://github.com "@author inject")', {
402+
allowedAliases: ["author"],
403+
});
404+
expect(result).toBe("[text (@author inject)](https://github.com)");
405+
});
381406
});
382407

383408
describe("XML/HTML tag conversion", () => {

0 commit comments

Comments
 (0)