Skip to content

fix: complete Zip Slip hardening and eliminate BASE_PATH prefix-collision bypass#2409

Open
ashnaaseth2325-oss wants to merge 1 commit intoOWASP:masterfrom
ashnaaseth2325-oss:fix/path-validation-security
Open

fix: complete Zip Slip hardening and eliminate BASE_PATH prefix-collision bypass#2409
ashnaaseth2325-oss wants to merge 1 commit intoOWASP:masterfrom
ashnaaseth2325-oss:fix/path-validation-security

Conversation

@ashnaaseth2325-oss
Copy link
Contributor

Summary

This PR completes the Zip Slip hardening in scripts/convert.py by fixing an incomplete path containment check in _validate_file_paths.

While _safe_extractall() correctly used the + os.sep suffix pattern to prevent prefix collisions, _validate_file_paths() still relied on a naïve:

if not source_path.startswith(base_path):

This allowed sibling directories such as:

/app/cornucopia
/app/cornucopiaevil

to bypass the intended BASE_PATH sandbox boundary.

The PR also enforces containment at the output write boundary and disables LibreOffice scripting during conversion.


Steps to Reproduce

Assume:

BASE_PATH = /app/cornucopia

Run:

python scripts/convert.py \
  --inputfile /tmp/template.odt \
  --outputfile /app/cornucopiaevil/output.odt \
  --pdf

Before this fix:

"/app/cornucopiaevil/output.odt".startswith("/app/cornucopia")
# → True  (prefix collision bypass)

The validation passes incorrectly, allowing file creation and LibreOffice execution outside the intended base directory.


Impact

  • Filesystem sandbox escape via prefix collision
  • Arbitrary file write outside BASE_PATH
  • LibreOffice executed on out-of-bounds paths
  • Potential macro execution (without --noscripting)
  • Inconsistent containment compared to _safe_extractall

This is production-relevant for any deployment where CLI inputs are user-controlled (CI pipelines, automation, scripted builds).


Fix

1️⃣ Correct containment check

Updated _validate_file_paths:

if not source_path.startswith(base_path + os.sep):

Same change applied to output_dir.


2️⃣ Enforce output write boundary

Added realpath containment in rename_output_file():

resolved = os.path.realpath(output_filename)
base = os.path.realpath(convert_vars.BASE_PATH)

if not resolved.startswith(base + os.sep):
    raise ValueError(...)

3️⃣ Disable LibreOffice scripting

Added:

"--noscripting",

to the headless LibreOffice command.


Result

  • Prefix-collision bypass eliminated
  • Output writes constrained to BASE_PATH
  • Macro execution surface removed
  • Zip Slip hardening now consistently enforced

This restores the intended filesystem sandbox boundary with minimal, surgical changes.

@ashnaaseth2325-oss
Copy link
Contributor Author

Hi @sydseter @cw-owasp @rewtd,
All tests pass (178 tests, 0 failures). The build-test-copi job is failing due to coverage at 74.3% (required: 80%), but this PR only changes scripts/convert.py and does not touch the Elixir COPI code (lib/copi/...).
Additionally, Comment on PR with build artifacts fails during checkout (git fetch exit code 1), before any code runs.
Please let me know if anything needs adjustment on my side.

@ashnaaseth2325-oss ashnaaseth2325-oss force-pushed the fix/path-validation-security branch from 57b2fac to 848cb52 Compare February 26, 2026 23:16
@ashnaaseth2325-oss
Copy link
Contributor Author

Hello @sydseter
Just following up in case this got buried.
Happy to address any feedback or make further changes.

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.

1 participant