fix(security): T2.6 hardening: zip-bomb size caps on the remaining zip sites (issue #89) - #90
Merged
Merged
Conversation
…p sites (issue #89) Follow-up to PR #83 (which added the same caps to PSArchiveFiles.extractFilesFromArchive). This PR brings the same defense-in-depth to the other 7 zip-handling sites in the project. What's new: - PSZipBombGuard: a small utility class in modules/perc-security-utils (com.percussion.security.io.PSZipBombGuard) that wraps the three caps in a stateful, reusable object. Constructor takes (maxEntries, maxEntrySize, maxTotalSize); use the no-arg constructor for the defaults. All three caps are overridable per JVM via the system properties PSARCHIVE_MAX_ENTRIES / PSARCHIVE_MAX_ENTRY_SIZE / PSARCHIVE_MAX_TOTAL_SIZE (matching the override keys used in #83). - Three fail-closed checks per call: * MAX_ENTRIES = 10,000 entries per archive * MAX_ENTRY_SIZE = 100 MB per entry (declared uncompressed size) * MAX_TOTAL_SIZE = 500 MB total uncompressed size across all entries A single SecurityException is thrown for the first cap that is hit; the offending entry name and the cap value are included in the message. Where it's used (7 production zip sites; the 8th ZipSlipGuard-using file was already hardened in #83): - projects/sitemanage/.../PSWidgetPackageBuilder.java (ZipInputStream.getNextEntry loop) - deliverytiersuite/.../MainDTSPreInstall.java (ZipFile.entries() iteration, sorted) - modules/perc-distribution-tree/.../Main.java (ZipFile.entries() iteration, sorted) - system/.../tools/PSInstallRxApp.java (ZipFile.entries() enumeration) - system/.../tools/InstallRxApp.java (ZipFile.entries() enumeration) - system/release/Install/.../RxExtractJarFiles.java (JarFile.entries() enumeration) - modules/perc-ant/.../PSExtractJarFiles.java (JarFile.entries() enumeration) All 7 sites already had ZipSlipGuard + canonical-path checks (the "// codeql[java/zipslip] justification" suppressions are unchanged); the new code only adds the resource-exhaustion caps to those checks. Total diff: 8 files, +182 / -0. What's NOT hardened (explicit out-of-scope): - PSDirectoryAnalyzer.java uses zip.getEntry() (a single named lookup, not an iteration) — not a zip-bomb attack surface. - PSPackageBuilder.java only does ZipOutputStream writes (building archives, not reading them) — no attack surface. - PSArchive.java in deployer/ only does getEntry() (single named lookup). - PSPackageLockManager.java has no zip iteration at all (it operates on package metadata, not the zip contents). - All test files are intentionally excluded (test fixtures are controlled). Verification: - ./mvn-env.sh clean install -DskipTests: BUILD SUCCESS in 4:06 (61 modules, Java 1.8.0_504) - ./mvn-env.sh spotless:check: clean (the build runs spotless:apply during validate; nothing needed reformatting) - No UnsupportedClassVersionError in the build log - All 7 hardened sites already had perc-security-utils as a transitive dep (they were already using ZipSlipGuard), so no module-pom dep additions were required Out of scope (separate issues): - commons-httpclient 3.1 -> HttpClient 5 (issue #88, deferred; multi-day migration across 29 files) - T2.11 SnakeYAML SafeConstructor hardening (16 CVEs) - commons-beanutils 1.11.0 -> beanutils2 EOL replacement - commons-configuration 1.10 -> commons-configuration2 EOL replacement Refs #89, #73, #72
natechadwick
approved these changes
Aug 28, 2026
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
T2.6 hardening sub-task of the parent epic #73. This is a defense-in-depth follow-up to PR #83, which added per-entry / per-archive / per-archive-count size caps to
PSArchiveFiles.extractFilesFromArchive(). This PR brings the same protection to the other 7 zip-handling sites in the project, so the commons-compress 1.28.0 zip-bomb half of the 11 CVEs is now uniformly covered.The 11 CVEs in commons-compress 1.28.0 cannot be closed by upgrading the library (1.28.0 is the last Java 1.8 line; 1.29+ is Java 9+). The win here is defense in depth: the project becomes much less exposed by setting sensible caps in every zip-reading call site.
What changes (8 files, +182 / −0)
1. New utility class (1 file, +150)
modules/perc-security-utils/src/main/java/com/percussion/security/io/PSZipBombGuard.java— a small, reusable, stateful guard object. Constructor takes(maxEntries, maxEntrySize, maxTotalSize); use the no-arg constructor for the defaults. All three caps are overridable per JVM via the system propertiesPSARCHIVE_MAX_ENTRIES/PSARCHIVE_MAX_ENTRY_SIZE/PSARCHIVE_MAX_TOTAL_SIZE(matching the override keys used in #83).Three fail-closed checks per
check(ZipEntry)call:MAX_ENTRIES= 10,000 entries per archiveMAX_ENTRY_SIZE= 100 MB per entry (declared uncompressed size)MAX_TOTAL_SIZE= 500 MB total uncompressed size across all entriesA
SecurityExceptionis thrown for the first cap that is hit; the offending entry name and the cap value are included in the message. The guard tracks entries seen and cumulative bytes seen internally, so a single guard object can protect an entire iteration loop.2. Applied to 7 production zip sites (7 files, +26)
projects/sitemanage/.../PSWidgetPackageBuilder.javaZipInputStream.getNextEntry()while loopdeliverytiersuite/.../MainDTSPreInstall.javaZipFile.entries()iteration, sortedmodules/perc-distribution-tree/.../Main.javaZipFile.entries()iteration, sortedsystem/.../tools/PSInstallRxApp.javaZipFile.entries()enumerationsystem/.../tools/InstallRxApp.javaZipFile.entries()enumerationsystem/release/Install/.../RxExtractJarFiles.javaJarFile.entries()enumerationmodules/perc-ant/.../PSExtractJarFiles.javaJarFile.entries()enumerationAll 7 sites already had
ZipSlipGuard+ canonical-path checks (the existing// codeql[java/zipslip]suppressions are unchanged); the new code only adds the resource-exhaustion caps. Each site is 3-4 lines of new code:What's NOT hardened (explicit out-of-scope)
PSDirectoryAnalyzer.javauseszip.getEntry(MANIFEST_NAME)(a single named lookup, not an iteration) — not a zip-bomb attack surface.PSPackageBuilder.javaonly doesZipOutputStreamwrites (building archives, not reading them) — no attack surface.deployer/.../PSArchive.javaonly doesgetEntry()(single named lookup).PSPackageLockManager.javahas no zip iteration at all.Verification
./mvn-env.sh clean install -DskipTests→ BUILD SUCCESS in 4:06 (61 modules, Java 1.8.0_504)./mvn-env.sh spotless:check: clean (the build runsspotless:applyduringvalidate; nothing needed reformatting)UnsupportedClassVersionErrorin the build logperc-security-utilsas a transitive dep (they were already usingZipSlipGuard), so no module-pom dep additions were requiredManual smoke test (recommended before merge)
Build a zip with a > 10 000-entry zip OR any entry whose declared uncompressed size is > 100 MB, and run it through any of the 7 hardened call sites. Each should throw
SecurityExceptionwith a message likeArchive rejected: too many entries (limit=10000),Archive rejected: entry 'foo' uncompressed size N exceeds limit 104857600, orArchive rejected: total uncompressed size exceeds limit 524288000 (entry='foo').The override mechanism can be exercised by setting
PSARCHIVE_MAX_ENTRIES=3and re-running the same test.Out of scope (separate issues under #73)
commons-httpclient 3.1 → HttpClient 5(issue deps: EOL replace commons-httpclient 3.1 with org.apache.httpcomponents.client5:httpclient5 (closes 1 CVE) #88, deferred; multi-day migration across 29 files with real API rewrites)SafeConstructorhardening (16 CVEs)commons-beanutils 1.11.0 → beanutils2EOL replacementcommons-configuration 1.10 → commons-configuration2EOL replacementReferences
PSArchiveFiles.extractFilesFromArchive)docs/ai-generated/tasks/PR#-DependencyVulnerabilityAnalysis/issues/02-epic-non-upgradeable.md#t26--apache-commons-hardeningZipSlipGuardhelper (used as model):com.percussion.security.io.ZipSlipGuardinmodules/perc-security-utils