fix(ai): broaden SSRF pre-scan regex to cover srcset, poster, formaction, @import

This commit is contained in:
SnapOtter
2026-06-13 10:36:12 +08:00
parent fc718c1684
commit 5397f9b21c
2 changed files with 26 additions and 2 deletions
+2 -2
View File
@@ -10,11 +10,11 @@ import re
import sys
_REMOTE_REF_RE = re.compile(
r'(?:src|href|action)\s*=\s*["\']?\s*(https?:/{1,2}[^\s"\'>\)]{1,200})',
r'(?:src|href|action|srcset|poster|formaction)\s*=\s*["\']?\s*(https?:/{1,2}[^\s"\'>\)]{1,200})',
re.IGNORECASE,
)
_REMOTE_CSS_URL_RE = re.compile(
r'url\s*\(\s*["\']?\s*(https?:/{1,2}[^\s"\'>\)]{1,200})',
r'(?:url\s*\(|@import)\s*["\']?\s*(https?:/{1,2}[^\s"\'>\)]{1,200})',
re.IGNORECASE,
)
+24
View File
@@ -49,6 +49,18 @@ describe.skipIf(!hasPython)("SSRF pre-scan regexes (doc_html_pdf.py)", () => {
it("does NOT match relative paths", () => {
expect(testRegex('<a href="page2.xhtml">', "_REMOTE_REF_RE")).toBe(false);
});
it("matches srcset attribute", () => {
expect(testRegex('<img srcset="https://h/x.png 2x">', "_REMOTE_REF_RE")).toBe(true);
});
it("matches poster attribute", () => {
expect(testRegex('<video poster="https://h/thumb.jpg">', "_REMOTE_REF_RE")).toBe(true);
});
it("matches formaction attribute", () => {
expect(testRegex('<button formaction="https://h/submit">', "_REMOTE_REF_RE")).toBe(true);
});
});
describe("_REMOTE_CSS_URL_RE", () => {
@@ -63,5 +75,17 @@ describe.skipIf(!hasPython)("SSRF pre-scan regexes (doc_html_pdf.py)", () => {
it("does NOT match data: CSS url()", () => {
expect(testRegex("url(data:image/png;base64,AA==)", "_REMOTE_CSS_URL_RE")).toBe(false);
});
it("matches @import with double-quoted URL", () => {
expect(testRegex('@import "https://h/style.css";', "_REMOTE_CSS_URL_RE")).toBe(true);
});
it("matches @import with single-quoted URL", () => {
expect(testRegex("@import 'https://h/style.css';", "_REMOTE_CSS_URL_RE")).toBe(true);
});
it("matches @import with bare URL", () => {
expect(testRegex("@import https://h/style.css;", "_REMOTE_CSS_URL_RE")).toBe(true);
});
});
});