Files
gbrain/eval/generators/world-html.test.ts
T
Garry TanandClaude Opus 4.7 f0649e2f32 feat(eval): Phase 3 world.html explorer + eval:* CLI surface
Contributor DX magical moment. Static HTML explorer renders the full
canonical world (240 entities) as an explorable tree, opens in any browser,
zero install. Every string HTML-entity-encoded (XSS-safe — direct vuln
class per eng pass 2, confidence 9/10).

Added:
  eval/generators/world-html.ts         — renderer (~240 LOC; single-file
                                          HTML with inline CSS + minimal JS)
  eval/generators/world-html.test.ts    — 16 tests (XSS + rendering correctness)
  eval/cli/world-view.ts                — render + open in default browser
  eval/cli/query-validate.ts            — CLI wrapper for queries/validator
  eval/cli/query-new.ts                 — scaffold a query template

Modified:
  package.json                          — 7 new eval:* scripts
  .gitignore                            — ignore generated world.html

package.json scripts shipped:
  bun run test:eval                 all eval unit tests (57 pass)
  bun run eval:run                  full 4-adapter N=5 side-by-side
  bun run eval:run:dev              N=1 fast dev iteration
  bun run eval:world:view           render world.html + open in browser
  bun run eval:world:render         render only (CI-friendly, --no-open)
  bun run eval:query:validate       validate built-in T5+T5.5 (or a file path)
  bun run eval:query:new            scaffold a new Query JSON template
  bun run eval:type-accuracy        per-link-type accuracy report

XSS safety:
  escapeHtml() encodes the 5 critical chars (& < > " '). Tested directly
  with representative Opus-generated attacks:
    <img src=x onerror=alert('xss')>  → &lt;img src=x onerror=alert(&#39;xss&#39;)&gt;
    <script>fetch('/steal')</script>  → &lt;script&gt;fetch(&#39;/steal&#39;)&lt;/script&gt;
  Ledger metadata (generated_at, model) also escaped — covers the less
  obvious attack surface where Opus could emit tag-like content into the
  metadata file.

world.html structure:
  - Left rail: entities grouped by type with counts (companies, people,
    meetings, concepts), alphabetical within type
  - Right pane: per-entity cards with title + slug + compiled_truth +
    timeline + canonical _facts as collapsed JSON
  - URL fragment deep-links (#people/alice-chen)
  - Sticky rail on desktop; responsive stack on mobile
  - Vanilla JS for active-link highlighting on scroll (no framework)

Generated file: ~1MB for 240 entities (full prose). Gitignored; rebuild
with `bun run eval:world:view`. Regeneration is ~50ms.

Contributor TTHW (Tier 5.5 query authoring):
  1. bun run eval:world:view                         # see entities
  2. bun run eval:query:new --tier externally-authored --author "@me"
  3. edit template with real slug + query text
  4. bun run eval:query:validate path/to/file.json
  5. submit PR

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 00:15:18 +08:00

135 lines
5.0 KiB
TypeScript

import { describe, test, expect } from 'bun:test';
import { escapeHtml, renderWorldHtml } from './world-html.ts';
describe('escapeHtml — XSS safety', () => {
test('escapes the 5 critical chars', () => {
expect(escapeHtml('<script>alert(1)</script>'))
.toBe('&lt;script&gt;alert(1)&lt;/script&gt;');
});
test('escapes ampersand FIRST (or double-escape bug happens)', () => {
expect(escapeHtml('&lt;'))
.toBe('&amp;lt;'); // the input &lt; becomes &amp;lt;
});
test('escapes quotes (attribute context)', () => {
expect(escapeHtml('onclick="alert(1)"'))
.toBe('onclick=&quot;alert(1)&quot;');
expect(escapeHtml("onclick='alert(1)'"))
.toBe('onclick=&#39;alert(1)&#39;');
});
test('passthrough for safe ASCII', () => {
expect(escapeHtml('Hello world.')).toBe('Hello world.');
});
test('handles null and undefined', () => {
expect(escapeHtml(null)).toBe('');
expect(escapeHtml(undefined)).toBe('');
});
test('handles numbers', () => {
expect(escapeHtml(42)).toBe('42');
});
test('preserves Unicode (non-ASCII that\'s not special HTML)', () => {
expect(escapeHtml('Héctor García 🎉')).toBe('Héctor García 🎉');
});
test('real Opus prose injection attempt neutralized', () => {
// Representative: if Opus generates this in an entity backstory,
// the explorer HTML must NOT execute it. The XSS protection works
// because `<img` is escaped to `&lt;img` — the browser renders the
// resulting string as text, not as an img element, so the embedded
// `onerror=` attribute never gets parsed as an attribute.
const attack = `<img src=x onerror=alert('xss')>`;
const safe = escapeHtml(attack);
// The opening `<` must be escaped (this is what neutralizes the tag).
expect(safe).not.toContain('<img');
// The single quote (which would break out of an attribute) is escaped.
expect(safe).not.toContain("'xss'");
expect(safe).toBe('&lt;img src=x onerror=alert(&#39;xss&#39;)&gt;');
});
test('javascript: URL neutralized when inserted as text', () => {
// Even as text content, escape quotes so it can't break attribute context.
const attack = `"javascript:alert(1)//`;
expect(escapeHtml(attack)).toBe('&quot;javascript:alert(1)//');
});
});
describe('renderWorldHtml', () => {
const samplePage = {
slug: 'people/alice-chen',
type: 'person' as const,
title: 'Alice Chen',
compiled_truth: 'Alice Chen is a senior engineer.',
timeline: '- **2023-01-15** | Promoted to staff engineer',
_facts: { type: 'person', role: 'engineer' },
};
test('renders an HTML document', () => {
const html = renderWorldHtml([samplePage]);
expect(html).toContain('<!doctype html>');
expect(html).toContain('<html');
expect(html).toContain('Alice Chen');
});
test('includes page in rail and in main cards', () => {
const html = renderWorldHtml([samplePage]);
// Rail link uses #slug anchor
expect(html).toContain('href="#people/alice-chen"');
// Entity card has matching id
expect(html).toContain('id="people/alice-chen"');
});
test('escapes all user content (XSS neutralization)', () => {
const maliciousPage = {
...samplePage,
title: '<img src=x onerror=alert(1)>',
compiled_truth: `<script>fetch('/steal')</script>`,
};
const html = renderWorldHtml([maliciousPage]);
// Raw <script> / <img with onerror must NOT be present.
expect(html).not.toContain('<img src=x');
expect(html).not.toContain('<script>fetch');
// Escaped forms must be present.
expect(html).toContain('&lt;img src=x');
expect(html).toContain('&lt;script&gt;fetch');
});
test('groups by type in the rail', () => {
const pages = [
{ ...samplePage, slug: 'people/alice', title: 'Alice', type: 'person' as const },
{ ...samplePage, slug: 'companies/acme', title: 'Acme', type: 'company' as const },
{ ...samplePage, slug: 'meetings/standup', title: 'Standup', type: 'meeting' as const },
];
const html = renderWorldHtml(pages);
expect(html).toContain('companys'); // rendered as "${type}s" — imperfect plural but cheap
expect(html).toContain('persons');
expect(html).toContain('meetings');
});
test('empty corpus produces a valid (if sparse) document', () => {
const html = renderWorldHtml([]);
expect(html).toContain('<!doctype html>');
expect(html).toContain('<nav');
expect(html).toContain('<main');
});
test('ledger metadata renders when provided', () => {
const ledger = { generated_at: '2026-04-19T10:00:00Z', model: 'claude-opus-4-7', costUsd: 3.14 };
const html = renderWorldHtml([samplePage], ledger);
expect(html).toContain('claude-opus-4-7');
expect(html).toContain('$3.14');
});
test('ledger HTML-escapes generated_at and model', () => {
const ledger = { generated_at: '<malicious>', model: 'opus<script>' };
const html = renderWorldHtml([samplePage], ledger);
expect(html).not.toContain('<malicious>');
expect(html).not.toContain('opus<script>');
expect(html).toContain('&lt;malicious&gt;');
});
});