Software Distribution
Publish versioned software releases, configure secure vault storage, and distribute updates to entitled customers
Overview & security scope
WooNooW Software Distribution manages software releases, changelogs, private artifact storage outside the public webroot, SHA-256 integrity verification, and short-lived one-time download authorization.
The architecture is product-generic: authorization and storage logic make no assumptions about product names, editions, or client frameworks. It serves WordPress plugins, themes, desktop binaries, CLI tools, and arbitrary clients capable of communicating via the website-identity and package protocol.
Local artifact protection allows ZIP files by default; trusted deployments may extend allowed source extensions via the woonoow/software/allowed_artifact_extensions filter. Direct ZIP upload is the default release publication mechanism: operators choose an ordinary ZIP file from their workstation and publish immediately into secure storage, without routing packages through the public WordPress Media Library or configuring pre-existing WooCommerce downloadable file rows.
Zero-setup automatic secure storage & dual storage architecture
WooNooW Software Distribution is designed to work immediately from the WordPress admin dashboard without requiring SSH access, terminal commands (mkdir/chmod), manual directory path entry, or cryptographic key generation. When the module is activated, WooNooW automatically selects and initializes the most secure viable local storage mechanism:
graph TD
A[Admin Publishes Release ZIP] --> B{Outside Webroot Vault Viable?}
B -->|Yes: Verified & Writable Path Outside Document Root| C[Private Filesystem Vault]
B -->|No: Root-Owned Parent / Unverified Webroot / Shared Hosting| D[Encrypted-at-Rest Storage Fallback]
C --> E[Plaintext ZIP in Private Vault Directory]
D --> F[Authenticated Encryption: Sodium Secretstream XChaCha20-Poly1305]
F --> G[Ciphertext .wnwenc in Writable WP Storage]
E --> H[Authorized One-Time Token Download]
G --> H
H --> I[PHP Streams Decrypted Original Bytes to Client]
-
Private Filesystem Vault (Outside Webroot — Primary when viable):
- Used when a writable directory outside the server document root is verified and accessible.
- Canonical packages are placed outside the webroot (e.g.
/srv/woonoow-vault/products/{productId}/{filename}). - Direct web server requests cannot reach the directory because it sits outside every checked web document root.
-
Encrypted-at-Rest Storage (Within Writable WP Storage — Automatic Fallback):
- Automatically activates when the outside-webroot vault is not viable (e.g. managed hosting environments like
notif.inwhere the parent directory above webroot is owned byroot:root, environments withopen_basedirrestrictions, or hosts whereDOCUMENT_ROOTis unverified). - Stores artifacts inside WordPress's standard writable directory (
wp_upload_dir()['basedir'] . '/woonoow-vault'), which is guaranteed writable in functional WordPress installations without SSH or permission adjustments. - Authenticated Encryption at Rest: Packages are encrypted using Sodium Secretstream XChaCha20-Poly1305 (
WNWENC1format) with 64 KB chunking. - Never Plaintext in Public Storage: Uploaded packages are stream-encrypted immediately during ingestion into private staging. Plaintext files are never staged, cached, or written into public or web-accessible folders.
- Server-Managed Key Isolation: Encryption keys (256-bit) are stored in the database (
wp_options,woonoow_software_vault_key) withautoload = 'no'(or overridden viaWOONOOW_SOFTWARE_ENCRYPTION_KEYinwp-config.php). Keys are never stored in files or within the public uploads directory, and are explicitly queried from the database only when software publishing, download streaming, or storage diagnostics require them. - Streaming Decrypted Original Bytes: When an entitled client redeems an authorized one-time token, PHP stream-decrypts the package chunk-by-chunk in bounded memory, delivering the exact original uncorrupted bytes with verified original SHA-256 and byte length.
- Coexistence without Forced Migration: Existing outside-webroot vault artifacts are preserved intact; both drivers coexist in the same database without forced or automatic cross-migration.
- Automatically activates when the outside-webroot vault is not viable (e.g. managed hosting environments like
The public webroot bypass problem & protection limitations
Files uploaded to standard WordPress locations (wp-content/uploads/) are served directly by web servers (Nginx, Apache, LiteSpeed) or cached by CDNs without invoking PHP or WooCommerce access controls. Adding a .htaccess rule, a database permission row, or a custom query parameter does not guarantee security across diverse hosting environments.
For local delivery, WooNooW stores the canonical release binary in a private filesystem vault located outside every document root that PHP can verify. When an entitled client requests an update, PHP streams the package from the vault through a single-use, short-lived token URL rather than exposing a direct file path or Media URL.
Storage modes & operational split
Software Distribution enforces a strict, honest operational split between store merchants, client software updaters, and hosting infrastructure:
Private vault prerequisites (when using outside-webroot storage)
If you explicitly configure or run the dedicated Outside-Webroot Private Vault (storage_driver: local), ensure your hosting environment satisfies these prerequisites. (If your environment does not permit placing files outside webroot, WooNooW automatically runs the encrypted-at-rest driver within writable storage, bypassing these requirements completely):
- Configured Vault Path: By default, WooNooW creates and uses
woonoow-vaultas a sibling directory placed beside (never beneath) the verified document root (e.g./srv/woonoow-vault). You may override this path using theWOONOOW_SOFTWARE_STORAGE_PATHenvironment variable or PHP constant inwp-config.php. - Verified Document Root: WooNooW evaluates
$_SERVER['DOCUMENT_ROOT']. If your environment runs behind a reverse proxy, in Docker containers, or uses symlinked release paths whereDOCUMENT_ROOTis unverified or inaccurate, defineWOONOOW_SOFTWARE_DOCUMENT_ROOTinwp-config.php: - Strict Filesystem Boundary: The vault path must be an absolute path strictly outside every verified document root and public WordPress directory (
ABSPATH). Paths inside public roots are rejected immediately. - No Symbolic Links: The vault path, its parent directories, and internal product folders must not be symbolic links.
- Safe File and Directory Permissions:
- Vault root and product directories: mode
0750(or stricter), owned and writable by the web server process (e.g.www-data). - Staged packages and internal index guards: mode
0640. - WooNooW writes a defense-in-depth index file (
<?php // Silence is golden\n) with mode0640inside the vault and staging directories upon creation. Filesystem placement outside the webroot remains the primary security boundary.
- Vault root and product directories: mode
- Diagnostics Verification: Verify storage health at any time by inspecting the REST endpoint
GET /wp-json/woonoow/v1/software/storage/statusor viewing storage indicators in the Admin SPA.
Encrypted-at-rest storage architecture & container format
When running in standard shared hosting, managed WordPress hosts (such as notif.in), or environments where the parent directory above webroot is owned by root and unwritable by PHP, WooNooW uses Encrypted-at-Rest Storage.
Container binary specification (WNWENC1)
Encrypted packages on disk (.wnwenc) use a compact, versioned, tamper-proof authenticated container:
Cryptographic properties & security guarantees
- AEAD Authentication: Every 64 KB chunk carries a 16-byte Poly1305 MAC computed with additional authenticated data (
WNWENC1). Any modified byte, altered bit, or corrupted sector is detected immediately upon reading. - Truncation & Drop Protection: The final chunk is tagged with
SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL. If an incomplete or truncated file is read, the stream fails authentication and aborts; truncated packages are never delivered to clients. - Reordering & Replay Immunity: Libsodium Secretstream maintains an internal sequential counter state across chunks. Chunks cannot be reordered, swapped, or omitted.
- Bounded Memory Consumption: Decryption buffers only 64 KB in memory at any point. Streaming multi-gigabyte files requires under 1 MB of PHP memory.
- Decrypted Original Bytes Guarantee: The output stream matches the original unencrypted archive byte-for-byte. The package SHA-256 hash reported in the
X-Package-Sha256HTTP header and theContent-Lengthheader strictly reflect the unencrypted release binary.
Key management rules (fail-closed)
- Database Key Storage: The active key is stored in
wp_optionsunder optionwoonoow_software_vault_keyas a 32-byte binary key (hex-encoded) withautoload = 'no'. Because autoload is disabled, WordPress does not query or load the key into memory during normal page views, cart/checkout operations, or unrelated REST API requests. It is explicitly queried from the database only when software release publishing, download streaming, or storage diagnostics require it. - Lazy Automatic Initialization: The encryption key is not generated during plugin installation or module activation. It is generated lazily and automatically upon the merchant's first encrypted release publication (using atomic
add_optionto eliminate concurrent creation races). Store merchants require no SSH access, key generation tools, or server-level setup—ordinary product management and ZIP uploads work out of the box with zero extra merchant work. - No Public Key Files: Encryption keys are never written to disk files, never placed in
wp-content/uploads/, and never exposed in client API responses. - Fail-Closed on Missing or Corrupt Keys: If encrypted releases exist in the database or encrypted ciphertext files (
.wnwenc) exist on disk in the vault, but the encryption key is missing or corrupted, WooNooW strictly fails closed with HTTP500 encryption_key_missingorencryption_key_corrupted. - No Silent Regeneration: WooNooW never silently generates a new key when encrypted releases already exist (either as database release rows or as ciphertext
.wnwencfiles on disk). Silently regenerating a key would permanently orphan and corrupt existing encrypted releases. A new key is generated atomically (usingadd_optionto eliminate concurrent creation races) only when zero encrypted releases exist in the store and vault. - Server Override: Operators may define
WOONOOW_SOFTWARE_ENCRYPTION_KEY(32-byte raw or 64-character hex) inwp-config.phpto manage keys outside the database.
Honest backup, restore, and migration model
Because WooNooW Software Distribution couples database records with filesystem artifacts, administrators must observe an honest, disciplined backup strategy:
graph LR
subgraph Synchronized Backup Pair
DB[(MySQL Database)]
FS[Filesystem Storage]
end
DB -->|Holds Keys, Versions, Entitlements| R1[Recovery Point]
FS -->|Holds Encrypted or Vault Binaries| R1
1. Coupled backup requirement (Database + Files together)
- The Database holds: Software version rows, release metadata, SHA-256 checksums, entitlement snapshots, and the server-managed encryption key (
woonoow_software_vault_key). - The Filesystem holds: The encrypted packages (
wp-content/uploads/woonoow-vault/products/{id}/{name}.wnwenc) or vault packages (woonoow-vault/products/{id}/{name}.zip). - Restoration consequence:
- Restoring files without database: Download tokens cannot be issued or claimed. The releases do not exist in WooCommerce.
- Restoring database without files: Download token issuance succeeds, but redemption fails with
404 artifact_not_found. - Restoring older database with newer files: Newer files will be unrecognized by the older database. A subsequent upload of the same version safely reuses the verified on-disk artifact.
- Restoring older files with newer database: If an encryption key was changed or files are missing, redemption fails closed with
encryption_key_missingor checksum mismatch errors.
2. Migrating between servers or hosting providers
When migrating your store from one host to another:
- Export the complete MySQL database.
- Copy the entire WordPress uploads directory, specifically including
wp-content/uploads/woonoow-vault(for encrypted storage) or the externalwoonoow-vaultdirectory (for private vault storage). - Ensure the destination server has the PHP
sodiumextension enabled (ext-sodium, bundled in PHP 7.2+). - If moving from outside-webroot storage to shared hosting (or vice-versa), existing release rows retain their assigned
storage_driver(localorlocal_encrypted). WooNooW does not automatically alter or force-migrate existing records. Both storage types operate simultaneously without conflict.
Comprehensive threat model & security boundaries
WooNooW Software Distribution implements multiple overlapping security boundaries. This matrix defines what the security architecture protects against and where hosting audits remain necessary:
Architecture & product modeling
Install and enable WooNooW once on the selling WordPress store. That one store-level installation can manage many software products and releases. Within it, distribution maps directly to the WooCommerce catalog:
- One parent product per authorization boundary and release stream: Each independently licensed software product or edition should have its own parent WooCommerce product and unique software slug.
- Variations determine commercial terms and entitlements, not release streams: Variations belonging to one parent (such as annual/lifetime purchase options or different activation limits) can determine pricing, usage duration, activation limits, update windows, and support rights. They do not create independent software slugs or release streams.
- Artifact origin flexibility within one parent: A release source may be selected from a simple parent product or any variation owned by a variable parent. The resulting release always belongs to the parent stream. If all variations receive the same binary, attach it to one chosen variation and reference that artifact when publishing the parent release; do not publish one release per commercial variation.
- Separate parent products remain separate: If two parent products intentionally use the same bytes, each still needs its own authorized release record because product entitlement and vault storage are parent-scoped. A license for one parent never authorizes the other. Avoid pointing two parents at one public source that must be deleted: the shared-reference audit will correctly block deletion until each dependency is migrated.
Illustrative catalog example
Consider a vendor selling two editions of an application with different feature sets:
Note: The labels "Standard Edition", "Enterprise Edition", "Personal Annual", and "Team Lifetime" are arbitrary merchant catalog data, not hardcoded plugin behaviors. You may model your catalog using simple or variable products to suit your business needs.
Module setup & dependencies
Both Software Licensing and Software Distribution are disabled by default.
To enable them:
- Navigate to WooNooW → Settings → Modules.
- Enable Software Licensing (
licensing) when distributing protected software. - Enable Software Distribution (
software).
Dependency and fail-closed behavior
The relationship between Licensing and Software Distribution is strictly enforced:
- Public software: Products configured with
_woonoow_licensing_enabled: "no"can distribute packages without requiring active licenses or the Licensing module. - Protected software: Products configured with licensing enabled (or missing/invalid licensing meta, which defaults to protected) require the Licensing module.
- Fail-closed protection: If Software Distribution is enabled while Licensing is disabled, requests to check updates, fetch package tokens, or download protected software fail closed with HTTP
503 licensing_unavailable. WooNooW never downgrades a protected product to public access when Licensing is inactive.
Product configuration workflow
To prepare a product for software distribution:
- Assign a Unique Stable Slug to the Parent: Enter a unique slug in the parent product's Software Slug field (
_woonoow_software_slug). Product resolution is fail-closed: the slug must match exactly one published parent product. If no product matches, WooNooW returnsproduct_not_found; if duplicate published products share a slug, it returnssoftware_slug_ambiguous. - Enable Software Updates: Check Enable Software Updates (
_woonoow_software_enabled). - Confirm Licensing Policy: Ensure Enable Licensing (
_woonoow_licensing_enabled) is checked unless the product is deliberately free and public. - Configure WordPress Integration (Optional): If distributing a WordPress plugin or theme, check WordPress Plugin/Theme (
_woonoow_software_wp_enabled) and provide:- Requires WP (
_woonoow_software_requires_wp, e.g.6.4) - Tested WP (
_woonoow_software_tested_wp, e.g.6.7) - Requires PHP (
_woonoow_software_requires_php, e.g.7.4)
- Requires WP (
Save the product to persist these settings.
Filename templates & output conventions
WooNooW standardizes package naming when creating vault copies.
- Default template:
{slug}_v_{version}.zip - Available template variables:
{slug}— The product software slug.{version}— The release version string.{product_id}— The WooCommerce parent product ID.{name}— The sanitized product title.{ext}— The package file extension (typicallyzip).
Merchants can configure a site-wide template under WooNooW → Settings → Modules → Software Distribution, or specify an override per product via _woonoow_software_filename_template (e.g. {slug}-app-v_{version}.zip).
Output organization boundary
Filename templates exist solely for merchant file organization and client header presentation. Clients, automated updaters, and third-party scripts must never infer a storage bucket, private vault path, Media Library URL, or target version string from the filename structure.
Release publication standard operating procedures
WooNooW provides two distinct local publication workflows: the modern Direct ZIP Upload (default) and the Legacy WooCommerce Download Migration workflow.
Primary SOP: Direct ZIP Upload (Local Vault - Default)
Use this default procedure to publish a software release directly from a local .zip file on your computer.
Step 1: Open Software Versions
- Navigate to WooNooW → Products → Software Versions.
- Select your software product from the list.
- Click New Version.
Step 2: Configure release details & choose file
- Ingestion Mode: Defaults to Direct ZIP Upload (Local Vault - Default).
- Package ZIP File: Click the file picker (
Choose File) and select the release.ziparchive from your workstation. - Version Number: Enter a consistently ordered version string, such as
1.2.0. Semantic Versioning is recommended, while core ordering uses PHPversion_compare(). Once published, the version string, vault key, and checksum are immutable. - Live Template Preview: Inspect the real-time preview card showing:
- Software Slug: The product's configured slug.
- Product ID: The WooCommerce parent product ID.
- Target Vault Filename Preview: The rendered filename (e.g.
my-plugin_v_1.2.0.zip) according to the active filename template.
- Custom Filename Override (Optional): If you need a specific target filename, enter it in the custom override field. WooNooW validates the name against the product's filename template pattern and sanitizes directory traversal sequences (
..,/,\,\0). Leave empty to use the standard template. - Changelog: Enter an optional narrative overview and add structured change points (
ADD,FIX,CHANGE,REMOVE,SECURITY,DEPRECATE). - Set as Current Release: Ensure Set as current release is checked (enabled by default) if this version should immediately become the active release for update checkers.
Step 3: Publish release
Click Add Version. The Admin SPA builds a multipart payload and streams the archive directly to POST /wp-json/woonoow/v1/software/products/{product_id}/versions.
Under the hood: Direct upload guarantees
- No WooCommerce Artifact ID Required: Direct upload requires no pre-existing WooCommerce downloadable file entry or manual
artifact_download_idlookup. The server generates a unique UUIDv4 (artifact_download_id) automatically upon ingestion. - Automatic SHA-256 Checksum & File Sizing: The server streams the staged package into
{vaultDir}/products/{productId}/{filename}, calculating the SHA-256 hash and byte size in-flight. No client-side checksum entry is required. - Master Client File Unaffected, PHP Temp Consumed: The master archive on your workstation is completely unaffected. On the server, standard PHP HTTP upload places the file in temporary storage (
$_FILES['file']['tmp_name']). WooNooW verifiesis_uploaded_file(), stages it into private vault staging ({vaultDir}/staging/{uuid}.zip) viamove_uploaded_file()(which safely consumes PHP's temporary file), applies safe permissions (0640), verifies generic ZIP integrity, copies exclusively into the product vault, and unlinks the staging file immediately. - Generic ZIP Structure & Integrity Validation: WooNooW validates generic ZIP structure:
- Inspects binary magic headers (
PK\x03\x04,PK\x05\x06,PK\x07\x08). - Opens and validates archive consistency with
ZipArchive::open(). - No WordPress Header Inspection: The archive validator treats ZIP packages as generic opaque software bundles (supporting WordPress plugins, themes, desktop applications, CLI binaries, or arbitrary ZIP archives). It does not inspect or enforce WordPress-specific plugin header comments or require specific PHP files.
- Inspects binary magic headers (
- Staging Cleanup & Safe Vault Retention on Failure:
- Staging Temporary Files Always Cleaned Up: Temporary staging files (
{vaultDir}/staging/{uuid}.zip) are unlinked immediately following ingestion into the product vault or upon validation failure. - No Eager Deletion of Ingested Vault Artifacts: If version publication fails (whether on pre-draft conflicts like duplicate versions or mid-transaction commit rollbacks), successfully ingested vault artifacts are preserved safely in the private product vault rather than eagerly deleted. This eliminates concurrency races where one failing upload could delete the artifact out from under another simultaneous upload.
- Staging Temporary Files Always Cleaned Up: Temporary staging files (
- Safe Retry & Verified Reuse: On publication retries or subsequent versions using identical packages, WooNooW verifies the SHA-256 and byte size against the existing vault file and safely reuses the verified artifact.
- Zero Absolute Path Exposure: Failure error responses return safe recovery metadata (
storage_key,recovery_action) without leaking server-internal absolute filesystem paths. - Signatures Optional & Nullable (No Input Support): The database schema includes columns for
signature(text, nullable),signing_key_id(varchar, nullable), andsigned_at(datetime, nullable), and API response payloads include these fields (defaulting tonull). However, neither the direct multipart upload endpoint nor the Admin UI currently provides input fields or supports passing cryptographic signatures or signing keys during version publication. Releases are published unsigned with null signature metadata.
SOP: Private Artifact Library (Upload Advance & Select Package)
WooNooW features a dedicated Product-Scoped Private Artifact Library. This workflow decouples package uploading from release publication:
- Upload Ahead of Time: Store operators can upload software bundles before deciding on the final release version number or writing changelogs.
- Reuse Packages Without Re-uploading: The same build archive can be selected for multiple versions (e.g. testing in a draft, publishing an initial version, and referencing the package in subsequent maintenance releases).
- Product-Scoped Privacy: Packages in the artifact library are stored strictly in the product's private vault (or authenticated encrypted vault) outside the public webroot. They are never imported into the WordPress Media Library or accessible without authorization.
- Independent Original Filenames: The artifact library preserves the original source filename (e.g.
AppBuild-v3.0.0-rc2.zip), while the published release automatically renders client binaries according to the product's distribution filename template (e.g.app-slug_v_3.0.0.zip). - Fail-Closed Cryptographic Key Safety: Unreferenced library packages stored under
local_encryptedare automatically detected bySoftwareStorage::has_encrypted_artifacts(). WooNooW strictly prevents accidental or silent key regeneration whenever encrypted library packages exist, ensuring existing bytes remain decryptable.
Step 1: Manage Artifacts via Artifact Library
- Navigate to WooNooW → Products → Software Versions.
- Select your software product from the list.
- Click Artifact Library in the header.
- Review existing packages with their original filenames, byte sizes, upload dates, checksum previews, and release usage badges.
- To upload a new package, select a
.ziparchive under Upload New Package to Library. WooNooW streams the file directly to private vault staging, verifies generic ZIP integrity, computes the SHA-256 checksum, and adds it to the product's durable library. - Click Release as Version on any package to immediately launch the New Version modal with that package selected.
Step 2: Publish a Release Using an Uploaded Package
- In Software Versions, click New Version.
- Under workflow options, select Select uploaded package.
- Use the search input to filter packages by filename.
- Select the desired package from the list. The UI displays the package details, checksum preview, and whether the package is unused or already linked to existing release versions.
- Enter the Version Number (e.g.
2.0.0). - Review the Target Vault Filename Preview or specify an optional Custom Filename Override.
- Enter the changelog and click Add Version.
- WooNooW binds the release to the server-authoritative artifact ID, verifies physical file integrity against the database SHA-256 and size, and publishes the release.
Legacy SOP: Existing WooCommerce Download (Legacy Local Vault)
Use this workflow only when migrating pre-existing downloadable files attached to WooCommerce products or stored in wp-content/uploads/.
Step 1: Prepare the WooCommerce downloadable file
- For a simple product, edit the parent, mark it Downloadable, and attach the ZIP under Downloadable files. For a variable product, attach it to one owned variation.
- Save the product. WooCommerce assigns a stable
download_id(the artifact ID).
Step 2: Configure legacy migration in Software Versions
- In Software Versions, click New Version.
- Set Ingestion Mode to Existing WooCommerce Download (Legacy Local Vault).
- Enter the Version Number and paste the exact WooCommerce
download_idinto the WooCommerce Download Artifact ID field (artifact_download_id). - Review the Authorize safe deletion of public source file from Media Library (Destructive) checkbox (
delete_public_source):- Unchecked (
delete_public_source: false): Publication from a public Media/uploads source stops with HTTP409 source_still_publicbefore a deliverable release is created. The original file remains untouched. - Checked (
delete_public_source: true): WooNooW stages the private vault copy, creates a draft, detaches matching native WooCommerce download entries, audits shared references, and permanently unlinks the public source file.
- Unchecked (
The legacy migration pipeline under the hood
- Exclusive verified copy: Streams bytes from the uploaded file into
woonoow-vault/products/{product_id}/{filename}, calculating the SHA-256 checksum in-flight and applying strict permissions (0750for directories,0640for files). - Durable draft creation: Inserts a complete non-current
draftrow (draft_version_id) into the database. - WooCommerce download detachment: Snapshots
_downloadable_filesacross the parent product and all child variations, then removes all entries matching this source. - Reference auditing and safe deletion: Audits known references (other release rows, post meta, post content, and attachment parent links). If another reference exists, deletion is blocked with
409 source_has_shared_references, the public file remains intact, and detached entries are restored. Only when verified clear, WooNooW unlinks the public file. - Final publication: Revalidates the vault artifact and transactionally publishes the draft.
Draft recovery workflow
If publication halts after draft creation (for example, due to a reference, deletion, validation, or database failure):
- The API returns the error along with
draft_version_idandrecovery_action: "review_release_draft". - The release appears in the Admin UI with a Draft status badge and warning details.
- The staged vault copy is preserved; WooNooW does not delete it automatically.
- Operators can inspect the warning, resolve the shared reference or click Delete Public Source File when applicable, then use Publish Draft to revalidate and publish without re-uploading the package.
Package streaming (not a Media URL redirect)
WooNooW never simply redirects clients to a WordPress Media URL.
When an entitled client requests an update:
- The client calls
POST /wp-json/woonoow/v1/software/packagewith its license key and installation UUID + domain. - WooNooW verifies entitlements and mints a short-lived bearer token URL (default TTL: 5 minutes).
- The client calls
GET /wp-json/woonoow/v1/software/download?token={token}. - WooNooW atomically consumes the token in a short database transaction.
- PHP streams the package directly from the private vault with headers:
Content-Type: application/zipContent-Length: {size}X-Package-Sha256: {sha256}Accept-Ranges: noneCache-Control: no-cache, must-revalidate
Requests with Range headers return 416 range_not_supported, and HEAD requests return 405 head_not_supported, preserving the single-use token from accidental consumption.
External releases SOP
For packages hosted on external infrastructure (such as vendor CDNs or public mirrors):
- Create a WooCommerce downloadable file entry containing the external
https://URL. - In Software Versions, click New Version.
- Select External Link (Manual download only).
- Paste the exact WooCommerce Download Artifact ID into the current text input. The selected entry must belong to the parent or one of its variations and contain the HTTPS URL.
- Enter the exact Package SHA-256 (64 lowercase hexadecimal characters). WooNooW strictly requires this checksum at publication time.
- Click Release Version.
External delivery constraints
- Manual download only: External releases set
manual_download_only: true. They never issue automatic-updater bearer tokens. The manual receiving workflow should hash the provider's completed bytes and compare them with WooNooW'sfile_sha256metadata before installation. - Gated manual access: For a protected product, the client calls
GET /wp-json/woonoow/v1/software/products/{product_id}/versions/{version_id}/external-linkwith the license and installation identity. The route returns JSON containing the provider URL; it does not proxy the bytes or automatically redirect the browser. Explicitly public products retain the purchaser-login/admin gate. - Boundary limitation: Once the external HTTPS URL is disclosed to the client, it is outside WooNooW's revocable byte-delivery boundary. WooNooW cannot revoke access to third-party URLs or prevent link sharing once exposed.
- No built-in cloud storage drivers in core: WooNooW core does not contain built-in integrations for Amazon S3, Cloudflare R2, Google Drive, or Microsoft OneDrive. A dynamically generated private/presigned provider URL requires a custom addon contract through the core extension filters; merely storing one expiring URL as a core external release is not a refreshable integration. Public external URLs must never be assumed private.
Release lifecycle: withdrawal and republishing
Withdrawing a release
If a security flaw or critical regression is discovered in a published release:
- In Software Versions, find the release row and click the Withdraw button.
- Provide a withdrawal reason (e.g. "Critical security advisory CVE-XXXX").
- Confirm withdrawal.
Withdrawal semantics:
- Status changes to
withdrawnandwithdrawn_atis recorded. - The release is immediately removed from current status and excluded from all update checks and package resolution.
- WooNooW transactionally invalidates all outstanding unclaimed bearer tokens issued for that release.
Republishing a release
To reinstate a withdrawn release or publish a recovered draft:
- Click the Republish button on the release row.
- Choose whether to mark it as the current release (
set_current: true).
Republishing semantics:
- Preserved publication cutoff: For a withdrawn release, republishing preserves the original
published_attimestamp. It does not reset the release date or artificially extend customer update entitlement windows. - Invalidated tokens stay dead: Previously invalidated download tokens are never revived. Clients must perform a fresh update check to receive a new package token.
- Draft publication: For a new draft release, the first successful publication sets
published_atto the current UTC timestamp.
Entitlement policies and issuance snapshots
When a protected software release is published, client access is governed by the shared entitlement engine (LicenseEntitlements::authorize_release()).
Review the Entitlements Concept Overview for complete architectural specifications.
Key entitlement dimensions evaluated during release selection:
- Usage validity: The license must be active, unrevoked, and within its usage term.
- Update cutoff (
updates_expires_at): A release published after the cutoff is blocked even when general usage remains active. Once the window expires, pre-cutoff releases are available only when historical access permits them. - Historical access: If the license has
historical_downloads: true, all three purposes may receive an otherwise matching release published on or before the update cutoff, even after that window has elapsed. - Purpose equality: Purpose parameters (
install,reinstall,update) are checked equally against entitlement cutoffs; a client cannot bypass an expired update right by requesting aninstall.
Immutable issuance snapshot
Policies are configured at the parent product or variation level via _woonoow_entitlement_policy and can be inspected or updated via REST API:
When an order completes, WooNooW snapshots the policy immutably into the license record. Subsequent changes to product catalog policies do not alter rights granted to existing licenses.
SOP: Software Artifact Deletion & Admin-Controlled Retention (P0)
WooNooW provides store operators with complete, transparent control over server storage. Operators can permanently delete unneeded package files—whether unused, associated with historical releases, or even currently active—from the WordPress dashboard without requiring SSH access or raw filesystem knowledge.
Decision principle: Admin explicit consent vs. historical veto
Storing every historical software archive indefinitely is neither sustainable nor a policy that software developers should impose on merchants. Growth in package archives consumes local disk space and shared cloud object storage quotas across projects.
- Revision of the Historical Veto (O-04): Earlier design guidelines forbade cleanup from touching historical releases if customers held reinstall rights. This has been revised: WooNooW never deletes files automatically or silently, but store administrators may permanently delete historical packages after reviewing an honest impact preview.
- Customer Entitlements as Impact Information: A customer's historical download entitlement is treated as business impact information for the merchant, not an immutable technical lock that forces infinite byte retention. The merchant decides their service policy.
- Never Eager Delete on Upload/Publish Failure: When version publication or file ingestion fails (due to validation errors, pre-draft conflicts, or mid-transaction rollbacks), successfully staged vault artifacts are safely preserved in the private vault rather than eagerly deleted. This eliminates concurrency races between simultaneous uploads and ensures safe draft recovery. Automatic cleanup applies only to temporary staging files (
{vaultDir}/staging/{uuid}.zip).
Withdrawal vs. Physical deletion
Store operators must understand the critical operational difference between withdrawing a release and deleting an artifact:
Hapus File is NOT Version Replacement: Deleting an artifact does not erase the release version string from the database. The unique (product_id, version) constraint remains permanently allocated, and release changelogs, publication dates, and SHA-256 checksums are preserved in the audit trail. Publishing updated code requires a new, distinct version number.
Step-by-Step: Deleting artifacts from the Admin UI
Entry point 1: From the Artifact Library
- Navigate to WooNooW → Products → Software Versions.
- Click Artifact Library in the header.
- Locate the package you wish to remove. Review its original filename, physical stored size, and release usage badges.
- Click the Delete button (trash icon) on the artifact card or table row.
- To delete multiple artifacts, select the checkboxes beside the target packages and click Delete Selected.
Entry point 2: From the Software Versions table
- In Software Versions, locate the release row whose package file you want to delete.
- Click the action menu and select Delete package file.
- WooNooW resolves the bound physical artifact and all other releases that reference the same underlying file.
Tiered impact acknowledgement
Before any physical deletion occurs, WooNooW generates an impact preview modal. Depending on how the file is used, the modal requires explicit confirmation checkboxes:
- Unused Packages: Low-friction confirmation displaying the original filename, plaintext ZIP size, and physical stored bytes. Confirms permanent deletion from disk.
- Referenced Historical Releases: Displays the list of affected release versions. Requires checking:
I acknowledge that affected releases will no longer be available for customer download or reinstall, including for members previously entitled to these versions.
- Current Release: If deleting the package bound to the active current release, requires checking:
I acknowledge that the Current Version flag will be removed and no older release will be automatically promoted to current.
- Last Remaining Artifact: If no other packages remain for the product, requires checking:
I acknowledge that no downloadable packages will remain for this product. New installs, reinstalls, and updates will be unavailable until a new package is uploaded.
- Shared Physical Packages and Cross-Product Scope: If a single archive is bound to multiple releases or shared across products, the preview modal lists all referencing versions and affected products. Deletion requires the actor to have
edit_postpermission on all affected products and acknowledge the cross-product impact (cross_product_impact). Cross-product deletion is allowed with all affected products consent/permissions, not an absolute prohibition. Deletion withdraws all associated releases across all affected products while performing exactly one physical file unlink.
Click Permanently Delete File(s) to confirm.
Current release & cutoff semantics
- No Auto-Promotion: Deleting the current release clears
_current_versionand the releaseis_currentflag. WooNooW never automatically promotes an older release to current. Operators may explicitly assign another release as current or leave the product without a current version. - Strict Cutoff Enforcement (No Free Upgrades): If an entitled customer's license update window expired on a historical version whose package was deleted, the update resolver returns
no_eligible_release. WooNooW strictly prohibits falling back to newer releases outside the customer's purchase entitlement.
Physical stored bytes vs. Plaintext ZIP size
The storage summary (GET /software/storage/usage and the UI header) clearly distinguishes between:
- Stored Bytes: The actual physical disk blocks occupied by the managed file. For unencrypted local files, this matches the file size. For
local_encryptedstorage, this reflects the.wnwencciphertext archive on disk. - Plaintext ZIP Size: The uncompressed/plaintext byte size delivered to customers during download streaming.
- Measurement Confidence: Stored statistics report
measurementasknown(all objects have verified exact stored byte counts),estimated(some objects fallback to legacy catalog file sizes), ormixed(some objects have unknown sizes where neither stored bytes nor catalog file sizes exist). - Encryption Key Safety: When deleting an encrypted artifact (
local_encrypted), WooNooW unlinks the.wnwencfile only. The Sodium Secretstream encryption key stored in the database is never deleted, rotated, or regenerated, ensuring other encrypted packages remain decryptable. - Corrupt & Missing-Key Deletion: Packages that fail integrity checks or have lost encryption keys can still be deleted if their product vault placement is verified. Decryption is not required to delete unneeded bytes.
Active downloads & POSIX delayed block release
- File Descriptor Pinning: During download streaming, WooNooW pins open file descriptors.
- POSIX File System Unlink: When a file is unlinked on Linux/macOS filesystems while an authorized download is in progress, the directory entry is removed immediately, but the physical disk blocks remain allocated until the streaming process closes the file handle. Active downloads complete without interruption.
- Billing & Backup Latency: Disk blocks are freed by the operating system once handles close. Local server backups, cloud snapshots, and provider storage tier billing operate on independent cycles and do not reflect immediate reductions.
Two-phase journal, retry, and no fake "Undo"
- Durable Journaling & Idempotency Surviving Preview Expiry: Deletions are coordinated via database journal tables (
woonoow_software_deletion_jobsandwoonoow_software_deletion_items), decoupling destructive unlinking from short-lived web requests. Replaying an execution request with the same idempotency key and matching scope/preview token returns the committed operation without re-initiating unlinks, even if the preview transient has expired or been consumed. - Asynchronous Processing, Retry & Exponential Backoff: Background workers process deletion queues. When an item encounters a retryable storage failure, workers set its status to
pendingwithoutcome = 'retryable_failure'and apply an exponential backoff delay (min(300, 2^attempts * 30)seconds). The Retry action re-enqueues bothfaileditems and pending items inretryable_failureoutcome, resetting them to pending with 0 attempts for immediate execution. - Verified Post-Stat Absence: When using addon storage drivers, both
deletedandalready_missingoutcomes require verified post-stat identity absence viastat_managed_artifact. Missing files and delete markers report 0 removed bytes (delete markers do not count as released bytes). Absence evidence code strictly rejects raw HTTP 4xx/5xx status codes and failure keywords, requiring normalized named strings (not_foundorconfirmed_absent). WooNooW core contains no cloud storage driver implementation. - API Redaction & Storage Key Contract: Absolute filesystem paths are never exposed in API responses, but relative
storage_keyis retained in baseline artifact and version publication success response contracts. - No Undo Button: WooNooW provides no fake "Undo" button or trash bin. Physical bytes are permanently unlinked. Restoring deleted packages requires the operator to restore the archive from external offsite backups and upload it as a new version.
Roadmap boundary: P0 vs. Future P1/P2/Cloud
- P0 (implemented; automated verification recorded TESTING_CHECKLIST; live host acceptance pending): Manual deletion of unused, historical, current, last, and shared artifacts; two-phase durable journaling; local/encrypted storage deletion; admin impact preview dialog; storage usage summary.
- P1 (Future Scope): Automatic upload deduplication for identical packages and controlled orphan file reconciliation.
- P2 (Future Scope): Scheduled automatic retention policies (e.g. retain last N versions or packages older than X months).
- Cloud Addons (Future Scope): Cloudflare R2 and Amazon S3 direct storage integration via capability hooks.
Storage diagnostics and host verification checklist
Administrators can inspect storage health via REST API (manage_woocommerce capability required):
A representative response:
Filesystem status vs. host verification
Notice the distinction between the two status fields:
filesystem_status: "secure": Confirms that PHP's filesystem checks passed. The directory exists outside known document roots, permissions are0750, files are0640, and no symlinks exist in the path.status: "host_verification_required": Confirms that hosting-level verification remains necessary. PHP cannot inspect web server configuration files (Nginx vhosts, Apache virtual hosts), web server alias directives, proxy/CDN caching rules, or public file URLs that were cached before migration.
Operator host verification checklist
Complete these verification steps before distributing software in production:
- Configure the Real Document Root: If your server does not expose an accurate
DOCUMENT_ROOT(common in reverse-proxy, Docker, or symlinked deploy setups), defineWOONOOW_SOFTWARE_DOCUMENT_ROOTinwp-config.php: - Verify Vault Directory Placement: By default, WooNooW places
woonoow-vaultas a sibling directory beside your verified document root. Override withWOONOOW_SOFTWARE_STORAGE_PATHif needed. - Register Additional Public Roots: If your host serves public files from alternate directories (e.g. a static asset domain or upload subdomain), register them using the
woonoow/software/additional_public_document_rootsfilter. - Audit Permissions: Verify that the vault directory is owned by the web server user (e.g.
www-data), with directory mode0750and file mode0640. Group-writable or world-readable permissions fail security checks. - Perform Unauthenticated HTTP Probes: Audit the web-server/vhost configuration for aliases that map to the absolute vault directory. From an external client, probe every plausible mapped path and the exact old Media URL. A direct vault URL should not exist; every probe must return
403or404. If bytes are returned, reconfigure the host before release. - Purge CDN and Old Upload Caches: After publishing a release with public source deletion, verify that the former Media Library URL returns
404 Not Foundacross all CDN edge nodes and reverse proxies. - Coupled Backup Strategy: Always back up the MySQL database and the private vault directory together. The database holds the release records, SHA-256 bindings, and entitlement snapshots; the vault holds the actual binary files. Restoring one without the other leads to integrity errors.
- Measure Server Concurrency and Capacity:
- WooNooW core streams local downloads using PHP's
readfile(). Web-server direct offloading (X-Accel-RedirectorX-Sendfile) is not implemented in core. - Streaming a 25 MB package ties up a PHP-FPM worker for the duration of the customer's download. A burst of 25–50 customer sites can therefore occupy a comparable number of PHP workers while transfers remain active.
- Do not assume your server can handle 25–50 concurrent large downloads without verification. Conduct measured load tests against your specific host plan, PHP-FPM process limits, and network throughput before launching major releases.
- WooNooW core streams local downloads using PHP's
Pre-flight release checklist
Before making a software version available to customers, verify each item on this checklist:
- Modules Enabled: Software Distribution is active; Software Licensing is also active for protected products.
- Product Setup: The parent has a unique Software Slug and software updates enabled; licensing policy is explicitly confirmed.
- Catalog Entitlements: Explicit update/support/history policy is configured on the parent or variations, or legacy behavior is intentionally accepted.
- Secure Storage Verified: Outside-webroot private vault is verified (
filesystem_status: secure), or encrypted-at-rest storage is active (storage_mode: encrypted_at_rest) with verified Sodium Secretstream and non-autoload database key. - Direct Access Probed: Every plausible host alias and former upload URL were tested externally; no plaintext artifact bytes are returned.
- Package Ingested: The release package is uploaded directly into secure storage (or migrated from a legacy WooCommerce download with explicit source deletion authorized and references audited).
- Checksum Verified: Server-computed SHA-256 hash matches the exact binary compiled by your build pipeline.
- Changelog Formatted: Narrative overview and structured points (
ADD,FIX, etc.) are documented. - Coupled Backup Verified: Database (including
woonoow_software_vault_keyoption) and software storage directory (wp-content/uploads/woonoow-vaultor outside-webroot vault) are backed up together. - Load Capacity Tested: Server PHP-FPM concurrency limits are tuned for anticipated download spikes.
Last updated Sep 8, 2026