You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2026/09/06 05:22:02 UTC
(groovy) branch master updated: THREAT_MODEL: broaden P4 to what tooling writes, add P4b for path containment
This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 5a64fb2ca5 THREAT_MODEL: broaden P4 to what tooling writes, add P4b for path containment
5a64fb2ca5 is described below
commit 5a64fb2ca5232fbc3f39993bb759afc4c0c7ab48
Author: Paul King <pa...@asert.com.au>
AuthorDate: Sun Sep 6 15:19:41 2026 +1000
THREAT_MODEL: broaden P4 to what tooling writes, add P4b for path containment
P4 promised owner-only temporary artifacts only, but the §7 local adversary
concerns everything Groovy tooling writes, and three fixes had extended the
same care to persistent artifacts: owner-only groovysh state files
(GROOVY-12335), permission preservation across an in-place edit
(GROOVY-12337), and an AST dump inheriting its source's permissions
(GROOVY-12368).
The unifying principle is least-exposure, not owner-only: the naive reading
— persistent artifacts are owner-only — is false, because groovydoc HTML,
compiled classes and joint-compilation output are meant to be readable. P4
now states that a file tooling writes for its own use or as a byproduct is
no more permissive than its content warrants, with the baseline taken from
the artifact (owner-only for private state, the original's permissions for
a rewrite, the source's for a derived file), and explicitly carves out
output a tool is meant to publish.
P4b records the containment invariant established by the path-traversal
fixes: a tool that resolves a path from a name it was handed writes,
deletes or copies only inside its intended tree — groovydoc page and
resource output (GROOVY-12360, GROOVY-12370), deleteDir link/junction
handling (GROOVY-12125, GROOVY-12359), grape uninstall (GROOVY-12369). It
states what is not covered: a concurrent local writer swapping a path
mid-operation, which needs fd-relative calls and a write capability §7 does
not grant the local adversary — the residual parked when GROOVY-12359 left
its check-then-delete race unaddressed.
Some P4b references are in-flight PRs targeting the same release; the
property describes the invariant the tooling is converging on. P4 is a
provided property, so these become VALID rather than hardening if violated
by default: the scope was widened deliberately, as tmd-4 of the review.
---
THREAT_MODEL.md | 50 ++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 42 insertions(+), 8 deletions(-)
diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md
index bdb241c056..3454d44687 100644
--- a/THREAT_MODEL.md
+++ b/THREAT_MODEL.md
@@ -488,18 +488,52 @@ which it holds, and what its violation would look like.
are `@Incubating` (best-effort per the stability qualifier below).
*(documented — verified in `groovy-json`, `groovy-yaml` `YamlConverter`,
`groovy-toml`, `groovy-csv`)*
-- **P4 — Owner-only temporary artifacts.** Temp directories created by
- Groovy tooling (e.g. `FileSystemCompiler` joint-compilation staging) use
- NIO `Files.createTempDirectory`, yielding owner-only permissions — the
- fix class for CVE-2020-17521. *Violation:* a Groovy-created temp
- artifact being world-readable/writable. CWE-377 / CWE-378. *Indicative
+- **P4 — Least-exposure for artifacts tooling writes.** A file Groovy
+ tooling creates for its own use, or as a byproduct of a run, is given
+ permissions no more permissive than its content warrants — it does not
+ widen exposure by falling back to the process umask. The baseline is
+ taken from what the artifact is: a temporary artifact or a private state
+ file is owner-only (temp directories via NIO `Files.createTempDirectory`,
+ the fix class for CVE-2020-17521; the `groovysh` state files JLine writes
+ are created owner-only, GROOVY-12335); a file rewritten in place keeps
+ the permissions the original had (`groovy -i`, GROOVY-12337); and a file
+ derived from a source inherits the source's permissions, so it exposes no
+ more than the source already does (the `groovy.ast=xml` dump,
+ GROOVY-12368). This is **not** a promise about output a tool is meant to
+ publish — `groovydoc` HTML, compiled `.class` files, joint-compilation
+ results — which is intended to be readable and carries the umask by
+ design. *Condition:* a POSIX-permission filesystem; elsewhere the
+ platform's own model applies. *Violation:* a private or byproduct
+ artifact created world-readable/writable, or a rewrite widening an
+ existing file's permissions. CWE-377 / CWE-378 / CWE-732. *Indicative
severity if violated: Medium (local).* *(documented — verified in
- `DefaultGroovyStaticMethods`/`FileSystemCompiler`)*
+ `DefaultGroovyStaticMethods`/`FileSystemCompiler`, `groovy-groovysh`,
+ `GroovyMain`, `XStreamUtils`)*
+- **P4b — Tooling stays within its own output tree.** A tool that resolves
+ a filesystem path from a name it was given — a package path, a cached
+ descriptor's artifact name, a documentation resource — writes, deletes,
+ or copies only within its intended directory: a `..` segment, an absolute
+ path, or a symbolic link or other reparse point does not carry the
+ operation outside that tree. `groovydoc` confines its generated pages and
+ does not follow a link out of a mirrored resource directory
+ (GROOVY-12360, GROOVY-12370); `deleteDir` treats a link or junction as a
+ leaf rather than descending through it (GROOVY-12125, GROOVY-12359);
+ `grape uninstall` deletes only inside the module's cache (GROOVY-12369).
+ *Violation:* a tooling write, delete, or copy escaping its target
+ directory because of a name or link it was handed. CWE-22 / CWE-59.
+ *Indicative severity if violated: Medium (local); lower where the name is
+ operator-supplied.* **Not covered:** a local adversary who mutates the
+ tree *concurrently, during* the operation — swapping a checked path for a
+ link between the check and the act — which needs fd-relative filesystem
+ calls (`SecureDirectoryStream`, Linux-only in OpenJDK) and a write
+ capability [§7](#7-adversary-model) does not grant the local adversary.
+ *(documented — verified in `GroovyDocWriter`, `ResourceGroovyMethods`,
+ `groovy-nio`, `groovy-grape-ivy`)*
- **P5 — Coordinated security maintenance.** Supported branches receive
security fixes and coordinated disclosure per [`SECURITY.md`](.github/SECURITY.md)
and the [security history](https://groovy-lang.org/security.html). *(documented)*
-A report demonstrating a *default-configuration* violation of P1–P4 is a
+A report demonstrating a *default-configuration* violation of P1–P4 (and P4b) is a
genuine vulnerability and should be reported privately
([§13: VALID](#13-triage-dispositions)).
@@ -668,7 +702,7 @@ vulnerabilities** unless a concrete, in-model data-boundary crossing
| AST transforms executing code at **compile** time (local & global) | By design; compile time is trusted — `KNOWN-NON-FINDING` |
| `Eval` / `GroovyShell` inside Groovy's tools, tests, and `groovysh`/`groovyConsole` | By design — `BY-DESIGN: property-disclaimed` |
| `@Grab`/Grape fetching and loading artifacts | By design dependency resolution — `OUT-OF-MODEL` |
-| Temp-file/dir creation | Now NIO owner-only (P4) — `KNOWN-NON-FINDING` |
+| Temp-file/dir creation, and other artifacts tooling writes | Owner-only or least-exposure (P4); path-contained (P4b) — `KNOWN-NON-FINDING` unless a *default-config* case widens exposure or escapes its tree, which is `VALID-HARDENING` |
| Regex, `BigInteger`/`BigDecimal` parsing, hash-collision flooding (JDK treeifies heavily-collided `String`-keyed buckets since Java 8) | DoS bounded by developer-chosen input — `OUT-OF-MODEL: downstream-responsibility` |
| Deep recursion / unbounded input in Groovy's *own* data parsers (`JsonSlurper`, `XmlSlurper`/`XmlParser`, `groovy-yaml`/`-toml`/`-csv`) | Robustness of code meant to consume untrusted input — **`VALID-HARDENING`** *(maintainer)*; nesting depth is now bounded by default in all of them (JSON via the 6.0.0 `maxNestingDepth` cap, GROOVY-12064; XML via the 6.0.0 `jdk.xml.maxElementDepth` bound, GROOVY-12331), per-parser exposure in [§6](#6-assumptions-about-inputs) |
| Proportionate memory use from a *large* (not amplified) document handed to `JsonSlurper`/`XmlSlurper`/`XmlParser` | Cost proportional to input size; bound the input or use a streaming/Jackson parser ([§6](#6-assumptions-about-inputs)) — `OUT-OF-MODEL: downstream-responsibility` |