From a2d1997e2998ffe9f07baa75d57f1cb28792d4d8 Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 21 Sep 2026 01:45:35 +0200 Subject: [PATCH] fix(agents): validate document formats and trim HTML URLs --- docs/development/agent-workflow.md | 4 ++- tools/skills/agent-guidance-markdown.mjs | 5 +++- tools/skills/validate-agent-guidance.mjs | 4 ++- tools/skills/validate-agent-guidance.test.mjs | 26 +++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/docs/development/agent-workflow.md b/docs/development/agent-workflow.md index 7e76b8665..77dcf800a 100644 --- a/docs/development/agent-workflow.md +++ b/docs/development/agent-workflow.md @@ -57,6 +57,7 @@ Only headings present outside inert HTML containers contribute slugs or duplicat Explicit HTML anchors use `parse5`, excluding comments, scripts, styles and template contents. Rendered HTML anchor and image-map area hrefs and image sources use the same local-reference checks as Markdown links, including decoded attributes and fragment validation. +URL attributes discard surrounding ASCII control/space characters before resolution. Iframe sources are document references and retain Markdown-target anchor checks. Image references check file existence without interpreting image fragments as Markdown headings; document links keep anchor checks even when sharing a target. @@ -70,7 +71,8 @@ Declared scoped dependencies, scope wildcards and matching TypeScript path alias are recognized as package/alias mentions. Traversal and document-file imports are rejected before those exemptions, including document paths with fragments or queries. All recognized Markdown extensions share the document-import guard; reStructuredText -and AsciiDoc documents are also excluded from package exemptions. URL-encoded +and AsciiDoc, PDF, Word, OpenDocument, RTF, Org and TeX documents are also excluded +from package exemptions. URL-encoded paths do not receive package exemptions. TypeScript configuration is parsed as JSONC. Declared packages also permit safe subpaths; exact aliases stay exact. Declared package mentions may include a version (including semver comparators and wildcard ranges) or dist-tag qualifier. diff --git a/tools/skills/agent-guidance-markdown.mjs b/tools/skills/agent-guidance-markdown.mjs index 4484bd3ef..997b96476 100644 --- a/tools/skills/agent-guidance-markdown.mjs +++ b/tools/skills/agent-guidance-markdown.mjs @@ -83,7 +83,10 @@ function htmlNavigation(html, inspect = () => {}) { (node.tagName === 'video' && attribute.name === 'poster') ) references.push({ - target: attribute.value, + target: attribute.value.replace( + /^[\u0000-\u0020]+|[\u0000-\u0020]+$/gu, + '' + ), image: !['a', 'area', 'iframe'].includes(node.tagName), }); if ( diff --git a/tools/skills/validate-agent-guidance.mjs b/tools/skills/validate-agent-guidance.mjs index 0f138f1e3..92eb20abe 100644 --- a/tools/skills/validate-agent-guidance.mjs +++ b/tools/skills/validate-agent-guidance.mjs @@ -130,7 +130,9 @@ async function packageMentions(rootDir) { if ( /%[\da-f]{2}/iu.test(token) || MARKDOWN_EXTENSION.test(path) || - /\.(?:txt|json|ya?ml|html?|rst|rest|adoc|asciidoc)$/iu.test(path) + /\.(?:txt|json|ya?ml|html?|rst|rest|adoc|asciidoc|pdf|docx?|odt|rtf|org|tex)$/iu.test( + path + ) ) return false; if (packages.includes(token)) return true; diff --git a/tools/skills/validate-agent-guidance.test.mjs b/tools/skills/validate-agent-guidance.test.mjs index 1fb25caea..ddce49e73 100644 --- a/tools/skills/validate-agent-guidance.test.mjs +++ b/tools/skills/validate-agent-guidance.test.mjs @@ -1266,3 +1266,29 @@ for (const extension of ['rst', 'rest', 'adoc', 'asciidoc']) { ); }); } + +for (const extension of ['pdf', 'doc', 'docx', 'odt', 'rtf', 'org', 'tex']) { + test(`document format is not a package exemption: ${extension}`, async (t) => { + assert.ok( + ( + await diagnostics(t, { + 'package.json': JSON.stringify({ + dependencies: { '@angular/core': '*' }, + }), + 'CLAUDE.md': + '@AGENTS.md\n\nRead @angular/core/docs/guide.' + + extension, + }) + ).some((message) => message.includes('additional or inline')) + ); + }); +} +for (const html of [ + 'Guide', + '', + '', +]) { + test(`HTML URL edge whitespace is ignored: ${html}`, async (t) => { + assert.deepEqual(await diagnostics(t, { 'AGENTS.md': html }), []); + }); +}