Skip to content

Commit bf32bad

Browse files
test: parity and input oracles
1 parent bcde11d commit bf32bad

2 files changed

Lines changed: 406 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Part A — Golden-parity: JS renderer path for the adversarial bash tool_result.
3+
*
4+
* The Python md-exporter path is covered by
5+
* ``tests/test_dual_path_parity_oracle.py``. Both suites use the same
6+
* adversarial payload (``<img src=x onerror=alert(1)>``).
7+
*
8+
* These tests drive the **real** JS dispatch path via ``renderToolResult``
9+
* (registry.js) and the direct renderer ``renderBashResult`` (bash.js) — no
10+
* reimplementations.
11+
*
12+
* Oracle discipline (test-review C3/C8/C9):
13+
* Every assertion checks the real observable HTML output, never merely that
14+
* the function does not throw.
15+
*/
16+
17+
import { describe, it, expect } from 'vitest';
18+
import { renderToolResult } from '../registry.js';
19+
import { renderBashResult } from './bash.js';
20+
21+
// Shared adversarial payload — must match the Python constant in
22+
// tests/test_dual_path_parity_oracle.py.
23+
const PAYLOAD = '<img src=x onerror=alert(1)>';
24+
25+
// Parsed bash dict that mirrors what ``_parse_tool_result`` / ``_tool_result_build_bash``
26+
// would produce for the adversarial blob.
27+
const ADVERSARIAL_PARSED = {
28+
result_type: 'bash',
29+
slug: null,
30+
stdout: PAYLOAD,
31+
stderr: '',
32+
exit_code: 0,
33+
interrupted: false,
34+
is_error: false,
35+
return_code_interpretation: 'success',
36+
};
37+
38+
describe('Part A: golden-parity — adversarial bash result (JS renderer path)', () => {
39+
it('renders a non-empty HTML string for the adversarial payload', () => {
40+
// Oracle: output must not be empty — the payload is not silently dropped.
41+
const html = renderToolResult(ADVERSARIAL_PARSED);
42+
expect(typeof html).toBe('string');
43+
expect(html.length).toBeGreaterThan(0);
44+
});
45+
46+
it('HTML-escapes the adversarial payload — raw XSS tag must not appear', () => {
47+
// Oracle: the raw ``<img src=x onerror=`` string must be absent (escaped);
48+
// the HTML-entity form must be present.
49+
//
50+
// Negative control: if esc() were removed from renderBashResult, the raw
51+
// tag WOULD appear in the output and ``not.toContain`` would fail —
52+
// a regressed renderer cannot pass this test.
53+
const html = renderToolResult(ADVERSARIAL_PARSED);
54+
expect(html).not.toContain('<img src=x onerror=');
55+
expect(html).toContain('&lt;img src=x onerror=alert(1)&gt;');
56+
});
57+
58+
it('routes through the real renderBashResult dispatch path — no re-implementation', () => {
59+
// Driving both the registry dispatch (renderToolResult) and the direct
60+
// renderer (renderBashResult) confirms they produce the same output.
61+
// If registry.js ever wired a different renderer for result_type 'bash',
62+
// this equality assertion would fail.
63+
const viaRegistry = renderToolResult(ADVERSARIAL_PARSED);
64+
const viaDirect = renderBashResult(ADVERSARIAL_PARSED);
65+
expect(viaRegistry).toBe(viaDirect);
66+
});
67+
68+
it('negative control: the raw payload is a dangerous string without escaping', () => {
69+
// Documents why esc() is load-bearing: PAYLOAD itself contains the
70+
// executable XSS vector. If the renderer passed it through unchanged,
71+
// the browser could execute it.
72+
expect(PAYLOAD).toContain('<img');
73+
expect(PAYLOAD).toContain('onerror=');
74+
expect(PAYLOAD).not.toContain('&lt;');
75+
76+
// The renderer must NOT pass PAYLOAD through unchanged.
77+
const html = renderBashResult(ADVERSARIAL_PARSED);
78+
expect(html).not.toContain(PAYLOAD);
79+
expect(html).toContain('&lt;');
80+
});
81+
});

0 commit comments

Comments
 (0)