Skip to content

fix(security): complete #183 — XSS output encoding + legacy AI key purge#188

Merged
Vishnu2707 merged 1 commit into
devfrom
fix/183-followup
Jul 15, 2026
Merged

fix(security): complete #183 — XSS output encoding + legacy AI key purge#188
Vishnu2707 merged 1 commit into
devfrom
fix/183-followup

Conversation

@m-khan-97

@m-khan-97 m-khan-97 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #183, addressing two residual issues @ritiksah141 flagged in review after it merged. Both are real; neither warranted reverting #183, so this patches forward.

1. XSS — toEmbedUrl() returned the raw input, not the validated URL

website/script.js's embed fallback correctly validated the host via new URL(raw) against an allowlist, but then returned raw — the unmodified input. On an allowed host, an attribute-injection payload still passes:

https://www.youtube.com/embed/abc" onload="alert(1)

hostname is www.youtube.com (passes the allowlist), but the literal " survives and breaks out of the iframe src="..." attribute when interpolated. The earlier "rejected" tests only covered disallowed hosts, so this class was never exercised.

Fix: return parsed.href instead of raw. The WHATWG URL serializer percent-encodes quotes, spaces, and angle brackets, so the returned value is safe to interpolate. Legitimate embed URLs are unaffected (they contain no characters that get encoded).

2. Legacy plaintext key never purged (#180 regression)

The in-memory-key switch removed the localStorage.setItem('ai_api_key', ...) write but never removed keys older builds had already persisted. Any user who configured a key before that build keeps a plaintext ai_api_key in localStorage indefinitely — the exact leak #180 set out to close.

Fix: localStorage.removeItem('ai_api_key') once on module load (deliberately not read into memory) and again in clear() as defence-in-depth.

Verification

  • Both regression tests were confirmed to fail against the current dev code before the fix and pass after (not just added green).
  • node website/test_toEmbedUrl.mjs — passes (2 new attribute-injection-on-allowed-host cases)
  • node frontend/src/utils/aiApi.test.mjs — passes (3 new legacy-key migration cases)
  • npm run lint — 0 errors (4 pre-existing warnings in untouched files)

Note

This is the miss I flagged in the #183 post-merge review: my approval checked the host-validation gate but not the value flowing out of it. These tests close that specific gap. cc @ritiksah141 @Vishnu2707

Two residual issues from #183's merge, found in post-merge review:

- toEmbedUrl() validated the host via new URL() but returned the raw
  input, so an attribute-injection payload on an allowed host (e.g.
  https://www.youtube.com/embed/abc" onload="alert(1)) passed validation
  and kept its literal double-quote, breaking out of the iframe src="..."
  attribute. Now returns parsed.href, which percent-encodes quotes,
  spaces, and brackets.
- The switch to in-memory key storage removed the localStorage write but
  never purged keys that older builds had already stored, so existing
  users kept a plaintext ai_api_key indefinitely. Added a one-time purge
  on module load (not read into memory) and in clear().

Both fixes ship with regression tests confirmed to fail against the
pre-fix code and pass against the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@Vishnu2707 Vishnu2707 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the fixes. LGTM!

@Vishnu2707
Vishnu2707 merged commit 6df5918 into dev Jul 15, 2026
19 checks passed
@Vishnu2707
Vishnu2707 deleted the fix/183-followup branch July 15, 2026 00:48
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.

2 participants