Skip to content

fix(security): T2.6 hardening: zip-bomb size caps on the remaining zip sites (issue #89) - #90

Merged
natechadwick merged 1 commit into
mainfrom
security/t2-6-zip-size-caps
Aug 28, 2026
Merged

fix(security): T2.6 hardening: zip-bomb size caps on the remaining zip sites (issue #89)#90
natechadwick merged 1 commit into
mainfrom
security/t2-6-zip-size-caps

Conversation

@natechadwick-intsof

Copy link
Copy Markdown
Collaborator

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 properties PSARCHIVE_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 archive
  • MAX_ENTRY_SIZE = 100 MB per entry (declared uncompressed size)
  • MAX_TOTAL_SIZE = 500 MB total uncompressed size across all entries

A SecurityException is 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)

# File Pattern
1 projects/sitemanage/.../PSWidgetPackageBuilder.java ZipInputStream.getNextEntry() while loop
2 deliverytiersuite/.../MainDTSPreInstall.java ZipFile.entries() iteration, sorted
3 modules/perc-distribution-tree/.../Main.java ZipFile.entries() iteration, sorted
4 system/.../tools/PSInstallRxApp.java ZipFile.entries() enumeration
5 system/.../tools/InstallRxApp.java ZipFile.entries() enumeration
6 system/release/Install/.../RxExtractJarFiles.java JarFile.entries() enumeration
7 modules/perc-ant/.../PSExtractJarFiles.java JarFile.entries() enumeration

All 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:

com.percussion.security.io.PSZipBombGuard guard = new com.percussion.security.io.PSZipBombGuard();
while (it.hasMoreElements()) {  // or for-each, etc.
    ZipEntry entry = ...;
    guard.check(entry);
    ... existing code ...
}

What's NOT hardened (explicit out-of-scope)

  • PSDirectoryAnalyzer.java uses zip.getEntry(MANIFEST_NAME) (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.
  • deployer/.../PSArchive.java only does getEntry() (single named lookup).
  • PSPackageLockManager.java has no zip iteration at all.
  • All test files are intentionally excluded (test fixtures are controlled, not untrusted input).

Verification

  • ./mvn-env.sh clean install -DskipTestsBUILD 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

Manual 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 SecurityException with a message like Archive rejected: too many entries (limit=10000), Archive rejected: entry 'foo' uncompressed size N exceeds limit 104857600, or Archive rejected: total uncompressed size exceeds limit 524288000 (entry='foo').

The override mechanism can be exercised by setting PSARCHIVE_MAX_ENTRIES=3 and re-running the same test.

Out of scope (separate issues under #73)

References

Co-Authored by Mavis v1.0.0 using minimax-m3 with agent mavis.

…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
natechadwick merged commit 25e3b7b into main Aug 28, 2026
3 checks passed
@natechadwick
natechadwick deleted the security/t2-6-zip-size-caps branch August 28, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[security] T2.6 hardening: size caps on the remaining zip/tar sites (PSArchive, PSPackageBuilder, etc.)

2 participants