From 74f903bc167e986bd7e7872b3fd3c5750f6d7015 Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Fri, 13 Nov 2026 23:28:29 +0800 Subject: [PATCH 1/2] fix(dockerfile-utils): keep Python digit classes ASCII-only (#2132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python treats `\d` as a Unicode digit class, so a Dockerignore pattern such as `[\d]` excludes the file `١` (U+0661). Moby's matcher and the JavaScript utility use ASCII digit semantics and keep that file. Compile Python patterns with `re.ASCII`, and cover the public matcher with an ASCII digit, a non-ASCII digit and an ordinary letter. ```python from e2b_dockerfile_utils import PatternMatcher matcher = PatternMatcher([r"[\d]"]) matcher.matches("5") # True matcher.matches("\u0661") # False matcher.matches("d") # False ``` The expected class follows [Go regexp syntax](https://pkg.go.dev/regexp/syntax) and the [Moby pattern compiler](https://github.com/moby/patternmatcher/blob/5a6d8429a19bb6948a372ff19e86fe83599a04b7/patternmatcher.go). Existing non-ASCII literals and ranges remain covered by the package suite. Validation on Windows / Python 3.12.2: - Full Python utility suite: before, 945 passed / 1 failed / 1 skipped; after, 946 passed / 1 skipped. The failing case is U+0661; the test stays unchanged. - Ruff check, Ruff format check and `ty check e2b_dockerfile_utils` pass. - The unchanged JS public matcher passes the same three source-level controls on Node 24.14.0. The Python run uses the checkout on `PYTHONPATH` because Python 3.12 does not resolve the Unicode editable `.pth` path in this Windows environment. The skipped BuildKit fixture is the existing quote-aware ADD/COPY case. Includes a patch changeset for `@e2b/dockerfile-utils-python`. --- Devin Review --- .changeset/dockerignore-ascii-digit-class.md | 5 +++++ .../e2b_dockerfile_utils/dockerignore.py | 2 +- .../dockerfile-utils-python/tests/test_dockerignore.py | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .changeset/dockerignore-ascii-digit-class.md diff --git a/.changeset/dockerignore-ascii-digit-class.md b/.changeset/dockerignore-ascii-digit-class.md new file mode 100644 index 0000000000..27cef05799 --- /dev/null +++ b/.changeset/dockerignore-ascii-digit-class.md @@ -0,0 +1,5 @@ +--- +"@e2b/dockerfile-utils-python": patch +--- + +Keep bracket digit classes in Dockerignore patterns ASCII-only, matching Moby and the JavaScript utility. Non-ASCII decimal digits are no longer excluded by a pattern such as `[\d]`. diff --git a/packages/dockerfile-utils-python/e2b_dockerfile_utils/dockerignore.py b/packages/dockerfile-utils-python/e2b_dockerfile_utils/dockerignore.py index 18e6d7448f..9353ffd4f5 100644 --- a/packages/dockerfile-utils-python/e2b_dockerfile_utils/dockerignore.py +++ b/packages/dockerfile-utils-python/e2b_dockerfile_utils/dockerignore.py @@ -144,7 +144,7 @@ def _compile(pattern: str, backslash_is_escape: bool) -> Callable[[str], bool]: return lambda path: path.endswith(suffix) or ( suffix.startswith("/") and path == suffix[1:] ) - compiled = re.compile(regex + r"\Z") + compiled = re.compile(regex + r"\Z", flags=re.ASCII) return lambda path: compiled.match(path) is not None diff --git a/packages/dockerfile-utils-python/tests/test_dockerignore.py b/packages/dockerfile-utils-python/tests/test_dockerignore.py index 4bed4766a3..9902943d4a 100644 --- a/packages/dockerfile-utils-python/tests/test_dockerignore.py +++ b/packages/dockerfile-utils-python/tests/test_dockerignore.py @@ -72,3 +72,11 @@ def test_rejects_invalid_patterns_with_value_error(): PatternMatcher(["[abc"]) with pytest.raises(ValueError, match=r"Invalid ignore pattern '\[\\q\]'"): PatternMatcher(["[\\q]"]) + + +@pytest.mark.parametrize( + ("path", "excluded"), + [("5", True), ("\u0661", False), ("d", False)], +) +def test_digit_class_only_matches_ascii_digits(path, excluded): + assert PatternMatcher([r"[\d]"]).matches(path) is excluded From 1136b62e480bfd6f29a341b3e04fe52023a02fcf Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 13 Nov 2026 15:39:30 +0000 Subject: [PATCH 2/2] fix(dockerfile-utils): keep JS whitespace classes ASCII-only like Python and Moby Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .changeset/dockerignore-ascii-digit-class.md | 2 +- .../dockerignore-ascii-whitespace-class.md | 5 ++++ .../dockerfile-utils-js/src/dockerignore.ts | 19 +++++++++++++-- .../tests/dockerignore.test.ts | 17 ++++++++++++++ .../tests/test_dockerignore.py | 23 +++++++++++++++---- 5 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 .changeset/dockerignore-ascii-whitespace-class.md diff --git a/.changeset/dockerignore-ascii-digit-class.md b/.changeset/dockerignore-ascii-digit-class.md index 27cef05799..86ba6267ec 100644 --- a/.changeset/dockerignore-ascii-digit-class.md +++ b/.changeset/dockerignore-ascii-digit-class.md @@ -2,4 +2,4 @@ "@e2b/dockerfile-utils-python": patch --- -Keep bracket digit classes in Dockerignore patterns ASCII-only, matching Moby and the JavaScript utility. Non-ASCII decimal digits are no longer excluded by a pattern such as `[\d]`. +Keep bracket class escapes in Dockerignore patterns ASCII-only, matching Moby and the JavaScript utility. Non-ASCII decimal digits are no longer excluded by a pattern such as `[\d]`, and non-ASCII spaces by a pattern such as `[\s]`. diff --git a/.changeset/dockerignore-ascii-whitespace-class.md b/.changeset/dockerignore-ascii-whitespace-class.md new file mode 100644 index 0000000000..a8c853c22b --- /dev/null +++ b/.changeset/dockerignore-ascii-whitespace-class.md @@ -0,0 +1,5 @@ +--- +"@e2b/dockerfile-utils": patch +--- + +Keep bracket whitespace classes in Dockerignore patterns ASCII-only, matching Moby and the Python utility. Non-ASCII spaces such as U+00A0 are no longer matched by a pattern such as `[\s]`. diff --git a/packages/dockerfile-utils-js/src/dockerignore.ts b/packages/dockerfile-utils-js/src/dockerignore.ts index 6d5ae226d6..36581e9b4d 100644 --- a/packages/dockerfile-utils-js/src/dockerignore.ts +++ b/packages/dockerfile-utils-js/src/dockerignore.ts @@ -25,14 +25,23 @@ function escapeRegex(ch: string): string { } // Escapes that keep their regex meaning in a bracket expression, like in Python -const CLASS_ESCAPES = new Set('dDsSwWfnrtv') +const CLASS_ESCAPES = new Set('dDwWfnrtv') +// ASCII-only whitespace like Moby, as JS `\s` also matches Unicode spaces +const CLASS_WHITESPACE_ESCAPES: Record = { + s: '\\t-\\r ', + S: '\\u{0}-\\u{8}\\u{e}-\\u{1f}\\u{21}-\\u{10ffff}', +} const CLASS_CONTROL_ESCAPES: Record = { a: '\\x07', b: '\\x08' } const CLASS_HEX_ESCAPE_LENGTHS: Record = { x: 2, u: 4, U: 8 } // Characters that must stay escaped in a bracket expression with the `u` flag const CLASS_SYNTAX_CHARS = new Set('^$\\.*+?()[]{}|/-') // Make a bracket expression body valid with the regex `u` flag -function translateClass(body: string, backslashIsEscape: boolean): string { +function translateClass( + body: string, + backslashIsEscape: boolean, + unicodeWhitespace = false +): string { let cls = '' let i = 0 while (i < body.length) { @@ -45,6 +54,8 @@ function translateClass(body: string, backslashIsEscape: boolean): string { const next = body[i++] if (CLASS_ESCAPES.has(next)) { cls += '\\' + next + } else if (next in CLASS_WHITESPACE_ESCAPES) { + cls += unicodeWhitespace ? '\\' + next : CLASS_WHITESPACE_ESCAPES[next] } else if (next in CLASS_CONTROL_ESCAPES) { cls += CLASS_CONTROL_ESCAPES[next] } else if (next in CLASS_HEX_ESCAPE_LENGTHS) { @@ -78,6 +89,10 @@ function translateClass(body: string, backslashIsEscape: boolean): string { } } } + if (!unicodeWhitespace) { + // Reject ranges such as `[\s-x]` that the expanded set would allow + new RegExp('[' + translateClass(body, backslashIsEscape, true) + ']', 'u') + } return cls } diff --git a/packages/dockerfile-utils-js/tests/dockerignore.test.ts b/packages/dockerfile-utils-js/tests/dockerignore.test.ts index c778e6025f..fc7a382ca9 100644 --- a/packages/dockerfile-utils-js/tests/dockerignore.test.ts +++ b/packages/dockerfile-utils-js/tests/dockerignore.test.ts @@ -84,3 +84,20 @@ test('rejects invalid patterns with a plain Error', () => { "Invalid ignore pattern '[\\q]'" ) }) + +test.each([ + ['[\\d]', '5', true], + ['[\\d]', '\u0661', false], + ['[\\d]', 'd', false], + ['[\\s]', ' ', true], + ['[\\s]', '\u00a0', false], + ['[\\S]', '\u00a0', true], + ['[\\S]', '\t', false], +])('class %s matches %j: %s', (pattern, path, excluded) => { + assert.strictEqual(new PatternMatcher([pattern]).matches(path), excluded) +}) + +test('rejects ranges with a whitespace class escape', () => { + assert.throws(() => new PatternMatcher(['[\\s-x]']), Error) + assert.throws(() => new PatternMatcher(['[a-\\S]']), Error) +}) diff --git a/packages/dockerfile-utils-python/tests/test_dockerignore.py b/packages/dockerfile-utils-python/tests/test_dockerignore.py index 9902943d4a..5de2d1e79f 100644 --- a/packages/dockerfile-utils-python/tests/test_dockerignore.py +++ b/packages/dockerfile-utils-python/tests/test_dockerignore.py @@ -75,8 +75,23 @@ def test_rejects_invalid_patterns_with_value_error(): @pytest.mark.parametrize( - ("path", "excluded"), - [("5", True), ("\u0661", False), ("d", False)], + ("pattern", "path", "excluded"), + [ + (r"[\d]", "5", True), + (r"[\d]", "\u0661", False), + (r"[\d]", "d", False), + (r"[\s]", " ", True), + (r"[\s]", "\u00a0", False), + (r"[\S]", "\u00a0", True), + (r"[\S]", "\t", False), + ], ) -def test_digit_class_only_matches_ascii_digits(path, excluded): - assert PatternMatcher([r"[\d]"]).matches(path) is excluded +def test_class_escapes_are_ascii_only(pattern, path, excluded): + assert PatternMatcher([pattern]).matches(path) is excluded + + +def test_rejects_ranges_with_a_whitespace_class_escape(): + with pytest.raises(ValueError): + PatternMatcher([r"[\s-x]"]) + with pytest.raises(ValueError): + PatternMatcher([r"[a-\S]"])