fix(agents): validate document formats and trim HTML URLs

This commit is contained in:
4gray committed 2026-09-21 01:45:35 +02:00
1 parent 8f76a2c456
commit a2d1997e29
4 files changed
+36 -3

No files matched your search

+3 -1
View File
@@ -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.
+4 -1
View File
@@ -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 (
+3 -1
View File
@@ -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;
@@ -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 [
'<a href=" docs/example.md#repeat ">Guide</a>',
'<iframe src="&#9;docs/example.md&#10;"></iframe>',
'<img src=" docs/example.md ">',
]) {
test(`HTML URL edge whitespace is ignored: ${html}`, async (t) => {
assert.deepEqual(await diagnostics(t, { 'AGENTS.md': html }), []);
});
}