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`.
---
---
.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]"])