Skip to content

🔒 fix: patch CodeQL XSS (#1) and path-injection (#2) alerts - #6

Merged
luiscosio merged 1 commit into
mainfrom
fix/code-scanning
Jul 24, 2026
Merged

🔒 fix: patch CodeQL XSS (#1) and path-injection (#2) alerts#6
luiscosio merged 1 commit into
mainfrom
fix/code-scanning

Conversation

@luiscosio

Copy link
Copy Markdown
Contributor

Resolves the two open CodeQL code-scanning alerts. Independent of PRs #4/#5; branched from current origin/main.

Alert #2js/path-injection (server.js:89) — FIXED

The static handler guarded traversal with !filePath.startsWith(publicDir) over a path.join result. That guard doesn't decode %2e%2e or collapse encoded traversal, so it's known-weak. Now the handler:

  • decodes the URL and strips the query/fragment (new URL(...).pathname + decodeURIComponent)
  • path.posix.normalizes the path, resolves it against publicDir with path.resolve
  • rejects (403) anything that escapes publicDir

Malformed encoding returns 400.

Alert #1js/xss (public/index.html:605) — FIXED (real, not a false positive)

appendAuditEntry built the row via innerHTML. Most values passed through esc(), but the attacker-controlled entry.source was concatenated raw into a class attribute:

const sourceClass = 'audit-source-' + source;   // no escaping
... '<span class="audit-source ' + sourceClass + '">' ...

source originates from the unauthenticated POST /api/events body, so a crafted value like "><img src=x onerror=...> breaks out of the attribute. (esc() is textContentinnerHTML, which doesn't escape quotes, so wrapping wouldn't have fully fixed the attribute context either.)

Fix: rebuild the row with createElement + textContent (no innerHTML), and restrict the source-derived class to a safe token ([^a-zA-Z0-9_-]-). Legitimate source classes (audit-source-agent-gateway, -agent-platform, -user) are unchanged, so styling is preserved.

Verification

  • npm test25/25 pass
  • Traversal probes (raw --path-as-is): /../server.js, /../../server.js, /%2e%2e/server.js, /..%2f..%2fserver.js, /../translate.js, /../../etc/passwd → all 404, no source leaked. Normal assets (/, /index.html, /topology.js, /theme.css, /registry-panel.js, /./index.html) → 200.
  • XSS: injected an audit entry with source='"><img src=x onerror=...>' and a <script> summary → no script executed, 0 injected img/script/b elements, values rendered as inert text, legit class styling intact.

No inline suppressions added; no existing behavior weakened.

… static server

Resolves two CodeQL code-scanning alerts.

js/xss (public/index.html): appendAuditEntry built the audit row via
innerHTML, and the attacker-controlled `entry.source` was concatenated
raw into a class attribute (`sourceClass`) with no escaping. A crafted
`source` could break out of the attribute and inject markup. Rebuild the
row with createElement + textContent so untrusted values can never be
parsed as HTML, and restrict the source-derived CSS class to a safe token.

js/path-injection (server.js): serveStatic relied on a startsWith guard
over a path.join result, which does not decode %2e%2e or collapse encoded
traversal. Decode the URL, strip the query, normalize, resolve against
publicDir, and reject anything escaping the root.

Verified: npm test 25/25; traversal probes (/../server.js, /%2e%2e/server.js,
encoded variants) all 404 while normal assets serve; injected audit payload
renders as inert text with no script execution.
@luiscosio
luiscosio merged commit 8d59e92 into main Jul 24, 2026
4 checks passed
@luiscosio
luiscosio deleted the fix/code-scanning branch July 24, 2026 18:31
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.

1 participant