Skip to content

[Security] LOW: Stored XSS via unescaped case names in quick-start dropdown #18

@noahwaldner

Description

@noahwaldner

Severity: Low

File: src/web/public/app.js:9311

Description

Case names returned from /api/cases are inserted into <option> elements without HTML escaping:

cases.forEach(c => {
  const displayName = c.name.length > maxNameLength
    ? c.name.substring(0, maxNameLength) + '…'
    : c.name;
  options += `<option value="${c.name}">${displayName}</option>`;
  //                          ^^^^^^^    ^^^^^^^^^^^
  //                     both missing escapeHtml()
});

escapeHtml() exists in the codebase and is used correctly elsewhere. Case names come from directory entries in ~/codeman-cases/ and from keys in ~/.codeman/linked-cases.json.

Note: The existing CSP includes 'unsafe-inline' in script-src, so CSP does not block inline script execution.

Proof of Concept

Linux (where <> are valid in directory names):

mkdir ~/codeman-cases/'"><img src=x onerror=alert(document.cookie)>'

Cross-platform (via ~/.codeman/linked-cases.json):

{
  "<img src=x onerror=alert(document.cookie)>": "/tmp/legit-path"
}

Reload the Codeman UI — XSS fires when the quick-start dropdown renders.

Remediation

Apply escapeHtml() to both interpolation points:

options += `<option value="${this.escapeHtml(c.name)}">${this.escapeHtml(displayName)}</option>`;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions