build: freeze operating system package inputs
This commit is contained in:
@@ -41,6 +41,9 @@ jobs:
|
||||
npm install --package-lock-only --ignore-scripts --no-audit --no-fund
|
||||
)
|
||||
done
|
||||
python3 scripts/complete_npm_lock_integrity.py \
|
||||
bot_bottle/contrib/claude/package-lock.json \
|
||||
bot_bottle/contrib/pi/package-lock.json
|
||||
|
||||
- name: Refresh pinned Codex installer checksum
|
||||
run: |
|
||||
|
||||
+11
-1
@@ -43,13 +43,23 @@
|
||||
# is pip-installed to the same effect as the upstream image.
|
||||
FROM python:3.12.13-slim-trixie@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de
|
||||
|
||||
# Freeze apt's package universe as well as the base filesystem. Without a
|
||||
# snapshot, the same Dockerfile resolves different package versions over time.
|
||||
ARG DEBIAN_SNAPSHOT=20260724T000000Z
|
||||
RUN sed -i \
|
||||
-e "s|http://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|http://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
/etc/apt/sources.list.d/debian.sources
|
||||
|
||||
# Runtime system deps:
|
||||
# git supplies the `git daemon` subcommand (no separate package)
|
||||
# plus the core `git` binary the pre-receive hook invokes.
|
||||
# openssh-client supplies the upstream SSH transport the
|
||||
# pre-receive hook uses to forward accepted refs.
|
||||
# ca-certificates is needed for mitmdump upstream TLS.
|
||||
RUN apt-get update \
|
||||
RUN apt-get -o Acquire::Check-Valid-Until=false update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
git openssh-client ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -18,7 +18,15 @@
|
||||
ARG ORCHESTRATOR_BASE_IMAGE
|
||||
FROM ${ORCHESTRATOR_BASE_IMAGE}
|
||||
|
||||
RUN apt-get update \
|
||||
ARG DEBIAN_SNAPSHOT=20260724T000000Z
|
||||
RUN sed -i \
|
||||
-e "s|http://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|http://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
/etc/apt/sources.list.d/debian.sources
|
||||
|
||||
RUN apt-get -o Acquire::Check-Valid-Until=false update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
buildah crun netavark aardvark-dns \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
# Version-qualified Node LTS, pinned to its multi-architecture manifest.
|
||||
FROM node:22.23.1-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba
|
||||
|
||||
ARG DEBIAN_SNAPSHOT=20260724T000000Z
|
||||
RUN sed -i \
|
||||
-e "s|http://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|http://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
/etc/apt/sources.list.d/debian.sources
|
||||
|
||||
# Install runtime system deps. claude-code shells out to git for several
|
||||
# features (status checks, commits, PR creation) — without git in the
|
||||
# image, those features fail in surprising ways once the user does any
|
||||
@@ -19,7 +27,7 @@ FROM node:22.23.1-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e8
|
||||
# HTTPS_PROXY-aware tool (curl itself, plus anything that shells out
|
||||
# to it) works against egress's bumped TLS without the agent needing
|
||||
# local DNS.
|
||||
RUN apt-get update \
|
||||
RUN apt-get -o Acquire::Check-Valid-Until=false update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
ca-certificates \
|
||||
@@ -34,7 +42,7 @@ RUN apt-get update \
|
||||
# (claude-code is a Node CLI), but is convenient for the agent to
|
||||
# shell out to for ad-hoc scripts. Kept on its own layer so it can
|
||||
# be moved to a downstream image if the base ever needs to shrink.
|
||||
RUN apt-get update \
|
||||
RUN apt-get -o Acquire::Check-Valid-Until=false update \
|
||||
&& apt-get install -y --no-install-recommends python3 python3-pip python3-venv \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -10,7 +10,15 @@ FROM node:22.23.1-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e8
|
||||
# installer source and checksum.
|
||||
ARG CODEX_VERSION=0.145.0
|
||||
|
||||
RUN apt-get update \
|
||||
ARG DEBIAN_SNAPSHOT=20260724T000000Z
|
||||
RUN sed -i \
|
||||
-e "s|http://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|http://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
/etc/apt/sources.list.d/debian.sources
|
||||
|
||||
RUN apt-get -o Acquire::Check-Valid-Until=false update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
ca-certificates \
|
||||
@@ -24,7 +32,7 @@ RUN apt-get update \
|
||||
# (codex is a Node CLI), but is convenient for the agent to shell
|
||||
# out to for ad-hoc scripts. Kept on its own layer so it can be
|
||||
# moved to a downstream image if the base ever needs to shrink.
|
||||
RUN apt-get update \
|
||||
RUN apt-get -o Acquire::Check-Valid-Until=false update \
|
||||
&& apt-get install -y --no-install-recommends python3 python3-pip python3-venv \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
|
||||
FROM node:22.23.1-trixie-slim@sha256:e6d9a389d34ff9678438af985c9913fbd1eb6ed36e80fea56644f4b4f6dd70ba
|
||||
|
||||
RUN apt-get update \
|
||||
ARG DEBIAN_SNAPSHOT=20260724T000000Z
|
||||
RUN sed -i \
|
||||
-e "s|http://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian-security|https://snapshot.debian.org/archive/debian-security/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|http://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
-e "s|https://deb.debian.org/debian|https://snapshot.debian.org/archive/debian/${DEBIAN_SNAPSHOT}|g" \
|
||||
/etc/apt/sources.list.d/debian.sources
|
||||
|
||||
RUN apt-get -o Acquire::Check-Valid-Until=false update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
ca-certificates \
|
||||
@@ -15,7 +23,7 @@ RUN apt-get update \
|
||||
&& ln -s /usr/bin/fdfind /usr/local/bin/fd \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-get update \
|
||||
RUN apt-get -o Acquire::Check-Valid-Until=false update \
|
||||
&& apt-get install -y --no-install-recommends python3 python3-pip python3-venv \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
Generated
+6
-3
@@ -1056,7 +1056,8 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
}
|
||||
},
|
||||
"integrity": "sha512-yqbh68CyhqxMov/jUogFJfMqlu2Gd37GAki+tr59YCmAPHfomiCA5ESzusXtpGzABeiZFC/OrRdQ4GwCCOMIHA=="
|
||||
},
|
||||
"node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": {
|
||||
"version": "0.81.1",
|
||||
@@ -1080,7 +1081,8 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
}
|
||||
},
|
||||
"integrity": "sha512-hzHE7Z8l5mgJk+ke67Lge0rwS2+wbKJrFKl9o5M1R1rh33+cCT7D1AHz1OAtX5wFs90E1/BTGhyJRTUHaMxGvQ=="
|
||||
},
|
||||
"node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": {
|
||||
"version": "0.81.1",
|
||||
@@ -1092,7 +1094,8 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
}
|
||||
},
|
||||
"integrity": "sha512-OMEe+Zt8oQYi/rCq3upxsTlIScWL0FPhXwQus34TbQb3EmTx88S7Uzx32JxvQiEeWOw8eDCdJf2PBUBE9r6wIg=="
|
||||
},
|
||||
"node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": {
|
||||
"version": "1.52.0",
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Complete duplicate npm lock entries from an identical verified artifact."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def complete_lock(path: Path) -> int:
|
||||
"""Fill missing SRI only from the same resolved URL; return change count."""
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
packages = data.get("packages", {})
|
||||
integrity_by_url: dict[str, str] = {}
|
||||
for package in packages.values():
|
||||
resolved = package.get("resolved")
|
||||
integrity = package.get("integrity")
|
||||
if not isinstance(resolved, str) or not resolved.startswith(
|
||||
("http://", "https://"),
|
||||
):
|
||||
continue
|
||||
if not isinstance(integrity, str) or not integrity.startswith("sha512-"):
|
||||
continue
|
||||
prior = integrity_by_url.setdefault(resolved, integrity)
|
||||
if prior != integrity:
|
||||
raise ValueError(f"conflicting integrity values for {resolved}")
|
||||
|
||||
changed = 0
|
||||
missing: list[str] = []
|
||||
for package_path, package in packages.items():
|
||||
resolved = package.get("resolved")
|
||||
if not isinstance(resolved, str) or not resolved.startswith(
|
||||
("http://", "https://"),
|
||||
):
|
||||
continue
|
||||
integrity = package.get("integrity")
|
||||
if isinstance(integrity, str) and integrity.startswith("sha512-"):
|
||||
continue
|
||||
known = integrity_by_url.get(resolved)
|
||||
if known is None:
|
||||
missing.append(package_path)
|
||||
continue
|
||||
package["integrity"] = known
|
||||
changed += 1
|
||||
if missing:
|
||||
joined = ", ".join(missing)
|
||||
raise ValueError(f"no verified duplicate artifact for: {joined}")
|
||||
if changed:
|
||||
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
return changed
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("locks", nargs="+", type=Path)
|
||||
args = parser.parse_args()
|
||||
for path in args.locks:
|
||||
changed = complete_lock(path)
|
||||
print(f"{path}: completed {changed} duplicate integrity entries")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,66 @@
|
||||
"""Unit tests for npm's duplicate lock-entry integrity completion."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.complete_npm_lock_integrity import complete_lock
|
||||
|
||||
|
||||
class TestCompleteLock(unittest.TestCase):
|
||||
def _lock(self, directory: str, packages: dict[str, dict[str, str]]) -> Path:
|
||||
path = Path(directory) / "package-lock.json"
|
||||
path.write_text(json.dumps({
|
||||
"lockfileVersion": 3,
|
||||
"packages": packages,
|
||||
}))
|
||||
return path
|
||||
|
||||
def test_copies_integrity_only_for_identical_resolved_url(self):
|
||||
url = "https://registry.npmjs.org/example/-/example-1.2.3.tgz"
|
||||
integrity = "sha512-trusted"
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = self._lock(directory, {
|
||||
"node_modules/example": {
|
||||
"resolved": url,
|
||||
"integrity": integrity,
|
||||
},
|
||||
"node_modules/parent/node_modules/example": {
|
||||
"resolved": url,
|
||||
},
|
||||
})
|
||||
self.assertEqual(1, complete_lock(path))
|
||||
packages = json.loads(path.read_text())["packages"]
|
||||
self.assertEqual(
|
||||
integrity,
|
||||
packages["node_modules/parent/node_modules/example"]["integrity"],
|
||||
)
|
||||
|
||||
def test_rejects_missing_integrity_without_verified_duplicate(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = self._lock(directory, {
|
||||
"node_modules/example": {
|
||||
"resolved": (
|
||||
"https://registry.npmjs.org/example/-/example-1.2.3.tgz"
|
||||
),
|
||||
},
|
||||
})
|
||||
with self.assertRaisesRegex(ValueError, "no verified duplicate"):
|
||||
complete_lock(path)
|
||||
|
||||
def test_rejects_conflicting_integrities_for_same_url(self):
|
||||
url = "https://registry.npmjs.org/example/-/example-1.2.3.tgz"
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = self._lock(directory, {
|
||||
"node_modules/a": {"resolved": url, "integrity": "sha512-a"},
|
||||
"node_modules/b": {"resolved": url, "integrity": "sha512-b"},
|
||||
})
|
||||
with self.assertRaisesRegex(ValueError, "conflicting integrity"):
|
||||
complete_lock(path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user