PHPackages                             innobrain/soak-time - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. innobrain/soak-time

ActiveComposer-plugin[Security](/categories/security)

innobrain/soak-time
===================

Protects against supply chain attacks by filtering recently published packages.

v1.10.1(4w ago)21.4k↑16.1%MITPHPPHP ^8.1

Since May 21Pushed 1mo agoCompare

[ Source](https://github.com/innobraingmbh/composer-soak-time)[ Packagist](https://packagist.org/packages/innobrain/soak-time)[ RSS](/packages/innobrain-soak-time/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (10)Dependencies (6)Versions (14)Used By (0)

[![Latest Version on Packagist](https://camo.githubusercontent.com/41df91b3767018a029bd72e19b47b1975d18293c50bf2fe65f63aff775417d4e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f696e6e6f627261696e2f736f616b2d74696d652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/innobrain/soak-time)[![Total Downloads](https://camo.githubusercontent.com/82770e2326e73d7e6f360f9acfbe026f0e33bdcce9989a39d0fadfb37d30f8be/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f696e6e6f627261696e2f736f616b2d74696d652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/innobrain/soak-time)

Innobrain Soak Time 🛡️
======================

[](#innobrain-soak-time-️)

A Composer plugin that enforces a **soak time** — a minimum age — on every package version before install. New releases stay out of the solver pool until they age past the threshold, blocking zero-day malicious releases (typosquats, account takeovers, malicious co-maintainer pushes).

A date filter alone is defeatable: an attacker can force-push an old tag at a malicious commit with a backdated `GIT_COMMITTER_DATE`, and Packagist serves that timestamp. Packagist.org locks the source and dist reference of **stable** versions and refuses moved tags ([composer/packagist#1742](https://github.com/composer/packagist/pull/1742), [docs](https://packagist.org/about/version-immutability)) — but only for stable versions on packagist.org. The plugin pins each version's git SHA, source URL, dist URL, and dist sha256 in `composer-integrity.lock`, extending that protection to dev versions, the local download cache, and non-packagist sources, and hard-fails on any later drift. See [SECURITY\_MODEL.md](SECURITY_MODEL.md).

🧭 How it works
--------------

[](#-how-it-works)

Four checks run on every install/update:

CheckHookCatches**Timestamp filter** (`PackageFilter`)`PRE_POOL_CREATE`Fresh malicious releases — drops versions younger than the soak time from the solver pool. Versions already pinned in `composer.lock` are exempt.**Reference drift** (`ReferenceDriftCheck`)`PRE_POOL_CREATE`Altered historical releases — a backdated `GIT_COMMITTER_DATE` still changes the content-addressed SHA, which can't be forged.**Hash pinning** (`HashVerifier`)`POST_FILE_DOWNLOAD`Cache poisoning at `~/.composer/cache/files/` — re-hashes the downloaded archive (Composer's native sha1 is empty for GitHub zips).**Source pinning** (`PackageIntegrityRecorder`)`POST_PACKAGE_INSTALL` / `POST_PACKAGE_UPDATE``--prefer-source` installs; fails closed if a dist install never exposes its archive.Pins are written to `composer-integrity.lock` when a version is first seen (trust-on-first-use) and verified on every later run.

📦 Installation
--------------

[](#-installation)

```
composer require --dev innobrain/soak-time   # project
composer global require innobrain/soak-time  # all local projects
```

> **Upgrading from ≤ v1.3.0?** `composer update` fails because the old `SoakTimeConfig` is still in PHP memory. Reinstall instead: `composer global remove innobrain/soak-time && composer global require innobrain/soak-time` (or the `--dev` equivalents).

⚙️ Configuration
----------------

[](#️-configuration)

Default soak time is **168h (7 days)**. Configure via `extra` in `composer.json`:

```
{
    "extra": {
        "soak-time-hours": 168,
        "soak-time-whitelist": ["roave/security-advisories", "your-company/*"],
        "soak-time-dev-branches": ["your-company/my-lib"]
    }
}
```

- **Per-run override:** `SOAK_TIME_HOURS=336 composer update` (takes precedence; ignored with a warning if not a non-negative integer).
- **Whitelist** bypasses the soak filter for trusted packages that update constantly. `*` is allowed in the **name** half only — the vendor must be a literal (`your-company/*`, `your-company/lib-*`). Vendor-side wildcards (`*/x`, `*/*`, `*`) are rejected. `SOAK_TIME_SKIP` accepts the same patterns.
- Versions with no release date are filtered unless whitelisted. Whitelist path/internal repos only if you trust their metadata.
- **Already-locked versions are exempt.** The soak window gates *adopting* a fresh version during resolution; a version already pinned in `composer.lock` was adopted earlier, so it passes through. This keeps `composer install` deterministic and stops a partial update (`composer require x`) from failing on an unrelated, freshly-published dependency you already have locked. Adopting a *new* or bumped version still gets gated; the artifact itself is still guarded by the integrity checks.

Windows PowerShell sets env vars as `$env:SOAK_TIME_HOURS=336; composer update`.

### Dev branches (`soak-time-dev-branches`)

[](#dev-branches-soak-time-dev-branches)

Dev versions like `dev-main` or `1.x-dev` are **mutable** — their `sourceReference` (git SHA) legitimately changes every time the branch advances. By default the plugin treats every version as immutable and hard-fails if a pinned reference drifts. That would make `composer update` permanently broken for any dev-branch dependency once the branch advances.

Declare the packages whose dev versions are intentionally mutable:

```
{
    "extra": {
        "soak-time-dev-branches": ["your-company/my-lib", "your-company/*"]
    }
}
```

Or pass the list as a comma-separated env var for a one-run override:

```
SOAK_TIME_DEV_BRANCHES=your-company/my-lib composer update
```

Patterns follow the same rules as the whitelist — vendor must be a literal, `*` is allowed only in the name half.

**Security trade-off:** for a declared dev package, the source reference is allowed to advance when `isDev()` is true. However, if the reference is **unchanged** but the downloaded archive's sha256 differs, the plugin still hard-fails — that is cache poisoning of a fixed SHA, not legitimate branch movement. Stable versions are never treated as mutable regardless of this list.

Undeclared dev versions whose reference changed are blocked with an error that names `soak-time-dev-branches` so you know how to unblock them after investigation.

### Ignoring packages (`soak-time-integrity-ignore`)

[](#ignoring-packages-soak-time-integrity-ignore)

Some Composer plugins install **multiple dist archives under a single `package@version`**. The clearest example is `statamic/cms`, which uses [`pixelfear/composer-dist-plugin`](https://github.com/pixelfear/composer-dist-plugin) to fetch both `dist.tar.gz` and `dist-frontend.tar.gz` — both presenting as `statamic/cms@dist`. The integrity model keys one set of metadata per `package@version`, so the second archive looks exactly like a drifted dist URL and hard-fails:

```
[Soak Time] Integrity metadata drift for statamic/cms@dist.
  Field:     dist URL
  Recorded:  …/v5.73.24/dist.tar.gz
  Candidate: …/v5.73.24/dist-frontend.tar.gz

```

There is no safe way to auto-support this: accepting a new dist URL under an already-pinned version on a package's say-so is precisely the altered-historical-release surface the plugin exists to close. Instead, manually greenlight the package after verifying its installs:

```
{
    "extra": {
        "soak-time-integrity-ignore": ["statamic/cms"]
    }
}
```

Or as a comma-separated env var for a one-run override:

```
SOAK_TIME_INTEGRITY_IGNORE=statamic/cms composer update
```

Patterns follow the same rules as the whitelist — vendor must be a literal, `*` is allowed only in the name half. A listed package is exempt from **all** integrity checks (drift, hash, and recording); a warning naming the ignored package(s) is printed on every run so the weakened coverage stays visible. The soak/freshness filter is unaffected.

🔐 Integrity lock file
---------------------

[](#-integrity-lock-file)

`composer-integrity.lock` records each version's `sha256` (when Composer exposes the archive), `sourceReference`, `sourceUrl`, `distUrl`, and `firstSeenAt`. **Commit it alongside `composer.lock`** — later installs verify against it and hard-fail on drift.

Packages from [`path` repositories](https://getcomposer.org/doc/05-repositories.md#path) are exempt from integrity pinning entirely: they are local code in the same trust domain as the root project, have no archive hash or source reference to pin, and would otherwise fail every install.

Some paths (including plugin self-update) install from dist without exposing the archive; the plugin then fails closed — fix with `composer global reinstall innobrain/soak-time --prefer-source`. Opt out (not recommended) with `soak-time-integrity: false`, or relocate via `soak-time-integrity-lock`:

```
{
    "extra": {
        "soak-time-integrity": true,
        "soak-time-integrity-lock": "composer-integrity.lock"
    }
}
```

🚨 Emergency skip
----------------

[](#-emergency-skip)

Install a fresh security patch by skipping the freshness filter for one package (integrity checks still run):

```
SOAK_TIME_SKIP=vendor/package composer update vendor/package
```

`SOAK_TIME_SKIP=1` skips freshness for the whole run.

🔍 Troubleshooting
-----------------

[](#-troubleshooting)

Run `composer update -v` to see dropped versions. If the soak time hides **every** version of a required package, resolution fails — the plugin names the package and its newest version's age up front. Fix by lowering `SOAK_TIME_HOURS`, whitelisting it, or a one-run `SOAK_TIME_SKIP`.

🙏 Credits &amp; License
-----------------------

[](#-credits--license)

Fork of [`cotonet/soak-time`](https://github.com/cotonet-resiliencia-digital/composer-soak-time) by **Cotonet - Resiliência Digital**. MIT License — see [LICENSE](LICENSE). Copyright Cotonet - Resiliência Digital (original) and Innobrain GmbH (fork).

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance91

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 77.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

13

Last Release

28d ago

PHP version history (3 changes)v1.2.0PHP ^7.4 || ^8.0 || ^8.1 || ^8.2 || ^8.3 || ^8.4 || ^8.5

v1.3.0PHP ^8.2

v1.5.0PHP ^8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/beb1d357716423ec46cf7e3ff0827574b0e17a095bc2cd7b218bf5955056be9b?d=identicon)[innobrain](/maintainers/innobrain)

---

Top Contributors

[![kauffinger](https://avatars.githubusercontent.com/u/62616071?v=4)](https://github.com/kauffinger "kauffinger (17 commits)")[![cotonet-resiliencia-digital](https://avatars.githubusercontent.com/u/254997756?v=4)](https://github.com/cotonet-resiliencia-digital "cotonet-resiliencia-digital (5 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/innobrain-soak-time/health.svg)

```
[![Health](https://phpackages.com/badges/innobrain-soak-time/health.svg)](https://phpackages.com/packages/innobrain-soak-time)
```

###  Alternatives

[phpro/grumphp

A composer plugin that enables source code quality checks.

4.3k16.7M1.0k](/packages/phpro-grumphp)[symfony/runtime

Enables decoupling PHP applications from global state

74298.8M1.1k](/packages/symfony-runtime)[drupal/core-composer-scaffold

A flexible Composer project scaffold builder.

5345.4M570](/packages/drupal-core-composer-scaffold)[drupal/core-vendor-hardening

Hardens the vendor directory for when it's in the docroot.

174.9M48](/packages/drupal-core-vendor-hardening)[drupal/core-project-message

Adds a message after Composer installation.

2124.7M205](/packages/drupal-core-project-message)[drupal-composer/drupal-paranoia

Composer Plugin for improving the security of composer-based Drupal projects by moving all PHP files out of docroot.

642.2M3](/packages/drupal-composer-drupal-paranoia)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
