3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-12 11:48:29 +01:00

Answer security round 1: 0 🔴, 1 🟡 (0 misread); build the refusal's path in linear time

This commit is contained in:
2026-11-04 20:22:16 +01:00
committed by Lillem4n
parent 977729621c
commit 13f03a4281
2 changed files with 12 additions and 2 deletions
+10 -1
View File
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
import test from 'node:test'
import vm from 'node:vm'
import type { AdfDocument } from './document.ts'
import type { AdfDocument, AdfNode } from './document.ts'
import type { JsonValue } from '../json-value.ts'
import { adfToHtml, adfToLosslessMarkdown, adfToPortableMarkdown } from '../index.ts'
import { adfDocumentFault, isAdfDocument, writableContent } from './document.ts'
@@ -158,3 +158,12 @@ test('writes a card holding no link mark, and a node type it does not know holdi
const marked = { content: [{ attrs: { url: 'https://example.com/roadmap' }, marks: [comment], type: 'inlineCard' }, { marks: [link], type: 'futureInline' }], type: 'paragraph' }
for (const write of [adfToHtml, adfToLosslessMarkdown, adfToPortableMarkdown]) assert.equal(write({ content: [marked], type: 'doc', version: 1 }).ok, true)
})
test('names a link-marked card at any depth by its path alone, in time linear in the depth', () => {
const depth = 100_000
let node: AdfNode = { attrs: { url: 'https://example.com/' }, marks: [{ attrs: { href: 'https://example.com/' }, type: 'link' }], type: 'inlineCard' }
for (let level = 0; level < depth; level += 1) node = { content: [node], type: 'panel' }
const refused = writableContent({ content: [node], type: 'doc', version: 1 })
assert.equal(!refused.ok && refused.error.path.length, 2 * (depth + 1))
assert.deepEqual(!refused.ok && refused.error.path.slice(0, 4), ['content', 0, 'content', 0])
})
+2 -1
View File
@@ -154,7 +154,8 @@ function refusedShape(content: readonly AdfNode[]): Result<never> | undefined {
for (let step = pending.pop(); step !== undefined; step = pending.pop()) {
if (step.node.type === 'inlineCard' && nodeMarks(step.node).some(({ type }) => type === 'link')) {
const path: (number | string)[] = []
for (let up: Step | undefined = step; up !== undefined; up = up.parent) path.unshift('content', up.index)
for (let up: Step | undefined = step; up !== undefined; up = up.parent) path.push(up.index, 'content')
path.reverse()
return failure('unsupported-node-shape', 'an inlineCard takes no link mark: this one holds one', path)
}
for (const child of nodeContent(step.node).map((node, index) => ({ index, node, parent: step })).reverse()) pending.push(child)