Sthora
Deploying applications

Deploying from a ZIP

Uploading source directly, the limits an archive has to stay inside, and why an archive is refused whole.

A ZIP upload needs nothing connected anywhere — no repository, no key, no webhook. It is the fastest way to prove the platform works, and it stays useful for anything not in Git.

Upload

On the application, upload a .zip of your source. POST /deployments is multipart/form-data — the only non-JSON request body in the API. The upload lands in the control plane's staging directory (DEPLOY_UPLOAD_STAGING_PATH, default /var/lib/sthora/uploads) and is then moved to the managed server by the driver.

Limits

LimitDefaultSetting
Archive size200 MBDEPLOY_MAX_ARCHIVE_BYTES
Entry count20,000DEPLOY_MAX_ARCHIVE_ENTRIES
Extracted size2 GBDEPLOY_MAX_EXTRACTED_BYTES

An archive past any of these is refused whole, not partially extracted. A half-extracted zip bomb is still a full disk.

What extraction refuses

Extraction rejects the entire archive on the first entry that:

  • resolves outside its destination directory,
  • carries an absolute path, or
  • is a symlink pointing outside the tree.

Why the whole archive

Zip-slip is the first thing anyone will try on a deployment platform. Refusing one entry and continuing leaves a tree that is partly the attacker's and looks like a successful upload; refusing the archive is the only outcome that is easy to reason about. The guard lives in archive-entry-path.ts and has its own regression suite.

Archives are opened with decodeStrings: false, so the platform's own path guard decides how an entry name is interpreted rather than the zip library.

Then

Detection runs exactly as it does for a Git clone — see build types. Nothing downstream of source materialisation knows or cares where the tree came from.

On this page