mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-31 04:07:52 +00:00
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')> → <img src=x onerror=alert('xss')>
<script>fetch('/steal')</script> → <script>fetch('/steal')</script>
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>
135 lines
5.0 KiB
TypeScript
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('<script>alert(1)</script>');
|
|
});
|
|
|
|
test('escapes ampersand FIRST (or double-escape bug happens)', () => {
|
|
expect(escapeHtml('<'))
|
|
.toBe('&lt;'); // the input < becomes &lt;
|
|
});
|
|
|
|
test('escapes quotes (attribute context)', () => {
|
|
expect(escapeHtml('onclick="alert(1)"'))
|
|
.toBe('onclick="alert(1)"');
|
|
expect(escapeHtml("onclick='alert(1)'"))
|
|
.toBe('onclick='alert(1)'');
|
|
});
|
|
|
|
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 `<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('<img src=x onerror=alert('xss')>');
|
|
});
|
|
|
|
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('"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('<img src=x');
|
|
expect(html).toContain('<script>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('<malicious>');
|
|
});
|
|
});
|