Skip to content

Commit 0d3eb35

Browse files
simongonzalezdcSinter Testclaude
authored
fix(tone): strip any-language code fences so 'html' never leaks into art (#688)
**F13 from the investor-audit register.** The tone fence stripper's language allowlist omitted `html`, so a ```html fence after the DOCTYPE lost only its backticks — leaking a literal `html` token that rendered as visible text beside the start button in the live proof artifact. The existing leading-`html` band-aid (`normalizeToneArtifact`) is anchored without `/m` and misses mid-document residue. Fix: fence-opening lines strip generically (`/^\`\`\`[ \t]*[\w-]*[ \t]*\r?\n?/gm`). Closing-fence handling unchanged. Verification: ToneGenerator suite 15/15; the new regression reproduces the exact observed artifact shape and **fails against the old regex** (stash-verified red-green); eslint clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Sinter Test <liminal@example.test> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 5eeb692 commit 0d3eb35

2 files changed

Lines changed: 26 additions & 2 deletions

File tree

src/generators/tone/ToneGenerator.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ export class ToneGenerator extends TierBasedGenerator {
9090

9191
let clean = code;
9292

93-
// Strip markdown code fences (only at start/end, preserve code inside)
94-
clean = clean.replace(/^```(?:javascript|js|typescript|ts)?\n?/gm, '');
93+
// Strip markdown code fences (only at start/end, preserve code inside).
94+
// The language tag must stay GENERIC: an allowlist that omitted `html`
95+
// ate only the backticks of a ```html fence and leaked a bare "html"
96+
// token into rendered artifacts (investor-audit F13).
97+
clean = clean.replace(/^```[ \t]*[\w-]*[ \t]*\r?\n?/gm, '');
9598
clean = clean.replace(/\n?```$/gm, '');
9699
clean = clean.replace(/^```$/gm, '');
97100

test/unit/generators/ToneGenerator.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,27 @@ describe('ToneGenerator', () => {
8888
expect(result).toContain('Tone.Synth');
8989
});
9090

91+
it('sanitizeCode strips html-tagged fences without leaking the language word (F13)', async () => {
92+
// Regression: a ```html fence after the DOCTYPE used to lose only its
93+
// backticks (language allowlist omitted html), leaving a bare "html"
94+
// line that rendered as visible text beside the artifact's start button
95+
// (live-creative-domains tone proof, 2026-06-10). The leading-"html"
96+
// band-aid in normalizeToneArtifact is anchored without /m and misses
97+
// this mid-document residue.
98+
mockComplete.mockResolvedValueOnce({
99+
text: '<!DOCTYPE html>\n```html\n<html><head><meta charset="UTF-8"><title>Tone.js Patch</title></head><body><button id="start">Start</button><script>document.getElementById("start").onclick=async()=>{await Tone.start();Tone.Transport.bpm.value=84;new Tone.Loop((time)=>new Tone.Synth().toDestination().triggerAttackRelease("C4","8n",time),"4n").start(0);Tone.Transport.start();};</script></body></html>\n```',
100+
success: true,
101+
});
102+
103+
const gen = new ToneGenerator();
104+
const result = await gen.generate('fenced ambient synth');
105+
106+
expect(result).not.toMatch(/^html$/m);
107+
expect(result).not.toContain('```');
108+
expect(result).toContain('Tone.Transport.bpm.value=84');
109+
expect(result).toContain('<html>');
110+
});
111+
91112
it('sanitizeCode strips think tags', async () => {
92113
mockComplete.mockResolvedValue({ text: '', success: true });
93114
mockToolLoop.mockResolvedValueOnce({

0 commit comments

Comments
 (0)