fix(ui): mask multi-line sensitive values in CodeEditor

Extend the sensitive-key decoration to cover the full logical value, not
just the rest of the current line. Adds value-extent detection for:

- TOML triple-quoted strings (multi-line literal/basic strings)
- TOML arrays ([ ... ] across multiple lines, string-aware)
- YAML block scalars (| and > with indent-based termination)
- JSON arrays and objects (balanced braces, string-aware)

Without this, a secret value spanning multiple lines (multi-line API
key, certificate, private key) was only blurred on its opening line and
the rest stayed readable when the Eye toggle was masked.

Adds regression tests for the TOML triple-quote and array cases.
Flagged by PR-Agent review on #1248.
This commit is contained in:
Tam Nhu Tran committed 2026-05-14 17:38:06 -04:00
1 parent 0ce01d4c65
commit eefb76214e
2 files changed
+172 -19

No files matched your search

@@ -97,4 +97,39 @@ describe('CodeEditor', () => {
render(<CodeEditor value={'{ "broken": '} onChange={vi.fn()} language="json" />);
expect(screen.queryByText('Valid JSON')).not.toBeInTheDocument();
});
it('masks the entire multi-line TOML triple-quoted secret value', () => {
const value = [
'name = "demo"',
'API_KEY = """',
'secret-line-one',
'secret-line-two',
'"""',
'other = "visible"',
].join('\n');
const { container } = render(<CodeEditor value={value} onChange={vi.fn()} language="toml" />);
const masks = container.querySelectorAll('.cm-sensitive-mask');
expect(masks.length).toBeGreaterThan(0);
const masked = Array.from(masks)
.map((el) => el.textContent ?? '')
.join('');
expect(masked).toContain('secret-line-one');
expect(masked).toContain('secret-line-two');
expect(masked).not.toContain('visible');
});
it('masks an entire multi-line TOML array value bound to a sensitive key', () => {
const value = ['AUTH_TOKEN = [', ' "first",', ' "second",', ']', 'visible = "ok"'].join('\n');
const { container } = render(<CodeEditor value={value} onChange={vi.fn()} language="toml" />);
const masked = Array.from(container.querySelectorAll('.cm-sensitive-mask'))
.map((el) => el.textContent ?? '')
.join('');
expect(masked).toContain('"first"');
expect(masked).toContain('"second"');
expect(masked).not.toContain('ok');
});
});