mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* fix(security): scope cross-source reads to the caller grant; close get_page exact-path leak One shared resolveRequestedScope() routes every source-scoped read op (query, code_callers/callees, search_by_image, code_blast/flow, get_page) through a single fail-closed trust+grant ladder: a remote caller's __all__ collapses to its granted sources (never the whole brain) and an explicit out-of-grant source_id is rejected. get_page's exact-match path now honors a federated grant via getPage(sourceIds[]) in both engines. Legacy bearer tokens carry their stored permissions.source_id grant (bounded, never widened). Also retries getConfig on transient connection loss. Closes #1924, #1371, #1393, #1336, #1603. * fix(ingest): non-string frontmatter no longer aborts lint/sync; embed/hook/catalog papercuts Parser coerces a non-string title to a string and falls back to inference for slug/type (never fabricating a "123" slug), with a lint NON_STRING_FIELD finding surfacing the malformed frontmatter; a defensive guard in content-sanity stops a non-string title from crashing the whole lint/sync run brain-wide. Plus: embed --catch-up no longer arms the overflowed 32-bit budget timer (and surfaces unembeddable chunks); the frontmatter pre-commit hook ships a correct .md/.mdx regex; and the skill catalog parses YAML block-scalar descriptions. Closes #1883, #1658, #1556, #1948, #1946, #1840, #1711. * v0.42.37.0 fix(security,ingest): source-isolation grant enforcement + non-string frontmatter guard + papercuts Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: add NON_STRING_FIELD frontmatter validation class to docs for v0.42.37.0 The v0.42.37.0 non-string-frontmatter fix added an eighth validation class (NON_STRING_FIELD / lint code frontmatter-non-string-field). Update the two current-state docs that enumerate the validation classes: - skills/frontmatter-guard/SKILL.md (seven->eight + table row) - docs/integrations/pre-commit.md (seven->eight + table row) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
84 lines
3.5 KiB
TypeScript
84 lines
3.5 KiB
TypeScript
/**
|
|
* Non-string frontmatter title/type/slug (#1883, #1658, #1556, #1948).
|
|
*
|
|
* A YAML scalar like `title: 123` parses to a NUMBER. Pre-fix the parser cast it
|
|
* `as string`, so a non-string flowed downstream typed as string and crashed the
|
|
* first `.toLowerCase()` in content-sanity — aborting the whole lint/sync run
|
|
* brain-wide (root trigger behind the never-converging-sync reports #1794/#1939).
|
|
*
|
|
* Fix: coerce title to a string at the parser; for slug/type fall back to
|
|
* inference (never fabricate a "123" slug); guard content-sanity defensively;
|
|
* and surface the malformed frontmatter via a lint NON_STRING_FIELD finding.
|
|
*/
|
|
import { describe, test, expect } from 'bun:test';
|
|
import { parseMarkdown } from '../src/core/markdown.ts';
|
|
import { assessContentSanity } from '../src/core/content-sanity.ts';
|
|
|
|
const fm = (body: string) => `---\n${body}\n---\nbody text here\n`;
|
|
|
|
describe('parseMarkdown coerces/guards non-string frontmatter', () => {
|
|
test('numeric title is coerced to a string (intent preserved)', () => {
|
|
const p = parseMarkdown(fm('title: 123'), 'notes/thing.md');
|
|
expect(typeof p.title).toBe('string');
|
|
expect(p.title).toBe('123');
|
|
});
|
|
|
|
test('boolean title is coerced to a string', () => {
|
|
const p = parseMarkdown(fm('title: false'), 'notes/thing.md');
|
|
expect(p.title).toBe('false');
|
|
});
|
|
|
|
test('missing title falls back to inferred title (string)', () => {
|
|
const p = parseMarkdown(fm('type: note'), 'notes/My Thing.md');
|
|
expect(typeof p.title).toBe('string');
|
|
expect(p.title.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('non-string slug is coerced to a usable string (date slugs are legitimate)', () => {
|
|
// YAML parses `2024-06-01` as a Date; coerceFrontmatterString → "2024-06-01".
|
|
const p = parseMarkdown(fm('slug: 2024-06-01'), 'notes/real-slug.md');
|
|
expect(typeof p.slug).toBe('string');
|
|
expect(p.slug).toBe('2024-06-01');
|
|
});
|
|
|
|
test('numeric type is coerced to a string (never crashes downstream)', () => {
|
|
const p = parseMarkdown(fm('type: 5\ntitle: ok'), 'notes/thing.md');
|
|
expect(typeof p.type).toBe('string');
|
|
});
|
|
|
|
test('valid string fields pass through unchanged', () => {
|
|
const p = parseMarkdown(fm('title: Real Title\ntype: concept\nslug: my-slug'), 'x.md');
|
|
expect(p.title).toBe('Real Title');
|
|
expect(p.type).toBe('concept');
|
|
expect(p.slug).toBe('my-slug');
|
|
});
|
|
});
|
|
|
|
describe('lint surfaces non-string frontmatter (NON_STRING_FIELD)', () => {
|
|
test('numeric title produces a NON_STRING_FIELD validation error', () => {
|
|
const p = parseMarkdown(fm('title: 123'), 'notes/thing.md', { validate: true });
|
|
const codes = (p.errors ?? []).map(e => e.code);
|
|
expect(codes).toContain('NON_STRING_FIELD');
|
|
});
|
|
|
|
test('all-string frontmatter produces no NON_STRING_FIELD error', () => {
|
|
const p = parseMarkdown(fm('title: Fine\ntype: note'), 'notes/thing.md', { validate: true });
|
|
const codes = (p.errors ?? []).map(e => e.code);
|
|
expect(codes).not.toContain('NON_STRING_FIELD');
|
|
});
|
|
});
|
|
|
|
describe('assessContentSanity never throws on a non-string title (belt-and-suspenders)', () => {
|
|
test('numeric title does not crash the sanity pass', () => {
|
|
expect(() =>
|
|
assessContentSanity({ compiled_truth: 'hello world', timeline: '', title: 123 as unknown as string }),
|
|
).not.toThrow();
|
|
});
|
|
|
|
test('undefined title does not crash the sanity pass', () => {
|
|
expect(() =>
|
|
assessContentSanity({ compiled_truth: 'hello world', timeline: '', title: undefined as unknown as string }),
|
|
).not.toThrow();
|
|
});
|
|
});
|