mirror of
https://github.com/teamchong/pxpipe.git
synced 2026-07-22 02:02:51 +02:00
fix(dashboard): escape model id in hx-vals to prevent injection/XSS (#73)
The model id is interpolated into the single-quoted hx-vals attribute. A crafted id (reachable as an `active` model from any /v1/messages request on the unauthenticated localhost dashboard) could break out of the JSON or the attribute itself. Build valid JSON via JSON.stringify, then escapeHtml the whole attribute value; the browser decodes the entities before htmx reads it, so well-formed ids are unaffected. Closes #58. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
50ca4725df
commit
7da9b26363
@@ -124,7 +124,7 @@ export function renderModelsFragment(
|
||||
return (
|
||||
`<button class="chip${lit ? ' on' : ''}" type="button" ` +
|
||||
`hx-post="/fragments/models" hx-target="#frag-models" ` +
|
||||
`hx-vals='{"model":"${id}","on":${!lit}}'>${escapeHtml(label)}${lit ? ' ✓' : ''}</button>`
|
||||
`hx-vals='${escapeHtml(`{"model":${JSON.stringify(id)},"on":${!lit}}`)}'>${escapeHtml(label)}${lit ? ' ✓' : ''}</button>`
|
||||
);
|
||||
};
|
||||
const claudeChips = ids.filter((id) => id.startsWith('claude')).map(chipFor).join('');
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* A crafted model id (attacker-controlled: it rides in as an `active` model
|
||||
* from a /v1/messages request on unauthenticated localhost) must not be able
|
||||
* to break out of the single-quoted hx-vals attribute or inject extra JSON
|
||||
* keys. See issue #58.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { renderModelsFragment } from '../src/dashboard/fragments.js';
|
||||
|
||||
describe('renderModelsFragment hx-vals escaping', () => {
|
||||
it('escapes crafted model ids so they cannot break out of the attribute', () => {
|
||||
// Double-quote → JSON injection; single-quote → attribute breakout.
|
||||
const evil = `foo","evil":"bar' onmouseover='alert(1)`;
|
||||
const html = renderModelsFragment([evil], [], true);
|
||||
|
||||
// Raw quotes from the id never reach the attribute unescaped.
|
||||
expect(html).not.toContain(`"model":"${evil}"`);
|
||||
expect(html).not.toContain(`bar' onmouseover=`);
|
||||
|
||||
// A well-formed id still renders its JSON value (as escaped entities that
|
||||
// the browser decodes back to valid JSON before htmx reads them).
|
||||
const ok = renderModelsFragment(['claude-fable-5'], [], true);
|
||||
expect(ok).toContain('"model":"claude-fable-5"');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user