Skip to content

Dependency cooldowns - #860

Open
filippomc wants to merge 6 commits into
developfrom
feature/CH-286-dependency-cooldowns
Open

Dependency cooldowns#860
filippomc wants to merge 6 commits into
developfrom
feature/CH-286-dependency-cooldowns

Conversation

@filippomc

@filippomc filippomc commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Closes CH-286

Implemented solution

Mitigates supply-chain attacks across both stacks: package versions published in the
last 7 days are never installed, and application images now install from lock
files. Most malicious releases are detected and pulled within days of publication;
the cooldown window skips that blast window entirely, at the cost of adopting genuine
releases a week late.

What's included

Python cooldown

  • Every pip install in the repository (Dockerfiles, dev-setup.sh, install.sh,
    CI configs, application templates) carries --uploaded-prior-to=P7D.
  • Deliberate exceptions: pip install --upgrade pip (bootstraps the pip that
    understands the flag) and installs from pylock.toml (the cooldown was applied at
    lock time; pip errors otherwise).
  • Requires pip ≥ 26.1 / Python ≥ 3.10: blueprint codefresh template bumped
    python:3.7 → 3.12, test/docker-compose.yml 3.9 → 3.12.

JavaScript cooldown (Yarn 1 → Yarn 4 migration)

  • Yarn Classic has no cooldown mechanism, so frontends moved to Yarn 4.18
    (pinned via packageManager, installed through corepack — no binary committed).
  • npmMinimalAgeGate: "7d" lives in exactly two places: the root .yarnrc.yml
    (developers; yarn merges rc files from ancestor directories) and the
    cloudharness-frontend-build base image (container builds, incl. downstream apps).
  • Yarn 4.18 disables dependency postinstall scripts by default — the main npm attack
    vector; re-enabled only for puppeteer via dependenciesMeta.
  • --frozen-lockfile--immutable everywhere; --timeout (a hard error in
    Yarn 4) replaced by httpTimeout/env.

PEP 751 lock files for applications

  • Each application that installs a requirements.txt carries a pylock.toml beside
    it, and its Dockerfile installs from the lock (full tree, hashes).
  • Locks are generated inside a throwaway container from the application's own base
    image
    : requirements are installed there first and a freeze constrains pip lock,
    so the lock records what the deployed image really provides (e.g. setuptools pins
    to the base image's version). Nothing of the lock toolchain enters deploy images.
  • Only applications are locked. Base images, libraries/ and tools/ stay
    unlocked so apps keep inheriting their updates.

harness-generate dependencies

New generation mode that audits and refreshes every lock file:

  • yarn projects: lock rebuilt against declared ranges, then audited (yarn has no
    audit fix); a failed refresh restores the previous lock instead of leaving a
    partial one.
  • npm projects: npm audit fix + npm update (both --package-lock-only).
  • python applications: pylock.toml regenerated in the base image, discovered by a
    Dockerfile that actually installs them; base images scanned with the same helpers
    skaffold/codefresh use (guess_build_dependencies_from_dockerfile,
    find_dockerfiles_paths) — downstream-defined images work without configuration.
    Image names resolve through the generated skaffold.yaml
    (cloud-harness/cloudharness-flask), with --registry/--tag overrides.
  • Options: -i/--interactive (confirm per project), --audit-only (report, write
    nothing), --upgrade (raise declared ranges — can cross majors), --registry,
    --tag, --cooldown-days N (default 7; without the flag yarn follows
    .yarnrc.yml).
  • Exits non-zero while vulnerabilities remain, with the remediation split by cause
    (range change / resolutions entry / fix still quarantined).
  • Offers to run corepack enable when the yarn on PATH is too old (only when
    attached to a terminal).

Also fixed on the way

  • test/test-e2e/yarn.lock in an earlier commit was a partial lock (failed
    --immutable, crashed yarn npm audit); repaired, and the tool now can't
    reproduce that state.
  • Runtime pip install fastapi-code-generator in ch_cli_tools/openapi.py now
    carries the cooldown flag.
  • samples/django-ninja/test-e2e Dockerfiles installed JS deps without a frozen
    lockfile; pinned.

Known limitations

  • pylock.toml files are single-platform (amd64 / CPython 3.12 — matching the
    images). pip lock is experimental in pip.
  • The npm path (test/test-e2e, used by harness-test) has no cooldown: npm gained
    min-release-age only in 11.10.0.
  • Editing requirements.txt alone no longer changes an image — regenerate the lock
    and commit both.
  • Developers must run corepack enable once (yarn refuses with that instruction
    until then).
  • Known remaining advisories in test/test-e2e: extract-zip needs puppeteer 25
    (semver-major), glob/inflight sit under EOL tslint/lint-staged tooling.

How to test this PR

Prereqs: conda activate ch, sh install.sh, corepack enable, Docker running.

# 1. Build the base images (needed for python lock generation)
harness-deployment .          # generates skaffold.yaml
skaffold build

# 2. Audit everything without writing
harness-generate dependencies --audit-only

# 3. Full refresh — JS locks re-resolved, npm audit-fixed, 5 pylock.toml
#    regenerated inside cloud-harness/cloudharness-{flask,base}:latest
harness-generate dependencies
git diff            # inspect lock movements

# 4. Interactive / selective
harness-generate dependencies -i

# 5. Cooldown proof: a version published <7 days ago is never picked.
#    e.g. nanoid 3.3.18 (published Aug 7) stays quarantined while 3.3.17 is taken;
#    lowering the window pulls it in:
harness-generate dependencies --cooldown-days 3

# 6. Locked image builds — installs from pylock.toml, no resolution
docker build applications/notifications/server --build-arg CLOUDHARNESS_BASE=cloud-harness/cloudharness-base:latest

# 7. Unit tests (32 for the new module; full suite 124+)
tox -c tools/deployment-cli-tools

# Sanity checks:
- [ ] The pull request is explicitly linked to the relevant issue(s)
- [ ] The issue is well described: clearly states the problem and the general proposed solution(s)
- [ ] In this PR it is explicitly stated how to test the current change
- [ ] The issue seta the scope and the type of issue (bug, story, task, etc.)
- [ ] The relevant components are indicated in the issue (if any)
- [ ] All the automated test checks are passing
- [ ] All the linked issues are included in one Sprint
- [ ] All the linked issues are in the Review state
- [ ] All the linked issues are assigned

# Breaking changes (select one):
- [ ] The present changes do not change the preexisting api in any way
- [ ] This PR and the issue are tagged as a `breaking-change` and the migration procedure is well described [above](#implemented-solution)

# Possible deployment updates issues (select one):
- [ ] There is no reason why deployments based on CloudHarness may break after the current update
- [ ] This PR and the issue are tagged as `alert:deployment`

### Test coverage (select one):
- [ ] Tests for the relevant cases are included in this pr
- [ ] The changes included in this pr are out of the current test coverage scope

### Documentation (select one):
- [ ] The documentation has been updated to match the current changes
- [ ] The changes included in this PR are out of the current documentation scope

### Nice to have (if relevant):
- [ ] Screenshots of the changes
- [ ] Explanatory video/animated gif


[CH-286]: https://metacell.atlassian.net/browse/CH-286?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


# Dependency updates need neither java nor the openapi generator.
if args.update_dependencies:
return update_js_dependencies(root_path, args)
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