Hall of Fame image from https://openclipart.org/detail/120343/trophy
Back to Hall of Fame Contents Back to Wekan Website

Contents / SpliceBleed

CVE Vulnerability name Date Responsible Security Disclosure by Vulnerabilities
-

SpliceBleed

2026-07-22 GitHub CodeQL (code scanning alert #425) and xet7 (fix)

Automated code scanning flagged the incomplete multi-character sanitization.
  • 1. SpliceBleed — incomplete multi-character sanitization when stripping exploit markup from a shown filename (CWE-116 Improper Encoding or Escaping of Output)
  • GitHub CodeQL code scanning alert #425, rule js/incomplete-multi-character-sanitization, severity High
  • Affected the WeKan filename display helper (imports/lib/fileNameDisplay.js)
  • Fixed at upcoming WeKan release


Details

1. SpliceBleed — incomplete multi-character sanitization when stripping filename markup (CWE-116)

Every filename WeKan shows anywhere (card attachment lists, download headers) is passed through cleanFileName() in imports/lib/fileNameDisplay.js, which calls stripExploitPatterns() to remove HTML/script/XML/template-injection markup so the displayed name is always plain, readable text. The original helper applied each removal exactly once, in a single pass:

// vulnerable — a single pass of chained removals
function stripExploitPatterns(s) {
  return String(s == null ? '' : s)
    .replace(/<\?[\s\S]*?(\?>|$)/g, '')                  // <?php … ?>
    .replace(/<!\[[\s\S]*?(\]>|$)/g, '')                 // <![CDATA[ … ]]>
    .replace(/\{\{[\s\S]*?\}\}|\$\{[\s\S]*?\}|<%[\s\S]*?%>/g, '')  // templates
    .replace(/<[^>]*>?/g, '')                         // any HTML/XML tag
    .replace(/(javascript|vbscript|data)\s*:/gi, '')           // dangerous schemes
    .replace(/[<>]/g, '');                               // stray angle brackets
}
    

A single pass is not enough when the removal of an INNER fragment splices two surviving OUTER fragments into a brand-new dangerous token. For example the tag-stripping step /<[^>]*>?/g matches the shortest <…> run, so given <scr<x>ipt> it removes the inner <x> and leaves <script> — a live tag the pass has already moved past. The template step does the same: <scr{{y}}ipt> becomes <script> once the {{y}} is deleted. This is exactly the weakness GitHub CodeQL reports as js/incomplete-multi-character-sanitization: "this string may still contain <script". It is the same class of defect as EscapeBleed and InputBleed (incomplete sanitization).

In WeKan this is defence-in-depth rather than a live cross-site-scripting hole: Blaze {{ }} interpolation HTML-escapes every rendered filename, so a residual <script> in the string is shown as literal text, never executed. The single-pass strip was nonetheless genuinely incomplete — the whole point of stripExploitPatterns() is that the stored/displayed text itself is plain, and a crafted name could defeat that.

Fix: apply the removals REPEATEDLY until the string stops changing (a fixpoint loop). Each pass only ever deletes text, so the string strictly shrinks and the loop always terminates; any dangerous token that an earlier removal reveals is caught on the next iteration.

// fixed — loop until the string stops changing (a fixpoint)
function stripExploitPatterns(s) {
  let out = String(s == null ? '' : s);
  let prev;
  do {
    prev = out;
    out = out
      .replace(/<\?[\s\S]*?(\?>|$)/g, '')
      .replace(/<!\[[\s\S]*?(\]>|$)/g, '')
      .replace(/\{\{[\s\S]*?\}\}|\$\{[\s\S]*?\}|<%[\s\S]*?%>/g, '')
      .replace(/<[^>]*>?/g, '')
      .replace(/(javascript|vbscript|data)\s*:/gi, '')
      .replace(/[<>]/g, '');
  } while (out !== prev);
  return out;
}
    

After the fix, <scr<x>ipt> and <scr{{y}}ipt> both reduce to empty/plain text and no <script survives. CodeQL rule js/incomplete-multi-character-sanitization was resolved by this change, and the tests/fileNameDisplay.test.cjs suite pins the splice cases as regressions.