From bda9381f070859ae997474cf44b86bf4eea9bb52 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 11 Sep 2026 01:21:37 +0200 Subject: [PATCH] Package protected review runtime and prepare deployment admission Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc --- .dockerignore | 5 + .gitignore | 2 + Containerfile | 24 + Makefile | 10 +- SCOPE.md | 16 +- deploy/README.md | 184 ++ deploy/admission.example.json | 12 + .../2026-09-11-container-candidate.json | 143 + docs/evidence/2026-09-11-container-scan.json | 2321 +++++++++++++++++ docs/evidence/2026-09-11-container-smoke.json | 30 + docs/protected-browser-review.md | 4 +- informed_decision/container.py | 114 + informed_decision/web.py | 5 +- pyproject.toml | 2 + requirements.lock | 164 ++ tests/test_container_runtime.py | 103 + tests/test_deployment_candidate.py | 77 + tools/render_deployment.py | 131 + tools/smoke_container.py | 150 ++ ...ounding-specs-and-approver-ui-ownership.md | 44 + 20 files changed, 3534 insertions(+), 7 deletions(-) create mode 100644 .dockerignore create mode 100644 Containerfile create mode 100644 deploy/README.md create mode 100644 deploy/admission.example.json create mode 100644 docs/evidence/2026-09-11-container-candidate.json create mode 100644 docs/evidence/2026-09-11-container-scan.json create mode 100644 docs/evidence/2026-09-11-container-smoke.json create mode 100644 informed_decision/container.py create mode 100644 requirements.lock create mode 100644 tests/test_container_runtime.py create mode 100644 tests/test_deployment_candidate.py create mode 100644 tools/render_deployment.py create mode 100644 tools/smoke_container.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..114b831 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +** +!Containerfile +!requirements.lock +!dist/ +!dist/informed_decision-*.whl diff --git a/.gitignore b/.gitignore index 8d86d4b..f397a6b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ __pycache__/ .pytest_cache/ *.egg-info/ .venv/ +dist/ +build/ diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..caa95c8 --- /dev/null +++ b/Containerfile @@ -0,0 +1,24 @@ +# Base matches the approved Approval Engine Alpine family; scan each candidate. +FROM python:3.12-alpine@sha256:b64631e04e4920160c50fbe8d8df828f7f35f06f425cb44aa09bca53e708a35a AS build +ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PATH=/opt/venv/bin:$PATH +RUN python -m venv /opt/venv +COPY requirements.lock /build/requirements.lock +RUN pip install --no-cache-dir --require-hashes --only-binary=:all: -r /build/requirements.lock +COPY dist/informed_decision-*.whl /build/ +RUN pip install --no-cache-dir --no-deps --no-index /build/informed_decision-*.whl + +FROM python:3.12-alpine@sha256:b64631e04e4920160c50fbe8d8df828f7f35f06f425cb44aa09bca53e708a35a +ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PATH=/opt/venv/bin:$PATH +RUN apk add --no-cache 'libuuid>=2.42.3-r1' \ + && addgroup -S -g 10001 informed \ + && adduser -S -u 10001 -G informed -H informed +COPY --from=build /opt/venv /opt/venv +# No package installer or build tooling belongs in the running service. +RUN rm -rf /usr/local/bin/pip* /usr/local/lib/python3.12/site-packages/pip* \ + /usr/local/lib/python3.12/site-packages/setuptools* /usr/local/lib/python3.12/site-packages/pkg_resources \ + /opt/venv/bin/pip* /opt/venv/lib/python3.12/site-packages/pip* \ + /opt/venv/lib/python3.12/site-packages/setuptools* /opt/venv/lib/python3.12/site-packages/pkg_resources +WORKDIR /app +USER 10001:10001 +EXPOSE 8080 +ENTRYPOINT ["informed-decision-container"] diff --git a/Makefile b/Makefile index ff39f4c..45416f6 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,12 @@ -.PHONY: test check sync +.PHONY: test check sync package image-build + +IMAGE ?= informed-decision:local + +package: + uv build --wheel + +image-build: package + docker build -f Containerfile -t $(IMAGE) . test: python3 -m pytest -q diff --git a/SCOPE.md b/SCOPE.md index c9f3b77..10a698a 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -10,8 +10,9 @@ durable evidence store and scheduled Audit Core delivery are implemented. The approval surface is not deployed.** -What exists and is tested (344 tests and 12 Chromium checks, including actual -Flex Auth, Approval Engine and Audit Core with synthetic identity/custody): +What exists and is tested (371 automated tests and 11 container checks; the +previous browser milestone passed 12 Chromium checks). Component tests use +actual Flex Auth, Approval Engine and Audit Core with synthetic identity/custody: - layer and stance declarations — `layer.yaml`, `pep-stance.yaml`, `informed_decision/stance.py`, with published-equals-shipped asserted; @@ -39,10 +40,15 @@ Flex Auth, Approval Engine and Audit Core with synthetic identity/custody): accept/return/discuss/decline and visible original/unresolved entry state; - `runtime.py` — explicit owner configuration, rotating credential-file readers, 30-second audit draining, heartbeat/reconciliation and delivery readiness. +- `Containerfile`, `container.py`, `requirements.lock` and deployment renderer — + a locally built/scanned image, private projected-config handoff, one serving + writer, backup/inspection commands and eight review-only Kubernetes objects. + Network-isolated container restart/restore preserves unresolved submissions. Remaining: native policy package/caller/assignment admission, registered human -login and deployed binding, independent production audit custody, packaging, -backup/restore and operator recovery admission. The legacy `evidence.Outbox` remains an +login and deployed binding, independent production audit custody, image +publication/cutover, platform backup/restore and operator recovery admission. +The legacy `evidence.Outbox` remains an in-memory test double; the new `Store` supplies durable atomicity. Browser sessions are ephemeral, with no approval state. Without owner runtime configuration `/readyz` returns 503. With it, readiness @@ -51,6 +57,8 @@ The origin's last observed deployment was an nginx placeholder on 2026-09-10. See [browser-authentication.md](docs/browser-authentication.md) and [durable-review-evidence.md](docs/durable-review-evidence.md) and [protected-browser-review.md](docs/protected-browser-review.md). +The [deployment packet](deploy/README.md) records native wiring observations, +the missing Approval Engine namespace and remaining owner admission. `INFD-WP-0001-T08` remains open for the live end-to-end proof, which is gated on `APPROVAL-WP-0002-T01` and a deployed `approval-engine`. diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..405b070 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,184 @@ +# Review service deployment candidate + +`INFD-WP-0001-T08` owns this packet. It prepares the existing +`decisions.coulomb.social` origin for the review service. **No image publication, +production configuration, credential provisioning or cluster apply is performed +by the build, renderer or tests.** The origin is still the Railiance Apps nginx +placeholder. Native policy, custody, registration and service admission remain +required before cutover. + +## Artifact and local proof + +```sh +make image-build IMAGE=informed-decision:local +python tools/smoke_container.py --image informed-decision:local --receipt /tmp/infd-container-proof.json +``` + +`Containerfile` uses the digest-pinned Alpine base already used by Approval +Engine, installs the hashed `requirements.lock`, then installs the built wheel +without dependency resolution. The image contains no pip or build installer. +Its user/group is 10001; runtime deployments must use a read-only root filesystem +and drop capabilities. Rebuild the wheel before building the image. Refresh +dependency pins deliberately with `uv pip compile pyproject.toml --python-version +3.12 --generate-hashes --output-file requirements.lock`, then repeat checks and +the image scan. A successful local build is not a published registry digest. + +The dated evidence pins the exact wheel/image/runtime-file hashes and scanner. +The local image passed 11 container checks and a HIGH/CRITICAL Trivy scan with +zero findings. The scanner also emitted an Alpine lifecycle-list warning; the +receipt retains it. This scan is evidence about the selected severities and +database at that time, not a general security attestation. + +The smoke harness creates and removes its own labeled containers/volumes. It +uses `--network none`, publishes no port and mounts only synthetic configuration +and credentials. It tests the installed entrypoint, private file ownership, +anonymous refusal, unhealthy audit readiness, exclusion of a second writer, +consistent backup, restart, and restore onto another volume. A synthetic +unresolved submission and its undelivered evidence survive unchanged. No native +issuer, PDP, Approval Engine or Audit Core is contacted. Actual component/browser +contracts have separate receipts from the previous integration milestone. + +The lifecycle check caught the initial PID-1 process requiring Docker's forced +kill (exit 137). The container now handles SIGTERM through Waitress shutdown and +the audit pump's stop path; the final image exits cleanly within the tested +15-second window. Kubernetes allows 45 seconds. Forced termination still cannot +justify retrying an unresolved approval entry. + +## Private storage and projected configuration + +The deployment uses a single replica with **Recreate**, a ReadWriteOnce PVC and +an OS lock held for the life of the serving process. Do not add replicas, a +rolling surge or another writer: sessions are process-local and this store is a +local-filesystem design. The lock rejects accidental concurrent service starts; +it is not an admission of network-filesystem locking semantics. + +Kubernetes projected ConfigMaps are root-owned and symlinked. The container +entrypoint reads at most 16 KiB from `/configuration/runtime.json`, requires +`evidence_db=/data/private/review.sqlite`, and copies the configuration into +owned ephemeral storage at `/run/informed-decision/private/runtime.json` (0600). +It creates `/data/private` as the process user with mode 0700 and refuses unsafe +existing ownership/modes. It does not silently chmod or take over existing data. +The evidence database remains 0600. Credential callbacks read rotating projected +files directly; bearer tokens are never copied into SQLite or the config snapshot. + +The pod uses fsGroup 10001 with `OnRootMismatch`. Its storage driver must make +the volume root writable by that group while preserving the private child's +ownership/modes on later mounts. A restored volume whose root triggers a +recursive permission rewrite needs owner repair before admission, not relaxed +store checks. The local-path class exists on Railiance; its backup/retention and +restore admission are still the platform owner's work. + +The normal workstation entrypoint continues listening on 127.0.0.1. The container +entrypoint explicitly selects 0.0.0.0; the public issuer/callback remain fixed, +and Host/forwarded headers cannot alter them. + +## Render the review packet + +Copy `admission.example.json` outside Git and replace its intentionally invalid +placeholders with the published image manifest digest, admitted PDP endpoint and +package/version/digest, exact PDP pod label, observed/admitted issuer IPs and +storage class. The renderer accepts no inline token or broad egress CIDR. + +```sh +python tools/render_deployment.py --input /operator/review-inputs.json --output /tmp/infd-candidate.json +kubectl --kubeconfig /operator/railiance-kubeconfig apply --dry-run=server -f /tmp/infd-candidate.json +``` + +Rendering and server dry-run do not admit the values supplied. The renderer +produces eight review objects: service account, PVC, immutable configuration, +Recreate Deployment, Service, the review pod's NetworkPolicy, and two exact +counterparty ingress proposals. It creates no Secret, RBAC grant, Namespace, +Ingress or policy assignment. The service retains the existing name/port and +selects the review component. The Deployment keeps the existing immutable +selector so the same origin can be replaced under a reviewed cutover. + +The incoming Traefik peer is namespace AND pod label. Egress permits DNS, +exact Approval/Audit/PDP peers, exact public KeyCape addresses on 443, and the +observed Traefik websecure peer on 8443 for enforcement after Service DNAT. +Railiance metadata on 2026-09-11 confirmed the Traefik label/8443 port and +`kc.coulomb.social -> 92.205.62.239`. These observations do not prove the CNI +path; test the actual issuer token/JWKS reachability after admission. The shared +HTTPS router is an L3/L4 boundary, not a hostname authorization policy; the +client itself pins the issuer and refuses redirects. + +Approval Engine's existing caller policy permits Secrets Engine or namespaces +carrying its client label. That does not admit this review pod. The packet adds +an **owner-reviewed proposal** ANDing namespace, app label and component rather +than labelling the whole namespace as an approved caller. Flex Auth receives a +similarly exact peer proposal. Audit Core already owns its prepared Informed +Decision ingress under `AUDIT-WP-0009-T11`; do not create a competing copy. + +Seven of eight objects passed server dry-run in their actual namespaces. The +Approval Engine peer failed because namespace `approval-engine` is absent. +The identical policy shape passed with only its metadata namespace/name mapped +to an existing namespace for schema validation. That does not close the missing +owner deployment. Schema checks used fixture policy pins and an unpublished image +reference; their rendered output is not an admitted production configuration. + +## Serving health and acceptance readiness + +Startup, Kubernetes readiness and liveness use `/healthz`. An audit dependency +outage must not restart the writer or remove the service's refusal/recovery +pages from the origin. `/readyz` separately reports recent audit delivery health, +and the application refuses accept while it is unhealthy. Monitor `/readyz` +and pending/blocked outbox counts explicitly; Kubernetes `Ready=True` alone is +not deployment acceptance or native policy proof. A healthy process can still +refuse every action when policy/custody has not been admitted. + +## Native admission and cutover + +Use the existing owners and records: + +1. Flex Auth/Informed Decision admit the exact request package, assignments and + caller `informed-decision=system:serviceaccount:informed-decision:review`. + The projected token's audience is `flex-auth`; no browser token substitutes. + The service account spelling is a candidate until this return is recorded. +2. Platform/Audit Core complete `AUDIT-WP-0009-T11`: the candidate expects + `informed-decision-audit` with key `token`, projected read-only. Its sender is + exact `informed-decision`, tenant `tenant:platform`, write-only, load-bearing, + redact. Run `warden route find/show` before any credential request. Rendering + the Secret reference provisions nothing and confirms no custody name. +3. Approval Engine admits its namespace/service and the exact review caller; + KeyCape rolls out the already published browser registration. Railiance Apps + admits image publication/cutover against its existing origin. Complete + private-state backup/restore, monitoring and rollback ownership. +4. Capture the current Deployment and Service configuration, close new review + traffic for cutover, and retain any existing source-held evidence. Only then + apply the admitted packet. Its Recreate rollout intentionally incurs downtime. +5. Check both process health and application readiness, policy caller/subject + allow/refusal cases, genuine human login and a declared human-control approval. + Retrieve the independent audit receipt and the original presentation. Recheck + the callback's TLS/redirect/cookie behavior and source custody after restart. + +The image publication, workload/policy/custody admission and rollout remain +distinct. No signed decision attribution, native approval or factory admission +is inferred from this packet. `FLEX-WP-0024` and the commitment-only custody +limitations remain as recorded in the consumer contract. + +## Backup, restore and rollback + +The installed `informed-decision-admin` supports local custody operations only: + +```sh +informed-decision-admin inspect --db /data/private/review.sqlite +informed-decision-admin backup --db /data/private/review.sqlite --output /backup/private/review.sqlite +``` + +The backup parent must already be owned/private 0700; the destination must not +exist. Backup uses SQLite's consistent backup API while the service may be live. +Do not copy a running SQLite file while ignoring WAL. Inspection prints counts +and schema version, not memo content or credentials. These are privileged +operator custody commands, not browser endpoints. An independent backup copy, +retention, access controls and an actual platform restoration drill remain owed. + +For restore, stop the serving writer, restore to a separately prepared private +volume, verify the immutable content and unresolved submission state, then +mount that volume as `/data`. A restart requires a new human login. Never reset +an unresolved intent or POST again to manufacture a presentation correlation. +Do not roll schema v2 back to an older reader against the same live database. + +For a failed initial cutover, stop the new writer, retain the PVC and restore +Railiance Apps' **Deployment and Service** from the prior origin manifest. +Restoring only the Deployment is insufficient: the candidate Service selector +also names the review component. Returning to the placeholder is a service +rollback, not deletion or reconciliation of any approval/evidence already held. diff --git a/deploy/admission.example.json b/deploy/admission.example.json new file mode 100644 index 0000000..9fdcf65 --- /dev/null +++ b/deploy/admission.example.json @@ -0,0 +1,12 @@ +{ + "image": "REQUIRED: forgejo.coulomb.social/coulomb/informed-decision@sha256:", + "policy": { + "origin": "REQUIRED: http://.flex-auth.svc.cluster.local:8080", + "package": "REQUIRED: owner-admitted package", + "version": "REQUIRED: owner-admitted version", + "package_digest": "REQUIRED: sha256:", + "pod_name": "REQUIRED: exact app.kubernetes.io/name label of admitted PDP" + }, + "keycape_egress_ips": ["REQUIRED: observed and admitted public issuer IPv4/IPv6 address"], + "storage_class": "REQUIRED: admitted local-filesystem ReadWriteOnce storage class" +} diff --git a/docs/evidence/2026-09-11-container-candidate.json b/docs/evidence/2026-09-11-container-candidate.json new file mode 100644 index 0000000..9a4fb5f --- /dev/null +++ b/docs/evidence/2026-09-11-container-candidate.json @@ -0,0 +1,143 @@ +{ + "schema": "informed-decision.container-candidate.v1", + "observed_at": "2026-09-10T23:15:35.252989+00:00", + "base_commit": "83849b75d44cab3d01d7ea56f6c839aef8e20046", + "contract_sources": { + "approval-engine": "a0a602976eef818f36dde35f76f7f2e589bd051b", + "audit-core": "5c0ad522fb36092aa7ec2e8d72f63a5a91853b5b", + "flex-auth": "88b354377c8e26b162f1234e673072f1c06dcd89", + "railiance-apps": "eff457ef52a29afc7b9beccf38480a9955c5233e" + }, + "source_task": "INFD-WP-0001-T08", + "status": "progress", + "image": { + "local_tag": "informed-decision:hfact-20260911", + "local_image_id": "sha256:c882d6c10ce61b29eed06594a0eb01abe5b1adb136a0343101f991c2305701b4", + "wheel_sha256": "00930d4fe7415c93f36ee82735abcbc69ca7cad694e2963b8f0ad8abdac6cb34", + "runtime_files": { + "container.py": "a8e7516ef6a7f110d8ed95493ae3b9492d913cc7a5650b88a519334ba544f9dd", + "presentation.py": "54375ccd8358fac41de220bccadbee09f8b7c8ebbeeafaafd381a7456331da50", + "disposition.py": "5ddd19c2444f8e6c9bb090d04045cf1227a34554e000f07b3f677325bf8089f8", + "records.py": "82f696151895c51b168144a3078d05764da3499b96917c465629ce0836fa8e33", + "audit.py": "06da36a8f3451b4abc7a46da2df66d5939524c170658b879499b2a47a1b10cd3", + "oidc.py": "35b37bae67968c2179b22af8151afe2981d5468debf4488771eaf3121b75e221", + "__main__.py": "3ba1467fa2d47fcf0bd032e8694904450775adf763a6e6f9e4888cc3ce3ed92b", + "stance.py": "62b96b76eba4ec277344172f10f6233e979dda4d079f972d9a55c4e5aed96c3c", + "policy.py": "7bf814a8332ff054bf5f1f651d8816a408138b1f197b17da0abb7f87411e8812", + "review.py": "c2d2aa1cf48d10d85b3606acf2830ab22701c1274221e5e2760d29901adfd786", + "http_transport.py": "fc9c1fd05cc89120d70bfcdb4c60fe244b81c8054d21505df8cfa9c5a229f7bc", + "approval_client.py": "56c201dda358cb4bef11a15da141bb68562cdde9e37b4a880bf1e9257ae82cb6", + "runtime.py": "6bc6a74657f526743cca6c6cdc0a86fff43d8282e0b867dc9288344636abe5eb", + "provenance.py": "de02c6bf162865ba9619d74cfd68dd0a5d7f81e6c9e427f67f848cb2c2214f83", + "memo.py": "697058863a4e6de2c143a2433eaaf5e15e5e7375dc5459af6911b146567e8b5d", + "canonicalize.py": "517bbc6b81cc839636b97863ebdefd38aa375d10f4915feb9bf21867f992f765", + "evidence.py": "5f84997f66409c7b6cec6ab57e73ad5bba2c1b2d669604adcbf36e72a824a88d", + "__init__.py": "0ffebe048ba583e7b623f60e8f449950dc5fd49afce2398381fa143eee834e19", + "approval_http.py": "203f462ff78f1409f2f924d4723197fdd502087c7f6da319f46ffffce2a9d478", + "web.py": "97f7e040237fa3b755e39d13e628181dc2a3f6a31b4331ca7284bef05f8d6038", + "store.py": "7a58b36d409cb4161b65985545e13573a5196779b4c6e95ca03ce45c6fada808", + "ui.py": "69fb7fc1794c8842f42e7f0f5a261c4cf64176053ef0a532db46ee6566621219" + }, + "installed_versions": { + "informed-decision": "0.2.0", + "PyJWT": "2.13.0", + "cryptography": "50.0.1", + "cffi": "2.1.1", + "pycparser": "3.0", + "waitress": "3.0.2" + }, + "requirements_lock_sha256": "539072cb7def057c170c065363d365fe7fb7cf540bef04b2a4c9fab8ee05a7db", + "all_22_runtime_files_match_source": true, + "published": false + }, + "verification": { + "command": "INFD_APPROVAL_ENGINE_SOURCE=/home/worsch/approval-engine INFD_AUDIT_CORE_SOURCE=/home/worsch/audit-core INFD_FLEX_AUTH_BINARY= make check", + "tests_passed": 371, + "tests_failed": 0, + "tests_skipped": 0, + "new_tests": 27, + "schema_json_valid": true, + "container_checks_passed": 11, + "container_receipt": "2026-09-11-container-smoke.json", + "container_network": "none", + "fixture_cleanup_complete": true, + "prior_browser_checks": { + "owner_commit": "83849b75d44cab3d01d7ea56f6c839aef8e20046", + "passed": 12, + "rerun_in_this_increment": false + } + }, + "scan": { + "scanner": "aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969", + "created_at": "2026-09-10T23:13:55.674183643Z", + "high": 0, + "critical": 0, + "exit_code": 0, + "report": "2026-09-11-container-scan.json", + "warning": "Alpine 3.24 is not on the scanner lifecycle/EOL list; no lifecycle acceptance is inferred." + }, + "cluster_schema_check": { + "apply_mode": "dry-run=server", + "candidate_objects": 8, + "exact_namespace_objects_passed": 7, + "refusal": "approval-engine namespace absent; peer ingress cannot be created there yet", + "representative_namespace_schema_check_passed": true, + "representative_change": "metadata namespace/name only for the missing peer rule", + "fixture_policy_pins": true, + "unpublished_image_reference": true, + "cluster_changes_applied": 0, + "schema_only_image_reference": "unpublished pre-shutdown-fix local image 3d2b62f; image bytes are not evaluated by API schema validation" + }, + "native_metadata_observations": { + "traefik_namespace": "kube-system", + "traefik_app_label": "traefik", + "websecure_container_port": 8443, + "keycape_public_addresses": [ + "92.205.62.239" + ], + "storage_class": "local-path", + "storage_provisioner": "rancher.io/local-path" + }, + "source_sha256": { + ".dockerignore": "fa81d3c056779e8fd5fea58022e42cf9704b16e4d24d75896a2ef2f413f426e8", + ".gitignore": "bde5cbd5d9797b283d92cd9c69b12981a2539f4b7fb6d191ab63dd015c4a2cc3", + "Containerfile": "a598563e9b2b4a80fd3c3ac580cf1e63ec44466a893d0068566036487aed20d2", + "Makefile": "15ffb7f2621dacd6145609119994193cd0c7962f01b4e869d4a415200f55e9fa", + "SCOPE.md": "d7e59d6906326846e4bc9ca8300215596fd316b7af9abce55dad67b229b1aa8d", + "deploy/README.md": "5705c1c317834b71087692b2209bbe33b447c5f4ffcbc7b2434cc0b1f87a165b", + "deploy/admission.example.json": "09de47289135c415927d524edf6c20f256a6f10fb467ceebccf08f221e64dfdb", + "docs/evidence/2026-09-11-container-scan.json": "e926951ef4f085542116b8bb9c68c9717688957ad2913b0c94fd141c11f4dd03", + "docs/evidence/2026-09-11-container-smoke.json": "6a71afa39cfd58b1502e300d8a9176905006b0d6bcd4f4989e200dcf371eeb38", + "docs/protected-browser-review.md": "6c9b1323e0eb28553033a9d7c3e5e0f8c2c02350f13ea5e5da1395499baf81c9", + "informed_decision/container.py": "a8e7516ef6a7f110d8ed95493ae3b9492d913cc7a5650b88a519334ba544f9dd", + "informed_decision/web.py": "97f7e040237fa3b755e39d13e628181dc2a3f6a31b4331ca7284bef05f8d6038", + "pyproject.toml": "5ce28585861413c0835699c5e14ac0ae80185fdf342dc1c765f1d9a0d1b6e211", + "requirements.lock": "539072cb7def057c170c065363d365fe7fb7cf540bef04b2a4c9fab8ee05a7db", + "tests/test_container_runtime.py": "475bf316bd9a1a014ccdbdb3267f40f42678d89849dc2e29d8345c27d1fc68e3", + "tests/test_deployment_candidate.py": "296bc1090db5e33e025ccb53d780c0523ab6368278d8cf1778f7df91af13fe50", + "tools/render_deployment.py": "51ea03afeb6c8986dfeb05bd76f4aa7fa8a22e8c6c1e522cc92dcaa6e348e377", + "tools/smoke_container.py": "1385986cbef1d673e7ea3ed57c8d50b5ca34520a298eaf70e1a9af988b210644", + "workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md": "4cc4a953a8e17adacc9d8fc19f1fd2cab441e8e8b47df07d533c2bf2732f25b4" + }, + "remaining": [ + "native policy package/caller/assignment and exact peer admission", + "AUDIT-WP-0009-T11 custody and protected receiver registration", + "image publication and approved Railiance Apps cutover", + "Approval Engine namespace/service admission and registered human binding", + "native CNI/readiness/audit receipt proof and platform backup/restore operating admission", + "existing T08 broader product acceptance" + ], + "native_secret_reads": 0, + "cluster_apply": false, + "factory_attempts": 0, + "paid_model_calls": 0, + "lifecycle_finding": { + "initial_local_image": "sha256:3d2b62f6ce8914b04c4d944d30996331b3c80028a3763da53055cb4b8d71a7c3", + "initial_exit_code": 137, + "corrected_exit_code": 0, + "graceful_stop_window_seconds": 15, + "correction": "explicit PID-1 SIGTERM handler through Waitress and audit-pump shutdown" + }, + "integrated_parent_commit": "bb5b607bbd3ae518f3dc101dc6402753564b84ed", + "concurrent_owner_change_preserved": "T07 premise correction; no runtime code changed" +} diff --git a/docs/evidence/2026-09-11-container-scan.json b/docs/evidence/2026-09-11-container-scan.json new file mode 100644 index 0000000..090180f --- /dev/null +++ b/docs/evidence/2026-09-11-container-scan.json @@ -0,0 +1,2321 @@ +{ + "SchemaVersion": 2, + "Trivy": { + "Version": "0.74.0" + }, + "ReportID": "01a08d99-65da-724c-9a2a-6f2aa22b3f6c", + "CreatedAt": "2026-09-10T23:13:55.674183643Z", + "ArtifactID": "sha256:057e1f81b72672b827af2ab07397863ac26e86b3f1fd1f1717ab8911bebf31ff", + "ArtifactName": "informed-decision:hfact-20260911", + "ArtifactType": "container_image", + "Metadata": { + "Size": 87843840, + "OS": { + "Family": "alpine", + "Name": "3.24.1" + }, + "ImageID": "sha256:c882d6c10ce61b29eed06594a0eb01abe5b1adb136a0343101f991c2305701b4", + "DiffIDs": [ + "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c", + "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd", + "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366", + "sha256:4098ad033525263f92a8a9c51865596025d47018e078c0b1f7d9cd5663fc1ffe", + "sha256:f9b4a8a412a0c07a489e46fb5db41f9d0dc84d4ec38a4e2c6ab9531f44e0d9f1", + "sha256:1d017566be3551b67af47235af9dd21b673f3ec7896894a1c4532cd379ef935a", + "sha256:80ed5cd7716e7b4ca3693e37d5017e40275435f138828717f3e59b9f5ca49700", + "sha256:3a389ca4c255083adbdf016f4dac3ac56079b178a7794b1dbbfe10fcc852bea9" + ], + "RepoTags": [ + "informed-decision:hfact-20260911" + ], + "RepoDigests": [ + "informed-decision@sha256:c882d6c10ce61b29eed06594a0eb01abe5b1adb136a0343101f991c2305701b4" + ], + "Reference": "informed-decision:hfact-20260911", + "ImageConfig": { + "architecture": "amd64", + "created": "2026-09-11T01:12:52.280443812+02:00", + "history": [ + { + "created": "2026-06-16T00:01:29Z", + "created_by": "ADD alpine-minirootfs-3.24.1-x86_64.tar.gz / # buildkit", + "comment": "buildkit.dockerfile.v0" + }, + { + "created": "2026-06-16T00:01:29Z", + "created_by": "CMD [\"/bin/sh\"]", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-01T00:05:24Z", + "created_by": "ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-01T00:05:24Z", + "created_by": "ENV LANG=C.UTF-8", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-01T00:05:24Z", + "created_by": "RUN /bin/sh -c set -eux; \tapk add --no-cache \t\tca-certificates \t\ttzdata \t; # buildkit", + "comment": "buildkit.dockerfile.v0" + }, + { + "created": "2026-09-01T00:05:24Z", + "created_by": "ENV GPG_KEY=7169605F62C751356D054A26A821E680E5FA6305", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-01T00:05:24Z", + "created_by": "ENV PYTHON_VERSION=3.12.14", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-01T00:05:24Z", + "created_by": "ENV PYTHON_SHA256=5c8462af5790baf43a321a1559dbe0db06d1be4300fb85fb53c40060668e548a", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-01T00:10:30Z", + "created_by": "RUN /bin/sh -c set -eux; \t\tapk add --no-cache --virtual .build-deps \t\tbluez-dev \t\tbzip2-dev \t\tdpkg-dev dpkg \t\tfindutils \t\tg++ \t\tgcc \t\tgdbm-dev \t\tgnupg \t\tlibc-dev \t\tlibffi-dev \t\tlibnsl-dev \t\tlibtirpc-dev \t\tlinux-headers \t\tmake \t\tncurses-dev \t\topenssl-dev \t\tpax-utils \t\treadline-dev \t\tsqlite-dev \t\ttar \t\ttcl-dev \t\ttk \t\ttk-dev \t\tutil-linux-dev \t\txz \t\txz-dev \t\tzlib-dev \t; \t\twget -O python.tar.xz \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz\"; \techo \"$PYTHON_SHA256 *python.tar.xz\" | sha256sum -c -; \twget -O python.tar.xz.asc \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc\"; \tGNUPGHOME=\"$(mktemp -d)\"; export GNUPGHOME; \tgpg --batch --keyserver hkps://keys.openpgp.org --recv-keys \"$GPG_KEY\"; \tgpg --batch --verify python.tar.xz.asc python.tar.xz; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\" python.tar.xz.asc; \tmkdir -p /usr/src/python; \ttar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; \trm python.tar.xz; \t\tcd /usr/src/python; \tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \t./configure \t\t--build=\"$gnuArch\" \t\t--enable-loadable-sqlite-extensions \t\t--enable-option-checking=fatal \t\t--enable-shared \t\t$(test \"${gnuArch%%-*}\" != 'riscv64' \u0026\u0026 echo '--with-lto') \t\t--with-ensurepip \t; \tnproc=\"$(nproc)\"; \tEXTRA_CFLAGS=\"-DTHREAD_STACK_SIZE=0x100000\"; \tLDFLAGS=\"${LDFLAGS:-} -Wl,--strip-all\"; \tarch=\"$(apk --print-arch)\"; \tcase \"$arch\" in \t\tx86_64|aarch64) \t\t\tEXTRA_CFLAGS=\"${EXTRA_CFLAGS:-} -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer\"; \t\t\t;; \t\tx86) \t\t\t;; \t\t*) \t\t\tEXTRA_CFLAGS=\"${EXTRA_CFLAGS:-} -fno-omit-frame-pointer\"; \t\t\t;; \tesac; \tmake -j \"$nproc\" \t\t\"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \t\t\"LDFLAGS=${LDFLAGS:-}\" \t; \trm python; \tmake -j \"$nproc\" \t\t\"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \t\t\"LDFLAGS=${LDFLAGS:-} -Wl,-rpath='\\$\\$ORIGIN/../lib'\" \t\tpython \t; \tmake install; \t\tcd /; \trm -rf /usr/src/python; \t\tfind /usr/local -depth \t\t\\( \t\t\t\\( -type d -a \\( -name test -o -name tests -o -name idle_test \\) \\) \t\t\t-o \\( -type f -a \\( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \\) \\) \t\t\\) -exec rm -rf '{}' + \t; \t\tfind /usr/local -type f -executable -not \\( -name '*tkinter*' \\) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \t\t| tr ',' '\\n' \t\t| sort -u \t\t| awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' \t\t| xargs -rt apk add --no-network --virtual .python-rundeps \t; \tapk del --no-network .build-deps; \t\texport PYTHONDONTWRITEBYTECODE=1; \tpython3 --version; \tpip3 --version # buildkit", + "comment": "buildkit.dockerfile.v0" + }, + { + "created": "2026-09-01T00:10:30Z", + "created_by": "RUN /bin/sh -c set -eux; \tfor src in idle3 pip3 pydoc3 python3 python3-config; do \t\tdst=\"$(echo \"$src\" | tr -d 3)\"; \t\t[ -s \"/usr/local/bin/$src\" ]; \t\t[ ! -e \"/usr/local/bin/$dst\" ]; \t\tln -svT \"$src\" \"/usr/local/bin/$dst\"; \tdone # buildkit", + "comment": "buildkit.dockerfile.v0" + }, + { + "created": "2026-09-01T00:10:30Z", + "created_by": "CMD [\"python3\"]", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-10T22:49:47Z", + "created_by": "ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PATH=/opt/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-10T22:49:47Z", + "created_by": "RUN /bin/sh -c apk add --no-cache 'libuuid\u003e=2.42.3-r1' \u0026\u0026 addgroup -S -g 10001 informed \u0026\u0026 adduser -S -u 10001 -G informed -H informed # buildkit", + "comment": "buildkit.dockerfile.v0" + }, + { + "created": "2026-09-10T23:12:51Z", + "created_by": "COPY /opt/venv /opt/venv # buildkit", + "comment": "buildkit.dockerfile.v0" + }, + { + "created": "2026-09-10T23:12:52Z", + "created_by": "RUN /bin/sh -c rm -rf /usr/local/bin/pip* /usr/local/lib/python3.12/site-packages/pip* /usr/local/lib/python3.12/site-packages/setuptools* /usr/local/lib/python3.12/site-packages/pkg_resources /opt/venv/bin/pip* /opt/venv/lib/python3.12/site-packages/pip* /opt/venv/lib/python3.12/site-packages/setuptools* /opt/venv/lib/python3.12/site-packages/pkg_resources # buildkit", + "comment": "buildkit.dockerfile.v0" + }, + { + "created": "2026-09-10T23:12:52Z", + "created_by": "WORKDIR /app", + "comment": "buildkit.dockerfile.v0" + }, + { + "created": "2026-09-10T23:12:52Z", + "created_by": "USER 10001:10001", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-10T23:12:52Z", + "created_by": "EXPOSE [8080/tcp]", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + }, + { + "created": "2026-09-10T23:12:52Z", + "created_by": "ENTRYPOINT [\"informed-decision-container\"]", + "comment": "buildkit.dockerfile.v0", + "empty_layer": true + } + ], + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c", + "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd", + "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366", + "sha256:4098ad033525263f92a8a9c51865596025d47018e078c0b1f7d9cd5663fc1ffe", + "sha256:f9b4a8a412a0c07a489e46fb5db41f9d0dc84d4ec38a4e2c6ab9531f44e0d9f1", + "sha256:1d017566be3551b67af47235af9dd21b673f3ec7896894a1c4532cd379ef935a", + "sha256:80ed5cd7716e7b4ca3693e37d5017e40275435f138828717f3e59b9f5ca49700", + "sha256:3a389ca4c255083adbdf016f4dac3ac56079b178a7794b1dbbfe10fcc852bea9" + ] + }, + "config": { + "Entrypoint": [ + "informed-decision-container" + ], + "Env": [ + "PATH=/opt/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "LANG=C.UTF-8", + "GPG_KEY=7169605F62C751356D054A26A821E680E5FA6305", + "PYTHON_VERSION=3.12.14", + "PYTHON_SHA256=5c8462af5790baf43a321a1559dbe0db06d1be4300fb85fb53c40060668e548a", + "PYTHONDONTWRITEBYTECODE=1", + "PYTHONUNBUFFERED=1" + ], + "User": "10001:10001", + "WorkingDir": "/app", + "ExposedPorts": { + "8080/tcp": {} + } + } + }, + "Layers": [ + { + "Size": 8697856, + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + { + "Size": 1538048, + "Digest": "sha256:47393b668ce535d06e8967917d424c177e2ecccae83856d48b2e99164f8cc6cb", + "DiffID": "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd" + }, + { + "Size": 47150592, + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + { + "Size": 5120, + "Digest": "sha256:ed62c91ccd94e36a928e96d704cfcf20861302590245cc12aa89a15a5d6be29b", + "DiffID": "sha256:4098ad033525263f92a8a9c51865596025d47018e078c0b1f7d9cd5663fc1ffe" + }, + { + "Size": 131584, + "Digest": "sha256:f0fe3e08ab046c0c5e29d34ee73bc8c7e03398eb891e4498e0a67088a5ea3e86", + "DiffID": "sha256:f9b4a8a412a0c07a489e46fb5db41f9d0dc84d4ec38a4e2c6ab9531f44e0d9f1" + }, + { + "Size": 30306816, + "Digest": "sha256:e8b154ff31e753638a4d96efdaaa0a35d048f04961cd45a513e873672cc146c0", + "DiffID": "sha256:1d017566be3551b67af47235af9dd21b673f3ec7896894a1c4532cd379ef935a" + }, + { + "Size": 12288, + "Digest": "sha256:27373590da885e2f56ee211dd5b848da1b6587dc800b2932b2d0d17fd71070e1", + "DiffID": "sha256:80ed5cd7716e7b4ca3693e37d5017e40275435f138828717f3e59b9f5ca49700" + }, + { + "Size": 1536, + "Digest": "sha256:4fb4682711832d2ae1d02a46b943602569402045b8c4bbcdbd3eabb913397fb1", + "DiffID": "sha256:3a389ca4c255083adbdf016f4dac3ac56079b178a7794b1dbbfe10fcc852bea9" + } + ] + }, + "Results": [ + { + "Target": "informed-decision:hfact-20260911 (alpine 3.24.1)", + "Class": "os-pkgs", + "Type": "alpine", + "Packages": [ + { + "ID": ".python-rundeps@20260901.001029", + "Name": ".python-rundeps", + "Identifier": { + "PURL": "pkg:apk/alpine/.python-rundeps@20260901.001029?arch=noarch\u0026distro=3.24.1", + "UID": "4e26c99fecac1e97" + }, + "Version": "20260901.001029", + "Arch": "noarch", + "DependsOn": [ + "gdbm@1.26-r0", + "libbz2@1.0.8-r6", + "libcrypto3@3.5.8-r0", + "libffi@3.5.2-r1", + "libncursesw@6.6_p20260516-r0", + "libnsl@2.0.1-r2", + "libpanelw@6.6_p20260516-r0", + "libssl3@3.5.8-r0", + "libtirpc@1.3.5-r1", + "libuuid@2.42.3-r1", + "musl@1.2.6-r2", + "readline@8.3.3-r1", + "sqlite-libs@3.53.4-r0", + "xz-libs@5.8.3-r0", + "zlib@1.3.2-r0" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:51d0c1e7d88a414345799f0309185c625013ef32", + "AnalyzedBy": "apk" + }, + { + "ID": "alpine-baselayout@3.7.2-r1", + "Name": "alpine-baselayout", + "Identifier": { + "PURL": "pkg:apk/alpine/alpine-baselayout@3.7.2-r1?arch=x86_64\u0026distro=3.24.1", + "UID": "1b9c543300f8073e" + }, + "Version": "3.7.2-r1", + "Arch": "x86_64", + "SrcName": "alpine-baselayout", + "SrcVersion": "3.7.2-r1", + "Licenses": [ + "GPL-2.0-only" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "alpine-baselayout-data@3.7.2-r1", + "busybox-binsh@1.37.0-r31" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:f0fcfdc2f4da058af6473bc45c4eab62916225b2", + "InstalledFiles": [ + "etc/motd", + "etc/crontabs/root", + "etc/modprobe.d/aliases.conf", + "etc/modprobe.d/blacklist.conf", + "etc/modprobe.d/i386.conf", + "etc/profile.d/20locale.sh", + "etc/profile.d/README", + "etc/profile.d/color_prompt.sh.disabled", + "usr/lib/sysctl.d/00-alpine.conf", + "var/lock", + "var/run", + "var/spool/mail", + "var/spool/cron/crontabs" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "alpine-baselayout-data@3.7.2-r1", + "Name": "alpine-baselayout-data", + "Identifier": { + "PURL": "pkg:apk/alpine/alpine-baselayout-data@3.7.2-r1?arch=x86_64\u0026distro=3.24.1", + "UID": "fcc3cd90122d8aa7" + }, + "Version": "3.7.2-r1", + "Arch": "x86_64", + "SrcName": "alpine-baselayout", + "SrcVersion": "3.7.2-r1", + "Licenses": [ + "GPL-2.0-only" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:c6f9bded17d844b6f1b0bb5766d2c35c0d5c8508", + "InstalledFiles": [ + "etc/fstab", + "etc/group", + "etc/hostname", + "etc/hosts", + "etc/inittab", + "etc/modules", + "etc/mtab", + "etc/nsswitch.conf", + "etc/passwd", + "etc/profile", + "etc/protocols", + "etc/services", + "etc/shadow", + "etc/shells", + "etc/sysctl.conf" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "alpine-keys@2.6-r0", + "Name": "alpine-keys", + "Identifier": { + "PURL": "pkg:apk/alpine/alpine-keys@2.6-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "cb1f6c8fa74713e0" + }, + "Version": "2.6-r0", + "Arch": "x86_64", + "SrcName": "alpine-keys", + "SrcVersion": "2.6-r0", + "Licenses": [ + "MIT" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:d8d6b80e53c059a77e4915da78ba964f3ffea240", + "InstalledFiles": [ + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5e69ca50.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-60ac2099.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616a9724.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616abc23.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616ac3bc.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616adfeb.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616ae350.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616db30d.rsa.pub", + "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-66ba20fe.rsa.pub", + "usr/share/apk/keys/aarch64/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", + "usr/share/apk/keys/aarch64/alpine-devel@lists.alpinelinux.org-616ae350.rsa.pub", + "usr/share/apk/keys/armhf/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/armhf/alpine-devel@lists.alpinelinux.org-616a9724.rsa.pub", + "usr/share/apk/keys/armv7/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "usr/share/apk/keys/armv7/alpine-devel@lists.alpinelinux.org-616adfeb.rsa.pub", + "usr/share/apk/keys/loongarch64/alpine-devel@lists.alpinelinux.org-66ba20fe.rsa.pub", + "usr/share/apk/keys/mips64/alpine-devel@lists.alpinelinux.org-5e69ca50.rsa.pub", + "usr/share/apk/keys/ppc64le/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", + "usr/share/apk/keys/ppc64le/alpine-devel@lists.alpinelinux.org-616abc23.rsa.pub", + "usr/share/apk/keys/riscv64/alpine-devel@lists.alpinelinux.org-60ac2099.rsa.pub", + "usr/share/apk/keys/riscv64/alpine-devel@lists.alpinelinux.org-616db30d.rsa.pub", + "usr/share/apk/keys/s390x/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", + "usr/share/apk/keys/s390x/alpine-devel@lists.alpinelinux.org-616ac3bc.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "alpine-release@3.24.1-r0", + "Name": "alpine-release", + "Identifier": { + "PURL": "pkg:apk/alpine/alpine-release@3.24.1-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "6cfad6f36e1f31aa" + }, + "Version": "3.24.1-r0", + "Arch": "x86_64", + "SrcName": "alpine-base", + "SrcVersion": "3.24.1-r0", + "Licenses": [ + "MIT" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "alpine-keys@2.6-r0" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:bc6912a25cdc7733e0035ed509eceaa4026946e3", + "InstalledFiles": [ + "etc/alpine-release", + "etc/issue", + "etc/os-release", + "etc/secfixes.d/alpine", + "usr/lib/os-release" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "apk-tools@3.0.6-r0", + "Name": "apk-tools", + "Identifier": { + "PURL": "pkg:apk/alpine/apk-tools@3.0.6-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "cb2633c883b17a04" + }, + "Version": "3.0.6-r0", + "Arch": "x86_64", + "SrcName": "apk-tools", + "SrcVersion": "3.0.6-r0", + "Licenses": [ + "GPL-2.0-only" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "ca-certificates-bundle@20260611-r0", + "libapk@3.0.6-r0", + "libcrypto3@3.5.8-r0", + "musl@1.2.6-r2", + "zlib@1.3.2-r0" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:043333589798a1f52e09ae63f026c6c8fa676d34", + "InstalledFiles": [ + "sbin/apk" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "busybox@1.37.0-r31", + "Name": "busybox", + "Identifier": { + "PURL": "pkg:apk/alpine/busybox@1.37.0-r31?arch=x86_64\u0026distro=3.24.1", + "UID": "771020c43c8440bf" + }, + "Version": "1.37.0-r31", + "Arch": "x86_64", + "SrcName": "busybox", + "SrcVersion": "1.37.0-r31", + "Licenses": [ + "GPL-2.0-only" + ], + "Maintainer": "Sören Tempel \u003csoeren+alpine@soeren-tempel.net\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:b5405ebc02f7dd8be95b6265c54b243d5456ab8f", + "InstalledFiles": [ + "bin/busybox", + "etc/securetty", + "etc/busybox-paths.d/busybox", + "etc/logrotate.d/acpid", + "etc/network/if-up.d/dad", + "etc/udhcpc/udhcpc.conf", + "usr/share/udhcpc/default.script" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "busybox-binsh@1.37.0-r31", + "Name": "busybox-binsh", + "Identifier": { + "PURL": "pkg:apk/alpine/busybox-binsh@1.37.0-r31?arch=x86_64\u0026distro=3.24.1", + "UID": "1ec8a3d5d2b63297" + }, + "Version": "1.37.0-r31", + "Arch": "x86_64", + "SrcName": "busybox", + "SrcVersion": "1.37.0-r31", + "Licenses": [ + "GPL-2.0-only" + ], + "Maintainer": "Sören Tempel \u003csoeren+alpine@soeren-tempel.net\u003e", + "DependsOn": [ + "busybox@1.37.0-r31" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:5cbd14acc6f1804c5bac6c77dc2c492f010b0fc7", + "InstalledFiles": [ + "bin/sh" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "ca-certificates@20260611-r0", + "Name": "ca-certificates", + "Identifier": { + "PURL": "pkg:apk/alpine/ca-certificates@20260611-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "2a3c316ee83c6198" + }, + "Version": "20260611-r0", + "Arch": "x86_64", + "SrcName": "ca-certificates", + "SrcVersion": "20260611-r0", + "Licenses": [ + "MPL-2.0", + "MIT" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "busybox-binsh@1.37.0-r31", + "libcrypto3@3.5.8-r0", + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:47393b668ce535d06e8967917d424c177e2ecccae83856d48b2e99164f8cc6cb", + "DiffID": "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd" + }, + "Digest": "sha1:8f2a04abeb54b331e2970a0a0dffb7bd6bdde567", + "InstalledFiles": [ + "etc/ca-certificates.conf", + "etc/apk/protected_paths.d/ca-certificates.list", + "etc/ca-certificates/update.d/certhash", + "usr/bin/c_rehash", + "usr/sbin/update-ca-certificates", + "usr/share/ca-certificates/mozilla/ACCVRAIZ1.crt", + "usr/share/ca-certificates/mozilla/AC_RAIZ_FNMT-RCM.crt", + "usr/share/ca-certificates/mozilla/AC_RAIZ_FNMT-RCM_SERVIDORES_SEGUROS.crt", + "usr/share/ca-certificates/mozilla/ANF_Secure_Server_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Actalis_Authentication_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Amazon_Root_CA_1.crt", + "usr/share/ca-certificates/mozilla/Amazon_Root_CA_2.crt", + "usr/share/ca-certificates/mozilla/Amazon_Root_CA_3.crt", + "usr/share/ca-certificates/mozilla/Amazon_Root_CA_4.crt", + "usr/share/ca-certificates/mozilla/Atos_TrustedRoot_2011.crt", + "usr/share/ca-certificates/mozilla/Atos_TrustedRoot_Root_CA_ECC_TLS_2021.crt", + "usr/share/ca-certificates/mozilla/Atos_TrustedRoot_Root_CA_RSA_TLS_2021.crt", + "usr/share/ca-certificates/mozilla/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.crt", + "usr/share/ca-certificates/mozilla/BJCA_Global_Root_CA1.crt", + "usr/share/ca-certificates/mozilla/BJCA_Global_Root_CA2.crt", + "usr/share/ca-certificates/mozilla/Buypass_Class_2_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Buypass_Class_3_Root_CA.crt", + "usr/share/ca-certificates/mozilla/CA_Disig_Root_R2.crt", + "usr/share/ca-certificates/mozilla/CFCA_EV_ROOT.crt", + "usr/share/ca-certificates/mozilla/COMODO_ECC_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/COMODO_RSA_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/Certainly_Root_E1.crt", + "usr/share/ca-certificates/mozilla/Certainly_Root_R1.crt", + "usr/share/ca-certificates/mozilla/Certigna_Root_CA.crt", + "usr/share/ca-certificates/mozilla/Certum_EC-384_CA.crt", + "usr/share/ca-certificates/mozilla/Certum_Trusted_Network_CA.crt", + "usr/share/ca-certificates/mozilla/Certum_Trusted_Network_CA_2.crt", + "usr/share/ca-certificates/mozilla/Certum_Trusted_Root_CA.crt", + "usr/share/ca-certificates/mozilla/D-TRUST_BR_Root_CA_1_2020.crt", + "usr/share/ca-certificates/mozilla/D-TRUST_BR_Root_CA_2_2023.crt", + "usr/share/ca-certificates/mozilla/D-TRUST_EV_Root_CA_1_2020.crt", + "usr/share/ca-certificates/mozilla/D-TRUST_EV_Root_CA_2_2023.crt", + "usr/share/ca-certificates/mozilla/D-TRUST_Root_Class_3_CA_2_2009.crt", + "usr/share/ca-certificates/mozilla/D-TRUST_Root_Class_3_CA_2_EV_2009.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Assured_ID_Root_G2.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Assured_ID_Root_G3.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Global_Root_G2.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Global_Root_G3.crt", + "usr/share/ca-certificates/mozilla/DigiCert_TLS_ECC_P384_Root_G5.crt", + "usr/share/ca-certificates/mozilla/DigiCert_TLS_RSA4096_Root_G5.crt", + "usr/share/ca-certificates/mozilla/DigiCert_Trusted_Root_G4.crt", + "usr/share/ca-certificates/mozilla/GDCA_TrustAUTH_R5_ROOT.crt", + "usr/share/ca-certificates/mozilla/GTS_Root_R1.crt", + "usr/share/ca-certificates/mozilla/GTS_Root_R3.crt", + "usr/share/ca-certificates/mozilla/GTS_Root_R4.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_ECC_Root_CA_-_R4.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_ECC_Root_CA_-_R5.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_Root_CA_-_R3.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_Root_CA_-_R6.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_Root_E46.crt", + "usr/share/ca-certificates/mozilla/GlobalSign_Root_R46.crt", + "usr/share/ca-certificates/mozilla/Go_Daddy_Root_Certificate_Authority_-_G2.crt", + "usr/share/ca-certificates/mozilla/HARICA_TLS_ECC_Root_CA_2021.crt", + "usr/share/ca-certificates/mozilla/HARICA_TLS_RSA_Root_CA_2021.crt", + "usr/share/ca-certificates/mozilla/Hellenic_Academic_and_Research_Institutions_ECC_RootCA_2015.crt", + "usr/share/ca-certificates/mozilla/Hellenic_Academic_and_Research_Institutions_RootCA_2015.crt", + "usr/share/ca-certificates/mozilla/HiPKI_Root_CA_-_G1.crt", + "usr/share/ca-certificates/mozilla/Hongkong_Post_Root_CA_3.crt", + "usr/share/ca-certificates/mozilla/ISRG_Root_X1.crt", + "usr/share/ca-certificates/mozilla/ISRG_Root_X2.crt", + "usr/share/ca-certificates/mozilla/IdenTrust_Commercial_Root_CA_1.crt", + "usr/share/ca-certificates/mozilla/IdenTrust_Public_Sector_Root_CA_1.crt", + "usr/share/ca-certificates/mozilla/Izenpe.com.crt", + "usr/share/ca-certificates/mozilla/Microsec_e-Szigno_Root_CA_2009.crt", + "usr/share/ca-certificates/mozilla/Microsoft_ECC_Root_Certificate_Authority_2017.crt", + "usr/share/ca-certificates/mozilla/Microsoft_RSA_Root_Certificate_Authority_2017.crt", + "usr/share/ca-certificates/mozilla/NAVER_Global_Root_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/NetLock_Arany_=Class_Gold=_Főtanúsítvány.crt", + "usr/share/ca-certificates/mozilla/OISTE_Server_Root_ECC_G1.crt", + "usr/share/ca-certificates/mozilla/OISTE_Server_Root_RSA_G1.crt", + "usr/share/ca-certificates/mozilla/OISTE_WISeKey_Global_Root_GB_CA.crt", + "usr/share/ca-certificates/mozilla/OISTE_WISeKey_Global_Root_GC_CA.crt", + "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_1_G3.crt", + "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_2_G3.crt", + "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_3_G3.crt", + "usr/share/ca-certificates/mozilla/SSL.com_EV_Root_Certification_Authority_ECC.crt", + "usr/share/ca-certificates/mozilla/SSL.com_EV_Root_Certification_Authority_RSA_R2.crt", + "usr/share/ca-certificates/mozilla/SSL.com_Root_Certification_Authority_ECC.crt", + "usr/share/ca-certificates/mozilla/SSL.com_Root_Certification_Authority_RSA.crt", + "usr/share/ca-certificates/mozilla/SSL.com_TLS_ECC_Root_CA_2022.crt", + "usr/share/ca-certificates/mozilla/SSL.com_TLS_RSA_Root_CA_2022.crt", + "usr/share/ca-certificates/mozilla/SZAFIR_ROOT_CA2.crt", + "usr/share/ca-certificates/mozilla/Sectigo_Public_Server_Authentication_Root_E46.crt", + "usr/share/ca-certificates/mozilla/Sectigo_Public_Server_Authentication_Root_R46.crt", + "usr/share/ca-certificates/mozilla/SecureSign_Root_CA14.crt", + "usr/share/ca-certificates/mozilla/SecureSign_Root_CA15.crt", + "usr/share/ca-certificates/mozilla/Security_Communication_ECC_RootCA1.crt", + "usr/share/ca-certificates/mozilla/Security_Communication_RootCA2.crt", + "usr/share/ca-certificates/mozilla/Starfield_Root_Certificate_Authority_-_G2.crt", + "usr/share/ca-certificates/mozilla/Starfield_Services_Root_Certificate_Authority_-_G2.crt", + "usr/share/ca-certificates/mozilla/SwissSign_RSA_TLS_Root_CA_2022_-_1.crt", + "usr/share/ca-certificates/mozilla/T-TeleSec_GlobalRoot_Class_2.crt", + "usr/share/ca-certificates/mozilla/T-TeleSec_GlobalRoot_Class_3.crt", + "usr/share/ca-certificates/mozilla/TUBITAK_Kamu_SM_SSL_Kok_Sertifikasi_-_Surum_1.crt", + "usr/share/ca-certificates/mozilla/TWCA_CYBER_Root_CA.crt", + "usr/share/ca-certificates/mozilla/TWCA_Global_Root_CA.crt", + "usr/share/ca-certificates/mozilla/TWCA_Root_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/Telekom_Security_TLS_ECC_Root_2020.crt", + "usr/share/ca-certificates/mozilla/Telekom_Security_TLS_RSA_Root_2023.crt", + "usr/share/ca-certificates/mozilla/Telia_Root_CA_v2.crt", + "usr/share/ca-certificates/mozilla/TrustAsia_Global_Root_CA_G3.crt", + "usr/share/ca-certificates/mozilla/TrustAsia_Global_Root_CA_G4.crt", + "usr/share/ca-certificates/mozilla/TrustAsia_TLS_ECC_Root_CA.crt", + "usr/share/ca-certificates/mozilla/TrustAsia_TLS_RSA_Root_CA.crt", + "usr/share/ca-certificates/mozilla/TunTrust_Root_CA.crt", + "usr/share/ca-certificates/mozilla/UCA_Extended_Validation_Root.crt", + "usr/share/ca-certificates/mozilla/UCA_Global_G2_Root.crt", + "usr/share/ca-certificates/mozilla/USERTrust_ECC_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/USERTrust_RSA_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/certSIGN_Root_CA_G2.crt", + "usr/share/ca-certificates/mozilla/e-Szigno_Root_CA_2017.crt", + "usr/share/ca-certificates/mozilla/e-Szigno_TLS_Root_CA_2023.crt", + "usr/share/ca-certificates/mozilla/ePKI_Root_Certification_Authority.crt", + "usr/share/ca-certificates/mozilla/emSign_ECC_Root_CA_-_C3.crt", + "usr/share/ca-certificates/mozilla/emSign_ECC_Root_CA_-_G3.crt", + "usr/share/ca-certificates/mozilla/emSign_Root_CA_-_C1.crt", + "usr/share/ca-certificates/mozilla/emSign_Root_CA_-_G1.crt", + "usr/share/ca-certificates/mozilla/vTrus_ECC_Root_CA.crt", + "usr/share/ca-certificates/mozilla/vTrus_Root_CA.crt" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "ca-certificates-bundle@20260611-r0", + "Name": "ca-certificates-bundle", + "Identifier": { + "PURL": "pkg:apk/alpine/ca-certificates-bundle@20260611-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "20337c2ea28bef28" + }, + "Version": "20260611-r0", + "Arch": "x86_64", + "SrcName": "ca-certificates", + "SrcVersion": "20260611-r0", + "Licenses": [ + "MPL-2.0", + "MIT" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:c9534a03750bdfae341f10969016090fc11febbd", + "InstalledFiles": [ + "etc/ssl/cert.pem", + "etc/ssl/certs/ca-certificates.crt", + "etc/ssl1.1/cert.pem", + "etc/ssl1.1/certs" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "gdbm@1.26-r0", + "Name": "gdbm", + "Identifier": { + "PURL": "pkg:apk/alpine/gdbm@1.26-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "5eb1a3f86262ca77" + }, + "Version": "1.26-r0", + "Arch": "x86_64", + "SrcName": "gdbm", + "SrcVersion": "1.26-r0", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:619493d8124fe49a8ee1d8f7a1372cf7e3977337", + "InstalledFiles": [ + "usr/lib/libgdbm.so.6", + "usr/lib/libgdbm.so.6.0.0", + "usr/lib/libgdbm_compat.so.4", + "usr/lib/libgdbm_compat.so.4.0.0" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "keyutils-libs@1.6.3-r4", + "Name": "keyutils-libs", + "Identifier": { + "PURL": "pkg:apk/alpine/keyutils-libs@1.6.3-r4?arch=x86_64\u0026distro=3.24.1", + "UID": "a1a29120eb342032" + }, + "Version": "1.6.3-r4", + "Arch": "x86_64", + "SrcName": "keyutils", + "SrcVersion": "1.6.3-r4", + "Licenses": [ + "GPL-2.0-or-later", + "LGPL-2.0-or-later" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:b7331c96f077fc3522ee4361a441d1097204cd90", + "InstalledFiles": [ + "usr/lib/libkeyutils.so.1", + "usr/lib/libkeyutils.so.1.10" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "krb5-conf@1.0-r2", + "Name": "krb5-conf", + "Identifier": { + "PURL": "pkg:apk/alpine/krb5-conf@1.0-r2?arch=x86_64\u0026distro=3.24.1", + "UID": "e7cf94ba8b6dbc33" + }, + "Version": "1.0-r2", + "Arch": "x86_64", + "SrcName": "krb5-conf", + "SrcVersion": "1.0-r2", + "Licenses": [ + "MIT" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:40a11e0690a59e110367b62a80661024e9942a2d", + "InstalledFiles": [ + "etc/krb5.conf" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "krb5-libs@1.22.2-r1", + "Name": "krb5-libs", + "Identifier": { + "PURL": "pkg:apk/alpine/krb5-libs@1.22.2-r1?arch=x86_64\u0026distro=3.24.1", + "UID": "958f8b4a4f087dac" + }, + "Version": "1.22.2-r1", + "Arch": "x86_64", + "SrcName": "krb5", + "SrcVersion": "1.22.2-r1", + "Licenses": [ + "MIT" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "keyutils-libs@1.6.3-r4", + "krb5-conf@1.0-r2", + "libcom_err@1.47.4-r0", + "libcrypto3@3.5.8-r0", + "libssl3@3.5.8-r0", + "libverto@0.3.2-r2", + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:ef02346e21b817ff379e4601e5b1fc9760e5e31a", + "InstalledFiles": [ + "usr/lib/libgssapi_krb5.so.2", + "usr/lib/libgssapi_krb5.so.2.2", + "usr/lib/libgssrpc.so.4", + "usr/lib/libgssrpc.so.4.2", + "usr/lib/libk5crypto.so.3", + "usr/lib/libk5crypto.so.3.1", + "usr/lib/libkadm5clnt_mit.so.12", + "usr/lib/libkadm5clnt_mit.so.12.0", + "usr/lib/libkadm5srv_mit.so.12", + "usr/lib/libkadm5srv_mit.so.12.0", + "usr/lib/libkdb5.so.10", + "usr/lib/libkdb5.so.10.0", + "usr/lib/libkrad.so.0", + "usr/lib/libkrad.so.0.0", + "usr/lib/libkrb5.so.3", + "usr/lib/libkrb5.so.3.3", + "usr/lib/libkrb5support.so.0", + "usr/lib/libkrb5support.so.0.1", + "usr/lib/krb5/plugins/kdb/db2.so", + "usr/lib/krb5/plugins/preauth/otp.so", + "usr/lib/krb5/plugins/preauth/spake.so", + "usr/lib/krb5/plugins/preauth/test.so", + "usr/lib/krb5/plugins/tls/k5tls.so" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libapk@3.0.6-r0", + "Name": "libapk", + "Identifier": { + "PURL": "pkg:apk/alpine/libapk@3.0.6-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "a35b8b9b3219513a" + }, + "Version": "3.0.6-r0", + "Arch": "x86_64", + "SrcName": "apk-tools", + "SrcVersion": "3.0.6-r0", + "Licenses": [ + "GPL-2.0-only" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "libcrypto3@3.5.8-r0", + "libssl3@3.5.8-r0", + "musl@1.2.6-r2", + "zlib@1.3.2-r0" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:523a8f58104589f74f743d501c81bf6517155378", + "InstalledFiles": [ + "usr/lib/libapk.so.3.0.0" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libbz2@1.0.8-r6", + "Name": "libbz2", + "Identifier": { + "PURL": "pkg:apk/alpine/libbz2@1.0.8-r6?arch=x86_64\u0026distro=3.24.1", + "UID": "48e17539404e2d56" + }, + "Version": "1.0.8-r6", + "Arch": "x86_64", + "SrcName": "bzip2", + "SrcVersion": "1.0.8-r6", + "Licenses": [ + "bzip-2-1.0.6" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:08566ba5c7af97c25474be8b24e8b48f0116b67d", + "InstalledFiles": [ + "usr/lib/libbz2.so.1", + "usr/lib/libbz2.so.1.0.8" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libcom_err@1.47.4-r0", + "Name": "libcom_err", + "Identifier": { + "PURL": "pkg:apk/alpine/libcom_err@1.47.4-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "3fc35a3f66d192af" + }, + "Version": "1.47.4-r0", + "Arch": "x86_64", + "SrcName": "e2fsprogs", + "SrcVersion": "1.47.4-r0", + "Licenses": [ + "GPL-2.0-or-later", + "LGPL-2.0-or-later", + "BSD-3-Clause", + "MIT" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:4b9fb5899ea0b1c0cfbb2a2c5952704437d4ecef", + "InstalledFiles": [ + "usr/lib/libcom_err.so.2", + "usr/lib/libcom_err.so.2.1" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libcrypto3@3.5.8-r0", + "Name": "libcrypto3", + "Identifier": { + "PURL": "pkg:apk/alpine/libcrypto3@3.5.8-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "4299bba2ba9c96aa" + }, + "Version": "3.5.8-r0", + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "3.5.8-r0", + "Licenses": [ + "Apache-2.0" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:1b3b9fc0c136f4936932bcb2ec21499f0ae5dfd3", + "InstalledFiles": [ + "etc/ssl/ct_log_list.cnf", + "etc/ssl/ct_log_list.cnf.dist", + "etc/ssl/openssl.cnf", + "etc/ssl/openssl.cnf.dist", + "usr/lib/libcrypto.so.3", + "usr/lib/engines-3/afalg.so", + "usr/lib/engines-3/capi.so", + "usr/lib/engines-3/loader_attic.so", + "usr/lib/engines-3/padlock.so", + "usr/lib/ossl-modules/legacy.so" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libffi@3.5.2-r1", + "Name": "libffi", + "Identifier": { + "PURL": "pkg:apk/alpine/libffi@3.5.2-r1?arch=x86_64\u0026distro=3.24.1", + "UID": "d3eaa921287b1ccd" + }, + "Version": "3.5.2-r1", + "Arch": "x86_64", + "SrcName": "libffi", + "SrcVersion": "3.5.2-r1", + "Licenses": [ + "MIT" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:2f7b261f7fcecc4eebd8b330e7f6fb80713bfe3a", + "InstalledFiles": [ + "usr/lib/libffi.so.8", + "usr/lib/libffi.so.8.2.0" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libintl@1.0-r0", + "Name": "libintl", + "Identifier": { + "PURL": "pkg:apk/alpine/libintl@1.0-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "25c18808d8fc87e0" + }, + "Version": "1.0-r0", + "Arch": "x86_64", + "SrcName": "gettext", + "SrcVersion": "1.0-r0", + "Licenses": [ + "LGPL-2.1-or-later" + ], + "Maintainer": "Carlo Landmeter \u003cclandmeter@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:282d6642ccac87237e43a864159d73cf5abe7550", + "InstalledFiles": [ + "usr/lib/libintl.so.8", + "usr/lib/libintl.so.8.4.6" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libncursesw@6.6_p20260516-r0", + "Name": "libncursesw", + "Identifier": { + "PURL": "pkg:apk/alpine/libncursesw@6.6_p20260516-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "e8939f45e2191bcc" + }, + "Version": "6.6_p20260516-r0", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.6_p20260516-r0", + "Licenses": [ + "X-11" + ], + "Maintainer": "Milan P. Stanić \u003cmps@arvanta.net\u003e", + "DependsOn": [ + "musl@1.2.6-r2", + "ncurses-terminfo-base@6.6_p20260516-r0" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:9f318e6e324d6827274829194cf6ac6afbaded12", + "InstalledFiles": [ + "usr/lib/libncursesw.so.6", + "usr/lib/libncursesw.so.6.6" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libnsl@2.0.1-r2", + "Name": "libnsl", + "Identifier": { + "PURL": "pkg:apk/alpine/libnsl@2.0.1-r2?arch=x86_64\u0026distro=3.24.1", + "UID": "3d893c60d41da67b" + }, + "Version": "2.0.1-r2", + "Arch": "x86_64", + "SrcName": "libnsl", + "SrcVersion": "2.0.1-r2", + "Licenses": [ + "LGPL-2.0-or-later" + ], + "DependsOn": [ + "libintl@1.0-r0", + "libtirpc@1.3.5-r1", + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:5194adbf26560b52be318481a7af294ab05fd795", + "InstalledFiles": [ + "usr/lib/libnsl.so.3", + "usr/lib/libnsl.so.3.0.0" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libpanelw@6.6_p20260516-r0", + "Name": "libpanelw", + "Identifier": { + "PURL": "pkg:apk/alpine/libpanelw@6.6_p20260516-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "ef1ad9bb256b373" + }, + "Version": "6.6_p20260516-r0", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.6_p20260516-r0", + "Licenses": [ + "X-11" + ], + "Maintainer": "Milan P. Stanić \u003cmps@arvanta.net\u003e", + "DependsOn": [ + "libncursesw@6.6_p20260516-r0", + "musl@1.2.6-r2", + "ncurses-terminfo-base@6.6_p20260516-r0" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:149bdc434232ed11fc5bb975ba519a07154a6f56", + "InstalledFiles": [ + "usr/lib/libpanelw.so.6", + "usr/lib/libpanelw.so.6.6" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libssl3@3.5.8-r0", + "Name": "libssl3", + "Identifier": { + "PURL": "pkg:apk/alpine/libssl3@3.5.8-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "f290ab59b88b1c4a" + }, + "Version": "3.5.8-r0", + "Arch": "x86_64", + "SrcName": "openssl", + "SrcVersion": "3.5.8-r0", + "Licenses": [ + "Apache-2.0" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "libcrypto3@3.5.8-r0", + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:5f9b1e55d821953d2e2b1db322dad70a3b5041ea", + "InstalledFiles": [ + "usr/lib/libssl.so.3" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libtirpc@1.3.5-r1", + "Name": "libtirpc", + "Identifier": { + "PURL": "pkg:apk/alpine/libtirpc@1.3.5-r1?arch=x86_64\u0026distro=3.24.1", + "UID": "471c8d955ca03be5" + }, + "Version": "1.3.5-r1", + "Arch": "x86_64", + "SrcName": "libtirpc", + "SrcVersion": "1.3.5-r1", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "krb5-libs@1.22.2-r1", + "libtirpc-conf@1.3.5-r1", + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:c32865bd86a8b8829d152279cabbc5ac6d430eb2", + "InstalledFiles": [ + "usr/lib/libtirpc.so.3", + "usr/lib/libtirpc.so.3.0.0" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libtirpc-conf@1.3.5-r1", + "Name": "libtirpc-conf", + "Identifier": { + "PURL": "pkg:apk/alpine/libtirpc-conf@1.3.5-r1?arch=x86_64\u0026distro=3.24.1", + "UID": "3437392c14aad33f" + }, + "Version": "1.3.5-r1", + "Arch": "x86_64", + "SrcName": "libtirpc", + "SrcVersion": "1.3.5-r1", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:0c17d9988dc986c90e22f79778df047eca9973ff", + "InstalledFiles": [ + "etc/bindresvport.blacklist", + "etc/netconfig" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libuuid@2.42.3-r1", + "Name": "libuuid", + "Identifier": { + "PURL": "pkg:apk/alpine/libuuid@2.42.3-r1?arch=x86_64\u0026distro=3.24.1", + "UID": "c444e9cdd1bdda40" + }, + "Version": "2.42.3-r1", + "Arch": "x86_64", + "SrcName": "util-linux", + "SrcVersion": "2.42.3-r1", + "Licenses": [ + "BSD-3-Clause" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:f0fe3e08ab046c0c5e29d34ee73bc8c7e03398eb891e4498e0a67088a5ea3e86", + "DiffID": "sha256:f9b4a8a412a0c07a489e46fb5db41f9d0dc84d4ec38a4e2c6ab9531f44e0d9f1" + }, + "Digest": "sha1:8f041ef637ad75f381cc0c034f2afbbb0303264e", + "InstalledFiles": [ + "usr/lib/libuuid.so.1", + "usr/lib/libuuid.so.1.3.0" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "libverto@0.3.2-r2", + "Name": "libverto", + "Identifier": { + "PURL": "pkg:apk/alpine/libverto@0.3.2-r2?arch=x86_64\u0026distro=3.24.1", + "UID": "9df4321a283a6cf6" + }, + "Version": "0.3.2-r2", + "Arch": "x86_64", + "SrcName": "libverto", + "SrcVersion": "0.3.2-r2", + "Licenses": [ + "MIT" + ], + "Maintainer": "Francesco Colista \u003cfcolista@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:73233059338142e4ac6d8fb8bb0074e93da75dc1", + "InstalledFiles": [ + "usr/lib/libverto.so.1", + "usr/lib/libverto.so.1.0.0" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "musl@1.2.6-r2", + "Name": "musl", + "Identifier": { + "PURL": "pkg:apk/alpine/musl@1.2.6-r2?arch=x86_64\u0026distro=3.24.1", + "UID": "1f8cb44befe503d4" + }, + "Version": "1.2.6-r2", + "Arch": "x86_64", + "SrcName": "musl", + "SrcVersion": "1.2.6-r2", + "Licenses": [ + "MIT" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:0f7e5827e4e1a73631beda27b78431a8ba240e05", + "InstalledFiles": [ + "lib/ld-musl-x86_64.so.1", + "lib/libc.musl-x86_64.so.1" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "musl-utils@1.2.6-r2", + "Name": "musl-utils", + "Identifier": { + "PURL": "pkg:apk/alpine/musl-utils@1.2.6-r2?arch=x86_64\u0026distro=3.24.1", + "UID": "4497482ec1d943e1" + }, + "Version": "1.2.6-r2", + "Arch": "x86_64", + "SrcName": "musl", + "SrcVersion": "1.2.6-r2", + "Licenses": [ + "MIT", + "BSD-2-Clause", + "GPL-2.0-or-later" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2", + "scanelf@1.3.9-r1" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:75ada3c48d63ec5d87c42fdf934a3fdd11298dc5", + "InstalledFiles": [ + "sbin/ldconfig", + "usr/bin/getconf", + "usr/bin/getent", + "usr/bin/iconv", + "usr/bin/ldd" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "ncurses-terminfo-base@6.6_p20260516-r0", + "Name": "ncurses-terminfo-base", + "Identifier": { + "PURL": "pkg:apk/alpine/ncurses-terminfo-base@6.6_p20260516-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "e7fac5c6a6e9ae8" + }, + "Version": "6.6_p20260516-r0", + "Arch": "x86_64", + "SrcName": "ncurses", + "SrcVersion": "6.6_p20260516-r0", + "Licenses": [ + "X-11" + ], + "Maintainer": "Milan P. Stanić \u003cmps@arvanta.net\u003e", + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:9d551d828e9c8ee52c5801c1d6046828ebf15752", + "InstalledFiles": [ + "etc/terminfo/a/alacritty", + "etc/terminfo/a/ansi", + "etc/terminfo/d/dumb", + "etc/terminfo/g/gnome", + "etc/terminfo/g/gnome-256color", + "etc/terminfo/k/konsole", + "etc/terminfo/k/konsole-256color", + "etc/terminfo/k/konsole-linux", + "etc/terminfo/l/linux", + "etc/terminfo/p/putty", + "etc/terminfo/p/putty-256color", + "etc/terminfo/r/rxvt", + "etc/terminfo/r/rxvt-256color", + "etc/terminfo/s/screen", + "etc/terminfo/s/screen-256color", + "etc/terminfo/s/st-0.6", + "etc/terminfo/s/st-0.7", + "etc/terminfo/s/st-0.8", + "etc/terminfo/s/st-0.8.5", + "etc/terminfo/s/st-16color", + "etc/terminfo/s/st-256color", + "etc/terminfo/s/st-direct", + "etc/terminfo/s/sun", + "etc/terminfo/t/terminator", + "etc/terminfo/t/terminology", + "etc/terminfo/t/terminology-0.6.1", + "etc/terminfo/t/terminology-1.0.0", + "etc/terminfo/t/terminology-1.8.1", + "etc/terminfo/t/tmux", + "etc/terminfo/t/tmux-256color", + "etc/terminfo/v/vt100", + "etc/terminfo/v/vt102", + "etc/terminfo/v/vt200", + "etc/terminfo/v/vt220", + "etc/terminfo/v/vt52", + "etc/terminfo/v/vte", + "etc/terminfo/v/vte-256color", + "etc/terminfo/x/xterm", + "etc/terminfo/x/xterm-256color", + "etc/terminfo/x/xterm-color", + "etc/terminfo/x/xterm-xfree86" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "readline@8.3.3-r1", + "Name": "readline", + "Identifier": { + "PURL": "pkg:apk/alpine/readline@8.3.3-r1?arch=x86_64\u0026distro=3.24.1", + "UID": "f50058781c93e1f2" + }, + "Version": "8.3.3-r1", + "Arch": "x86_64", + "SrcName": "readline", + "SrcVersion": "8.3.3-r1", + "Licenses": [ + "GPL-3.0-or-later" + ], + "Maintainer": "qaqland \u003cqaq@qaq.land\u003e", + "DependsOn": [ + "libncursesw@6.6_p20260516-r0", + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:945b16e378bc00e44d89d4de8124bba7647fdf4d", + "InstalledFiles": [ + "etc/inputrc", + "usr/lib/libreadline.so.8", + "usr/lib/libreadline.so.8.3" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "scanelf@1.3.9-r1", + "Name": "scanelf", + "Identifier": { + "PURL": "pkg:apk/alpine/scanelf@1.3.9-r1?arch=x86_64\u0026distro=3.24.1", + "UID": "936394657a1626fb" + }, + "Version": "1.3.9-r1", + "Arch": "x86_64", + "SrcName": "pax-utils", + "SrcVersion": "1.3.9-r1", + "Licenses": [ + "GPL-2.0-only" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:013f903d0ba219673aebbdd04f74bb5ed82b01f0", + "InstalledFiles": [ + "usr/bin/scanelf" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "sqlite-libs@3.53.4-r0", + "Name": "sqlite-libs", + "Identifier": { + "PURL": "pkg:apk/alpine/sqlite-libs@3.53.4-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "8b10aee2ccc192de" + }, + "Version": "3.53.4-r0", + "Arch": "x86_64", + "SrcName": "sqlite", + "SrcVersion": "3.53.4-r0", + "Licenses": [ + "blessing" + ], + "Maintainer": "Jingyun Hua \u003chuajingyun@loongson.cn\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:f7630ceb37c232b6609861aeab6cdb9cd120e6fa", + "InstalledFiles": [ + "usr/lib/libsqlite3.so.0", + "usr/lib/libsqlite3.so.3.53.4" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "ssl_client@1.37.0-r31", + "Name": "ssl_client", + "Identifier": { + "PURL": "pkg:apk/alpine/ssl_client@1.37.0-r31?arch=x86_64\u0026distro=3.24.1", + "UID": "d42e31f15a4ce47a" + }, + "Version": "1.37.0-r31", + "Arch": "x86_64", + "SrcName": "busybox", + "SrcVersion": "1.37.0-r31", + "Licenses": [ + "GPL-2.0-only" + ], + "Maintainer": "Sören Tempel \u003csoeren+alpine@soeren-tempel.net\u003e", + "DependsOn": [ + "libcrypto3@3.5.8-r0", + "libssl3@3.5.8-r0", + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:c220f4a5125a313af733e98def94132bb1e9d40d", + "InstalledFiles": [ + "usr/bin/ssl_client" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "tzdata@2026c-r0", + "Name": "tzdata", + "Identifier": { + "PURL": "pkg:apk/alpine/tzdata@2026c-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "52c6b50eff629f9d" + }, + "Version": "2026c-r0", + "Arch": "x86_64", + "SrcName": "tzdata", + "SrcVersion": "2026c-r0", + "Licenses": [ + "Public-Domain" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "Layer": { + "Digest": "sha256:47393b668ce535d06e8967917d424c177e2ecccae83856d48b2e99164f8cc6cb", + "DiffID": "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd" + }, + "Digest": "sha1:61f7544ecd8dd63eac23fe3e645702fda92ef02a", + "InstalledFiles": [ + "usr/share/zoneinfo/CET", + "usr/share/zoneinfo/CST6CDT", + "usr/share/zoneinfo/Cuba", + "usr/share/zoneinfo/EET", + "usr/share/zoneinfo/EST", + "usr/share/zoneinfo/EST5EDT", + "usr/share/zoneinfo/Egypt", + "usr/share/zoneinfo/Eire", + "usr/share/zoneinfo/Factory", + "usr/share/zoneinfo/GB", + "usr/share/zoneinfo/GB-Eire", + "usr/share/zoneinfo/GMT", + "usr/share/zoneinfo/GMT+0", + "usr/share/zoneinfo/GMT-0", + "usr/share/zoneinfo/GMT0", + "usr/share/zoneinfo/Greenwich", + "usr/share/zoneinfo/HST", + "usr/share/zoneinfo/Hongkong", + "usr/share/zoneinfo/Iceland", + "usr/share/zoneinfo/Iran", + "usr/share/zoneinfo/Israel", + "usr/share/zoneinfo/Jamaica", + "usr/share/zoneinfo/Japan", + "usr/share/zoneinfo/Kwajalein", + "usr/share/zoneinfo/Libya", + "usr/share/zoneinfo/MET", + "usr/share/zoneinfo/MST", + "usr/share/zoneinfo/MST7MDT", + "usr/share/zoneinfo/NZ", + "usr/share/zoneinfo/NZ-CHAT", + "usr/share/zoneinfo/Navajo", + "usr/share/zoneinfo/PRC", + "usr/share/zoneinfo/PST8PDT", + "usr/share/zoneinfo/Poland", + "usr/share/zoneinfo/Portugal", + "usr/share/zoneinfo/ROC", + "usr/share/zoneinfo/ROK", + "usr/share/zoneinfo/Singapore", + "usr/share/zoneinfo/Turkey", + "usr/share/zoneinfo/UCT", + "usr/share/zoneinfo/UTC", + "usr/share/zoneinfo/Universal", + "usr/share/zoneinfo/W-SU", + "usr/share/zoneinfo/WET", + "usr/share/zoneinfo/Zulu", + "usr/share/zoneinfo/iso3166.tab", + "usr/share/zoneinfo/leap-seconds.list", + "usr/share/zoneinfo/posixrules", + "usr/share/zoneinfo/zone.tab", + "usr/share/zoneinfo/zone1970.tab", + "usr/share/zoneinfo/Africa/Abidjan", + "usr/share/zoneinfo/Africa/Accra", + "usr/share/zoneinfo/Africa/Addis_Ababa", + "usr/share/zoneinfo/Africa/Algiers", + "usr/share/zoneinfo/Africa/Asmara", + "usr/share/zoneinfo/Africa/Asmera", + "usr/share/zoneinfo/Africa/Bamako", + "usr/share/zoneinfo/Africa/Bangui", + "usr/share/zoneinfo/Africa/Banjul", + "usr/share/zoneinfo/Africa/Bissau", + "usr/share/zoneinfo/Africa/Blantyre", + "usr/share/zoneinfo/Africa/Brazzaville", + "usr/share/zoneinfo/Africa/Bujumbura", + "usr/share/zoneinfo/Africa/Cairo", + "usr/share/zoneinfo/Africa/Casablanca", + "usr/share/zoneinfo/Africa/Ceuta", + "usr/share/zoneinfo/Africa/Conakry", + "usr/share/zoneinfo/Africa/Dakar", + "usr/share/zoneinfo/Africa/Dar_es_Salaam", + "usr/share/zoneinfo/Africa/Djibouti", + "usr/share/zoneinfo/Africa/Douala", + "usr/share/zoneinfo/Africa/El_Aaiun", + "usr/share/zoneinfo/Africa/Freetown", + "usr/share/zoneinfo/Africa/Gaborone", + "usr/share/zoneinfo/Africa/Harare", + "usr/share/zoneinfo/Africa/Johannesburg", + "usr/share/zoneinfo/Africa/Juba", + "usr/share/zoneinfo/Africa/Kampala", + "usr/share/zoneinfo/Africa/Khartoum", + "usr/share/zoneinfo/Africa/Kigali", + "usr/share/zoneinfo/Africa/Kinshasa", + "usr/share/zoneinfo/Africa/Lagos", + "usr/share/zoneinfo/Africa/Libreville", + "usr/share/zoneinfo/Africa/Lome", + "usr/share/zoneinfo/Africa/Luanda", + "usr/share/zoneinfo/Africa/Lubumbashi", + "usr/share/zoneinfo/Africa/Lusaka", + "usr/share/zoneinfo/Africa/Malabo", + "usr/share/zoneinfo/Africa/Maputo", + "usr/share/zoneinfo/Africa/Maseru", + "usr/share/zoneinfo/Africa/Mbabane", + "usr/share/zoneinfo/Africa/Mogadishu", + "usr/share/zoneinfo/Africa/Monrovia", + "usr/share/zoneinfo/Africa/Nairobi", + "usr/share/zoneinfo/Africa/Ndjamena", + "usr/share/zoneinfo/Africa/Niamey", + "usr/share/zoneinfo/Africa/Nouakchott", + "usr/share/zoneinfo/Africa/Ouagadougou", + "usr/share/zoneinfo/Africa/Porto-Novo", + "usr/share/zoneinfo/Africa/Sao_Tome", + "usr/share/zoneinfo/Africa/Timbuktu", + "usr/share/zoneinfo/Africa/Tripoli", + "usr/share/zoneinfo/Africa/Tunis", + "usr/share/zoneinfo/Africa/Windhoek", + "usr/share/zoneinfo/America/Adak", + "usr/share/zoneinfo/America/Anchorage", + "usr/share/zoneinfo/America/Anguilla", + "usr/share/zoneinfo/America/Antigua", + "usr/share/zoneinfo/America/Araguaina", + "usr/share/zoneinfo/America/Aruba", + "usr/share/zoneinfo/America/Asuncion", + "usr/share/zoneinfo/America/Atikokan", + "usr/share/zoneinfo/America/Atka", + "usr/share/zoneinfo/America/Bahia", + "usr/share/zoneinfo/America/Bahia_Banderas", + "usr/share/zoneinfo/America/Barbados", + "usr/share/zoneinfo/America/Belem", + "usr/share/zoneinfo/America/Belize", + "usr/share/zoneinfo/America/Blanc-Sablon", + "usr/share/zoneinfo/America/Boa_Vista", + "usr/share/zoneinfo/America/Bogota", + "usr/share/zoneinfo/America/Boise", + "usr/share/zoneinfo/America/Buenos_Aires", + "usr/share/zoneinfo/America/Cambridge_Bay", + "usr/share/zoneinfo/America/Campo_Grande", + "usr/share/zoneinfo/America/Cancun", + "usr/share/zoneinfo/America/Caracas", + "usr/share/zoneinfo/America/Catamarca", + "usr/share/zoneinfo/America/Cayenne", + "usr/share/zoneinfo/America/Cayman", + "usr/share/zoneinfo/America/Chicago", + "usr/share/zoneinfo/America/Chihuahua", + "usr/share/zoneinfo/America/Ciudad_Juarez", + "usr/share/zoneinfo/America/Coral_Harbour", + "usr/share/zoneinfo/America/Cordoba", + "usr/share/zoneinfo/America/Costa_Rica", + "usr/share/zoneinfo/America/Coyhaique", + "usr/share/zoneinfo/America/Creston", + "usr/share/zoneinfo/America/Cuiaba", + "usr/share/zoneinfo/America/Curacao", + "usr/share/zoneinfo/America/Danmarkshavn", + "usr/share/zoneinfo/America/Dawson", + "usr/share/zoneinfo/America/Dawson_Creek", + "usr/share/zoneinfo/America/Denver", + "usr/share/zoneinfo/America/Detroit", + "usr/share/zoneinfo/America/Dominica", + "usr/share/zoneinfo/America/Edmonton", + "usr/share/zoneinfo/America/Eirunepe", + "usr/share/zoneinfo/America/El_Salvador", + "usr/share/zoneinfo/America/Ensenada", + "usr/share/zoneinfo/America/Fort_Nelson", + "usr/share/zoneinfo/America/Fort_Wayne", + "usr/share/zoneinfo/America/Fortaleza", + "usr/share/zoneinfo/America/Glace_Bay", + "usr/share/zoneinfo/America/Godthab", + "usr/share/zoneinfo/America/Goose_Bay", + "usr/share/zoneinfo/America/Grand_Turk", + "usr/share/zoneinfo/America/Grenada", + "usr/share/zoneinfo/America/Guadeloupe", + "usr/share/zoneinfo/America/Guatemala", + "usr/share/zoneinfo/America/Guayaquil", + "usr/share/zoneinfo/America/Guyana", + "usr/share/zoneinfo/America/Halifax", + "usr/share/zoneinfo/America/Havana", + "usr/share/zoneinfo/America/Hermosillo", + "usr/share/zoneinfo/America/Indianapolis", + "usr/share/zoneinfo/America/Inuvik", + "usr/share/zoneinfo/America/Iqaluit", + "usr/share/zoneinfo/America/Jamaica", + "usr/share/zoneinfo/America/Jujuy", + "usr/share/zoneinfo/America/Juneau", + "usr/share/zoneinfo/America/Knox_IN", + "usr/share/zoneinfo/America/Kralendijk", + "usr/share/zoneinfo/America/La_Paz", + "usr/share/zoneinfo/America/Lima", + "usr/share/zoneinfo/America/Los_Angeles", + "usr/share/zoneinfo/America/Louisville", + "usr/share/zoneinfo/America/Lower_Princes", + "usr/share/zoneinfo/America/Maceio", + "usr/share/zoneinfo/America/Managua", + "usr/share/zoneinfo/America/Manaus", + "usr/share/zoneinfo/America/Marigot", + "usr/share/zoneinfo/America/Martinique", + "usr/share/zoneinfo/America/Matamoros", + "usr/share/zoneinfo/America/Mazatlan", + "usr/share/zoneinfo/America/Mendoza", + "usr/share/zoneinfo/America/Menominee", + "usr/share/zoneinfo/America/Merida", + "usr/share/zoneinfo/America/Metlakatla", + "usr/share/zoneinfo/America/Mexico_City", + "usr/share/zoneinfo/America/Miquelon", + "usr/share/zoneinfo/America/Moncton", + "usr/share/zoneinfo/America/Monterrey", + "usr/share/zoneinfo/America/Montevideo", + "usr/share/zoneinfo/America/Montreal", + "usr/share/zoneinfo/America/Montserrat", + "usr/share/zoneinfo/America/Nassau", + "usr/share/zoneinfo/America/New_York", + "usr/share/zoneinfo/America/Nipigon", + "usr/share/zoneinfo/America/Nome", + "usr/share/zoneinfo/America/Noronha", + "usr/share/zoneinfo/America/Nuuk", + "usr/share/zoneinfo/America/Ojinaga", + "usr/share/zoneinfo/America/Panama", + "usr/share/zoneinfo/America/Pangnirtung", + "usr/share/zoneinfo/America/Paramaribo", + "usr/share/zoneinfo/America/Phoenix", + "usr/share/zoneinfo/America/Port-au-Prince", + "usr/share/zoneinfo/America/Port_of_Spain", + "usr/share/zoneinfo/America/Porto_Acre", + "usr/share/zoneinfo/America/Porto_Velho", + "usr/share/zoneinfo/America/Puerto_Rico", + "usr/share/zoneinfo/America/Punta_Arenas", + "usr/share/zoneinfo/America/Rainy_River", + "usr/share/zoneinfo/America/Rankin_Inlet", + "usr/share/zoneinfo/America/Recife", + "usr/share/zoneinfo/America/Regina", + "usr/share/zoneinfo/America/Resolute", + "usr/share/zoneinfo/America/Rio_Branco", + "usr/share/zoneinfo/America/Rosario", + "usr/share/zoneinfo/America/Santa_Isabel", + "usr/share/zoneinfo/America/Santarem", + "usr/share/zoneinfo/America/Santiago", + "usr/share/zoneinfo/America/Santo_Domingo", + "usr/share/zoneinfo/America/Sao_Paulo", + "usr/share/zoneinfo/America/Scoresbysund", + "usr/share/zoneinfo/America/Shiprock", + "usr/share/zoneinfo/America/Sitka", + "usr/share/zoneinfo/America/St_Barthelemy", + "usr/share/zoneinfo/America/St_Johns", + "usr/share/zoneinfo/America/St_Kitts", + "usr/share/zoneinfo/America/St_Lucia", + "usr/share/zoneinfo/America/St_Thomas", + "usr/share/zoneinfo/America/St_Vincent", + "usr/share/zoneinfo/America/Swift_Current", + "usr/share/zoneinfo/America/Tegucigalpa", + "usr/share/zoneinfo/America/Thule", + "usr/share/zoneinfo/America/Thunder_Bay", + "usr/share/zoneinfo/America/Tijuana", + "usr/share/zoneinfo/America/Toronto", + "usr/share/zoneinfo/America/Tortola", + "usr/share/zoneinfo/America/Vancouver", + "usr/share/zoneinfo/America/Virgin", + "usr/share/zoneinfo/America/Whitehorse", + "usr/share/zoneinfo/America/Winnipeg", + "usr/share/zoneinfo/America/Yakutat", + "usr/share/zoneinfo/America/Yellowknife", + "usr/share/zoneinfo/America/Argentina/Buenos_Aires", + "usr/share/zoneinfo/America/Argentina/Catamarca", + "usr/share/zoneinfo/America/Argentina/ComodRivadavia", + "usr/share/zoneinfo/America/Argentina/Cordoba", + "usr/share/zoneinfo/America/Argentina/Jujuy", + "usr/share/zoneinfo/America/Argentina/La_Rioja", + "usr/share/zoneinfo/America/Argentina/Mendoza", + "usr/share/zoneinfo/America/Argentina/Rio_Gallegos", + "usr/share/zoneinfo/America/Argentina/Salta", + "usr/share/zoneinfo/America/Argentina/San_Juan", + "usr/share/zoneinfo/America/Argentina/San_Luis", + "usr/share/zoneinfo/America/Argentina/Tucuman", + "usr/share/zoneinfo/America/Argentina/Ushuaia", + "usr/share/zoneinfo/America/Indiana/Indianapolis", + "usr/share/zoneinfo/America/Indiana/Knox", + "usr/share/zoneinfo/America/Indiana/Marengo", + "usr/share/zoneinfo/America/Indiana/Petersburg", + "usr/share/zoneinfo/America/Indiana/Tell_City", + "usr/share/zoneinfo/America/Indiana/Vevay", + "usr/share/zoneinfo/America/Indiana/Vincennes", + "usr/share/zoneinfo/America/Indiana/Winamac", + "usr/share/zoneinfo/America/Kentucky/Louisville", + "usr/share/zoneinfo/America/Kentucky/Monticello", + "usr/share/zoneinfo/America/North_Dakota/Beulah", + "usr/share/zoneinfo/America/North_Dakota/Center", + "usr/share/zoneinfo/America/North_Dakota/New_Salem", + "usr/share/zoneinfo/Antarctica/Casey", + "usr/share/zoneinfo/Antarctica/Davis", + "usr/share/zoneinfo/Antarctica/DumontDUrville", + "usr/share/zoneinfo/Antarctica/Macquarie", + "usr/share/zoneinfo/Antarctica/Mawson", + "usr/share/zoneinfo/Antarctica/McMurdo", + "usr/share/zoneinfo/Antarctica/Palmer", + "usr/share/zoneinfo/Antarctica/Rothera", + "usr/share/zoneinfo/Antarctica/South_Pole", + "usr/share/zoneinfo/Antarctica/Syowa", + "usr/share/zoneinfo/Antarctica/Troll", + "usr/share/zoneinfo/Antarctica/Vostok", + "usr/share/zoneinfo/Arctic/Longyearbyen", + "usr/share/zoneinfo/Asia/Aden", + "usr/share/zoneinfo/Asia/Almaty", + "usr/share/zoneinfo/Asia/Amman", + "usr/share/zoneinfo/Asia/Anadyr", + "usr/share/zoneinfo/Asia/Aqtau", + "usr/share/zoneinfo/Asia/Aqtobe", + "usr/share/zoneinfo/Asia/Ashgabat", + "usr/share/zoneinfo/Asia/Ashkhabad", + "usr/share/zoneinfo/Asia/Atyrau", + "usr/share/zoneinfo/Asia/Baghdad", + "usr/share/zoneinfo/Asia/Bahrain", + "usr/share/zoneinfo/Asia/Baku", + "usr/share/zoneinfo/Asia/Bangkok", + "usr/share/zoneinfo/Asia/Barnaul", + "usr/share/zoneinfo/Asia/Beirut", + "usr/share/zoneinfo/Asia/Bishkek", + "usr/share/zoneinfo/Asia/Brunei", + "usr/share/zoneinfo/Asia/Calcutta", + "usr/share/zoneinfo/Asia/Chita", + "usr/share/zoneinfo/Asia/Choibalsan", + "usr/share/zoneinfo/Asia/Chongqing", + "usr/share/zoneinfo/Asia/Chungking", + "usr/share/zoneinfo/Asia/Colombo", + "usr/share/zoneinfo/Asia/Dacca", + "usr/share/zoneinfo/Asia/Damascus", + "usr/share/zoneinfo/Asia/Dhaka", + "usr/share/zoneinfo/Asia/Dili", + "usr/share/zoneinfo/Asia/Dubai", + "usr/share/zoneinfo/Asia/Dushanbe", + "usr/share/zoneinfo/Asia/Famagusta", + "usr/share/zoneinfo/Asia/Gaza", + "usr/share/zoneinfo/Asia/Harbin", + "usr/share/zoneinfo/Asia/Hebron", + "usr/share/zoneinfo/Asia/Ho_Chi_Minh", + "usr/share/zoneinfo/Asia/Hong_Kong", + "usr/share/zoneinfo/Asia/Hovd", + "usr/share/zoneinfo/Asia/Irkutsk", + "usr/share/zoneinfo/Asia/Istanbul", + "usr/share/zoneinfo/Asia/Jakarta", + "usr/share/zoneinfo/Asia/Jayapura", + "usr/share/zoneinfo/Asia/Jerusalem", + "usr/share/zoneinfo/Asia/Kabul", + "usr/share/zoneinfo/Asia/Kamchatka", + "usr/share/zoneinfo/Asia/Karachi", + "usr/share/zoneinfo/Asia/Kashgar", + "usr/share/zoneinfo/Asia/Kathmandu", + "usr/share/zoneinfo/Asia/Katmandu", + "usr/share/zoneinfo/Asia/Khandyga", + "usr/share/zoneinfo/Asia/Kolkata", + "usr/share/zoneinfo/Asia/Krasnoyarsk", + "usr/share/zoneinfo/Asia/Kuala_Lumpur", + "usr/share/zoneinfo/Asia/Kuching", + "usr/share/zoneinfo/Asia/Kuwait", + "usr/share/zoneinfo/Asia/Macao", + "usr/share/zoneinfo/Asia/Macau", + "usr/share/zoneinfo/Asia/Magadan", + "usr/share/zoneinfo/Asia/Makassar", + "usr/share/zoneinfo/Asia/Manila", + "usr/share/zoneinfo/Asia/Muscat", + "usr/share/zoneinfo/Asia/Nicosia", + "usr/share/zoneinfo/Asia/Novokuznetsk", + "usr/share/zoneinfo/Asia/Novosibirsk", + "usr/share/zoneinfo/Asia/Omsk", + "usr/share/zoneinfo/Asia/Oral", + "usr/share/zoneinfo/Asia/Phnom_Penh", + "usr/share/zoneinfo/Asia/Pontianak", + "usr/share/zoneinfo/Asia/Pyongyang", + "usr/share/zoneinfo/Asia/Qatar", + "usr/share/zoneinfo/Asia/Qostanay", + "usr/share/zoneinfo/Asia/Qyzylorda", + "usr/share/zoneinfo/Asia/Rangoon", + "usr/share/zoneinfo/Asia/Riyadh", + "usr/share/zoneinfo/Asia/Saigon", + "usr/share/zoneinfo/Asia/Sakhalin", + "usr/share/zoneinfo/Asia/Samarkand", + "usr/share/zoneinfo/Asia/Seoul", + "usr/share/zoneinfo/Asia/Shanghai", + "usr/share/zoneinfo/Asia/Singapore", + "usr/share/zoneinfo/Asia/Srednekolymsk", + "usr/share/zoneinfo/Asia/Taipei", + "usr/share/zoneinfo/Asia/Tashkent", + "usr/share/zoneinfo/Asia/Tbilisi", + "usr/share/zoneinfo/Asia/Tehran", + "usr/share/zoneinfo/Asia/Tel_Aviv", + "usr/share/zoneinfo/Asia/Thimbu", + "usr/share/zoneinfo/Asia/Thimphu", + "usr/share/zoneinfo/Asia/Tokyo", + "usr/share/zoneinfo/Asia/Tomsk", + "usr/share/zoneinfo/Asia/Ujung_Pandang", + "usr/share/zoneinfo/Asia/Ulaanbaatar", + "usr/share/zoneinfo/Asia/Ulan_Bator", + "usr/share/zoneinfo/Asia/Urumqi", + "usr/share/zoneinfo/Asia/Ust-Nera", + "usr/share/zoneinfo/Asia/Vientiane", + "usr/share/zoneinfo/Asia/Vladivostok", + "usr/share/zoneinfo/Asia/Yakutsk", + "usr/share/zoneinfo/Asia/Yangon", + "usr/share/zoneinfo/Asia/Yekaterinburg", + "usr/share/zoneinfo/Asia/Yerevan", + "usr/share/zoneinfo/Atlantic/Azores", + "usr/share/zoneinfo/Atlantic/Bermuda", + "usr/share/zoneinfo/Atlantic/Canary", + "usr/share/zoneinfo/Atlantic/Cape_Verde", + "usr/share/zoneinfo/Atlantic/Faeroe", + "usr/share/zoneinfo/Atlantic/Faroe", + "usr/share/zoneinfo/Atlantic/Jan_Mayen", + "usr/share/zoneinfo/Atlantic/Madeira", + "usr/share/zoneinfo/Atlantic/Reykjavik", + "usr/share/zoneinfo/Atlantic/South_Georgia", + "usr/share/zoneinfo/Atlantic/St_Helena", + "usr/share/zoneinfo/Atlantic/Stanley", + "usr/share/zoneinfo/Australia/ACT", + "usr/share/zoneinfo/Australia/Adelaide", + "usr/share/zoneinfo/Australia/Brisbane", + "usr/share/zoneinfo/Australia/Broken_Hill", + "usr/share/zoneinfo/Australia/Canberra", + "usr/share/zoneinfo/Australia/Currie", + "usr/share/zoneinfo/Australia/Darwin", + "usr/share/zoneinfo/Australia/Eucla", + "usr/share/zoneinfo/Australia/Hobart", + "usr/share/zoneinfo/Australia/LHI", + "usr/share/zoneinfo/Australia/Lindeman", + "usr/share/zoneinfo/Australia/Lord_Howe", + "usr/share/zoneinfo/Australia/Melbourne", + "usr/share/zoneinfo/Australia/NSW", + "usr/share/zoneinfo/Australia/North", + "usr/share/zoneinfo/Australia/Perth", + "usr/share/zoneinfo/Australia/Queensland", + "usr/share/zoneinfo/Australia/South", + "usr/share/zoneinfo/Australia/Sydney", + "usr/share/zoneinfo/Australia/Tasmania", + "usr/share/zoneinfo/Australia/Victoria", + "usr/share/zoneinfo/Australia/West", + "usr/share/zoneinfo/Australia/Yancowinna", + "usr/share/zoneinfo/Brazil/Acre", + "usr/share/zoneinfo/Brazil/DeNoronha", + "usr/share/zoneinfo/Brazil/East", + "usr/share/zoneinfo/Brazil/West", + "usr/share/zoneinfo/Canada/Atlantic", + "usr/share/zoneinfo/Canada/Central", + "usr/share/zoneinfo/Canada/Eastern", + "usr/share/zoneinfo/Canada/Mountain", + "usr/share/zoneinfo/Canada/Newfoundland", + "usr/share/zoneinfo/Canada/Pacific", + "usr/share/zoneinfo/Canada/Saskatchewan", + "usr/share/zoneinfo/Canada/Yukon", + "usr/share/zoneinfo/Chile/Continental", + "usr/share/zoneinfo/Chile/EasterIsland", + "usr/share/zoneinfo/Etc/GMT", + "usr/share/zoneinfo/Etc/GMT+0", + "usr/share/zoneinfo/Etc/GMT+1", + "usr/share/zoneinfo/Etc/GMT+10", + "usr/share/zoneinfo/Etc/GMT+11", + "usr/share/zoneinfo/Etc/GMT+12", + "usr/share/zoneinfo/Etc/GMT+2", + "usr/share/zoneinfo/Etc/GMT+3", + "usr/share/zoneinfo/Etc/GMT+4", + "usr/share/zoneinfo/Etc/GMT+5", + "usr/share/zoneinfo/Etc/GMT+6", + "usr/share/zoneinfo/Etc/GMT+7", + "usr/share/zoneinfo/Etc/GMT+8", + "usr/share/zoneinfo/Etc/GMT+9", + "usr/share/zoneinfo/Etc/GMT-0", + "usr/share/zoneinfo/Etc/GMT-1", + "usr/share/zoneinfo/Etc/GMT-10", + "usr/share/zoneinfo/Etc/GMT-11", + "usr/share/zoneinfo/Etc/GMT-12", + "usr/share/zoneinfo/Etc/GMT-13", + "usr/share/zoneinfo/Etc/GMT-14", + "usr/share/zoneinfo/Etc/GMT-2", + "usr/share/zoneinfo/Etc/GMT-3", + "usr/share/zoneinfo/Etc/GMT-4", + "usr/share/zoneinfo/Etc/GMT-5", + "usr/share/zoneinfo/Etc/GMT-6", + "usr/share/zoneinfo/Etc/GMT-7", + "usr/share/zoneinfo/Etc/GMT-8", + "usr/share/zoneinfo/Etc/GMT-9", + "usr/share/zoneinfo/Etc/GMT0", + "usr/share/zoneinfo/Etc/Greenwich", + "usr/share/zoneinfo/Etc/UCT", + "usr/share/zoneinfo/Etc/UTC", + "usr/share/zoneinfo/Etc/Universal", + "usr/share/zoneinfo/Etc/Zulu", + "usr/share/zoneinfo/Europe/Amsterdam", + "usr/share/zoneinfo/Europe/Andorra", + "usr/share/zoneinfo/Europe/Astrakhan", + "usr/share/zoneinfo/Europe/Athens", + "usr/share/zoneinfo/Europe/Belfast", + "usr/share/zoneinfo/Europe/Belgrade", + "usr/share/zoneinfo/Europe/Berlin", + "usr/share/zoneinfo/Europe/Bratislava", + "usr/share/zoneinfo/Europe/Brussels", + "usr/share/zoneinfo/Europe/Bucharest", + "usr/share/zoneinfo/Europe/Budapest", + "usr/share/zoneinfo/Europe/Busingen", + "usr/share/zoneinfo/Europe/Chisinau", + "usr/share/zoneinfo/Europe/Copenhagen", + "usr/share/zoneinfo/Europe/Dublin", + "usr/share/zoneinfo/Europe/Gibraltar", + "usr/share/zoneinfo/Europe/Guernsey", + "usr/share/zoneinfo/Europe/Helsinki", + "usr/share/zoneinfo/Europe/Isle_of_Man", + "usr/share/zoneinfo/Europe/Istanbul", + "usr/share/zoneinfo/Europe/Jersey", + "usr/share/zoneinfo/Europe/Kaliningrad", + "usr/share/zoneinfo/Europe/Kiev", + "usr/share/zoneinfo/Europe/Kirov", + "usr/share/zoneinfo/Europe/Kyiv", + "usr/share/zoneinfo/Europe/Lisbon", + "usr/share/zoneinfo/Europe/Ljubljana", + "usr/share/zoneinfo/Europe/London", + "usr/share/zoneinfo/Europe/Luxembourg", + "usr/share/zoneinfo/Europe/Madrid", + "usr/share/zoneinfo/Europe/Malta", + "usr/share/zoneinfo/Europe/Mariehamn", + "usr/share/zoneinfo/Europe/Minsk", + "usr/share/zoneinfo/Europe/Monaco", + "usr/share/zoneinfo/Europe/Moscow", + "usr/share/zoneinfo/Europe/Nicosia", + "usr/share/zoneinfo/Europe/Oslo", + "usr/share/zoneinfo/Europe/Paris", + "usr/share/zoneinfo/Europe/Podgorica", + "usr/share/zoneinfo/Europe/Prague", + "usr/share/zoneinfo/Europe/Riga", + "usr/share/zoneinfo/Europe/Rome", + "usr/share/zoneinfo/Europe/Samara", + "usr/share/zoneinfo/Europe/San_Marino", + "usr/share/zoneinfo/Europe/Sarajevo", + "usr/share/zoneinfo/Europe/Saratov", + "usr/share/zoneinfo/Europe/Simferopol", + "usr/share/zoneinfo/Europe/Skopje", + "usr/share/zoneinfo/Europe/Sofia", + "usr/share/zoneinfo/Europe/Stockholm", + "usr/share/zoneinfo/Europe/Tallinn", + "usr/share/zoneinfo/Europe/Tirane", + "usr/share/zoneinfo/Europe/Tiraspol", + "usr/share/zoneinfo/Europe/Ulyanovsk", + "usr/share/zoneinfo/Europe/Uzhgorod", + "usr/share/zoneinfo/Europe/Vaduz", + "usr/share/zoneinfo/Europe/Vatican", + "usr/share/zoneinfo/Europe/Vienna", + "usr/share/zoneinfo/Europe/Vilnius", + "usr/share/zoneinfo/Europe/Volgograd", + "usr/share/zoneinfo/Europe/Warsaw", + "usr/share/zoneinfo/Europe/Zagreb", + "usr/share/zoneinfo/Europe/Zaporozhye", + "usr/share/zoneinfo/Europe/Zurich", + "usr/share/zoneinfo/Indian/Antananarivo", + "usr/share/zoneinfo/Indian/Chagos", + "usr/share/zoneinfo/Indian/Christmas", + "usr/share/zoneinfo/Indian/Cocos", + "usr/share/zoneinfo/Indian/Comoro", + "usr/share/zoneinfo/Indian/Kerguelen", + "usr/share/zoneinfo/Indian/Mahe", + "usr/share/zoneinfo/Indian/Maldives", + "usr/share/zoneinfo/Indian/Mauritius", + "usr/share/zoneinfo/Indian/Mayotte", + "usr/share/zoneinfo/Indian/Reunion", + "usr/share/zoneinfo/Mexico/BajaNorte", + "usr/share/zoneinfo/Mexico/BajaSur", + "usr/share/zoneinfo/Mexico/General", + "usr/share/zoneinfo/Pacific/Apia", + "usr/share/zoneinfo/Pacific/Auckland", + "usr/share/zoneinfo/Pacific/Bougainville", + "usr/share/zoneinfo/Pacific/Chatham", + "usr/share/zoneinfo/Pacific/Chuuk", + "usr/share/zoneinfo/Pacific/Easter", + "usr/share/zoneinfo/Pacific/Efate", + "usr/share/zoneinfo/Pacific/Enderbury", + "usr/share/zoneinfo/Pacific/Fakaofo", + "usr/share/zoneinfo/Pacific/Fiji", + "usr/share/zoneinfo/Pacific/Funafuti", + "usr/share/zoneinfo/Pacific/Galapagos", + "usr/share/zoneinfo/Pacific/Gambier", + "usr/share/zoneinfo/Pacific/Guadalcanal", + "usr/share/zoneinfo/Pacific/Guam", + "usr/share/zoneinfo/Pacific/Honolulu", + "usr/share/zoneinfo/Pacific/Johnston", + "usr/share/zoneinfo/Pacific/Kanton", + "usr/share/zoneinfo/Pacific/Kiritimati", + "usr/share/zoneinfo/Pacific/Kosrae", + "usr/share/zoneinfo/Pacific/Kwajalein", + "usr/share/zoneinfo/Pacific/Majuro", + "usr/share/zoneinfo/Pacific/Marquesas", + "usr/share/zoneinfo/Pacific/Midway", + "usr/share/zoneinfo/Pacific/Nauru", + "usr/share/zoneinfo/Pacific/Niue", + "usr/share/zoneinfo/Pacific/Norfolk", + "usr/share/zoneinfo/Pacific/Noumea", + "usr/share/zoneinfo/Pacific/Pago_Pago", + "usr/share/zoneinfo/Pacific/Palau", + "usr/share/zoneinfo/Pacific/Pitcairn", + "usr/share/zoneinfo/Pacific/Pohnpei", + "usr/share/zoneinfo/Pacific/Ponape", + "usr/share/zoneinfo/Pacific/Port_Moresby", + "usr/share/zoneinfo/Pacific/Rarotonga", + "usr/share/zoneinfo/Pacific/Saipan", + "usr/share/zoneinfo/Pacific/Samoa", + "usr/share/zoneinfo/Pacific/Tahiti", + "usr/share/zoneinfo/Pacific/Tarawa", + "usr/share/zoneinfo/Pacific/Tongatapu", + "usr/share/zoneinfo/Pacific/Truk", + "usr/share/zoneinfo/Pacific/Wake", + "usr/share/zoneinfo/Pacific/Wallis", + "usr/share/zoneinfo/Pacific/Yap", + "usr/share/zoneinfo/US/Alaska", + "usr/share/zoneinfo/US/Aleutian", + "usr/share/zoneinfo/US/Arizona", + "usr/share/zoneinfo/US/Central", + "usr/share/zoneinfo/US/East-Indiana", + "usr/share/zoneinfo/US/Eastern", + "usr/share/zoneinfo/US/Hawaii", + "usr/share/zoneinfo/US/Indiana-Starke", + "usr/share/zoneinfo/US/Michigan", + "usr/share/zoneinfo/US/Mountain", + "usr/share/zoneinfo/US/Pacific", + "usr/share/zoneinfo/US/Samoa" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "xz-libs@5.8.3-r0", + "Name": "xz-libs", + "Identifier": { + "PURL": "pkg:apk/alpine/xz-libs@5.8.3-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "1ae658a5f132a091" + }, + "Version": "5.8.3-r0", + "Arch": "x86_64", + "SrcName": "xz", + "SrcVersion": "5.8.3-r0", + "Licenses": [ + "GPL-2.0-or-later", + "0BSD", + "Public-Domain", + "LGPL-2.1-or-later" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:2014fd3d47b2c2dd4dedc765822ac60ae269f3efa0e614f46ae29f540c7f1235", + "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" + }, + "Digest": "sha1:4c92c5be0c5bef404e93c880eba9037a9b147347", + "InstalledFiles": [ + "usr/lib/liblzma.so.5", + "usr/lib/liblzma.so.5.8.3" + ], + "AnalyzedBy": "apk" + }, + { + "ID": "zlib@1.3.2-r0", + "Name": "zlib", + "Identifier": { + "PURL": "pkg:apk/alpine/zlib@1.3.2-r0?arch=x86_64\u0026distro=3.24.1", + "UID": "e37054a2982d6c16" + }, + "Version": "1.3.2-r0", + "Arch": "x86_64", + "SrcName": "zlib", + "SrcVersion": "1.3.2-r0", + "Licenses": [ + "Zlib" + ], + "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", + "DependsOn": [ + "musl@1.2.6-r2" + ], + "Layer": { + "Digest": "sha256:55afa1ecc21d2bb5e5045f32dafee56272ffd89860bac26f6c32123439af26a4", + "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" + }, + "Digest": "sha1:dd4c3d102acaef2a71a1495951d55fabc8680613", + "InstalledFiles": [ + "usr/lib/libz.so.1", + "usr/lib/libz.so.1.3.2" + ], + "AnalyzedBy": "apk" + } + ] + }, + { + "Target": "Python", + "Class": "lang-pkgs", + "Type": "python-pkg", + "Packages": [ + { + "Name": "PyJWT", + "Identifier": { + "PURL": "pkg:pypi/pyjwt@2.13.0", + "UID": "8d24882c32751540" + }, + "Version": "2.13.0", + "Licenses": [ + "MIT" + ], + "Layer": { + "Digest": "sha256:e8b154ff31e753638a4d96efdaaa0a35d048f04961cd45a513e873672cc146c0", + "DiffID": "sha256:1d017566be3551b67af47235af9dd21b673f3ec7896894a1c4532cd379ef935a" + }, + "FilePath": "opt/venv/lib/python3.12/site-packages/pyjwt-2.13.0.dist-info/METADATA", + "AnalyzedBy": "python-pkg" + }, + { + "Name": "cffi", + "Identifier": { + "PURL": "pkg:pypi/cffi@2.1.1", + "UID": "21042e2f37e33c05" + }, + "Version": "2.1.1", + "Licenses": [ + "MIT-0" + ], + "Layer": { + "Digest": "sha256:e8b154ff31e753638a4d96efdaaa0a35d048f04961cd45a513e873672cc146c0", + "DiffID": "sha256:1d017566be3551b67af47235af9dd21b673f3ec7896894a1c4532cd379ef935a" + }, + "FilePath": "opt/venv/lib/python3.12/site-packages/cffi-2.1.1.dist-info/METADATA", + "AnalyzedBy": "python-pkg" + }, + { + "Name": "cryptography", + "Identifier": { + "PURL": "pkg:pypi/cryptography@50.0.1", + "UID": "dcb3df6383fe2a60" + }, + "Version": "50.0.1", + "Licenses": [ + "Apache-2.0 OR BSD-3-Clause" + ], + "Layer": { + "Digest": "sha256:e8b154ff31e753638a4d96efdaaa0a35d048f04961cd45a513e873672cc146c0", + "DiffID": "sha256:1d017566be3551b67af47235af9dd21b673f3ec7896894a1c4532cd379ef935a" + }, + "FilePath": "opt/venv/lib/python3.12/site-packages/cryptography-50.0.1.dist-info/METADATA", + "AnalyzedBy": "python-pkg" + }, + { + "Name": "informed-decision", + "Identifier": { + "PURL": "pkg:pypi/informed-decision@0.2.0", + "UID": "7989f2afae8ff568" + }, + "Version": "0.2.0", + "Layer": { + "Digest": "sha256:e8b154ff31e753638a4d96efdaaa0a35d048f04961cd45a513e873672cc146c0", + "DiffID": "sha256:1d017566be3551b67af47235af9dd21b673f3ec7896894a1c4532cd379ef935a" + }, + "FilePath": "opt/venv/lib/python3.12/site-packages/informed_decision-0.2.0.dist-info/METADATA", + "AnalyzedBy": "python-pkg" + }, + { + "Name": "pycparser", + "Identifier": { + "PURL": "pkg:pypi/pycparser@3.0", + "UID": "e5a4d03f15a695ff" + }, + "Version": "3.0", + "Licenses": [ + "BSD-3-Clause" + ], + "Layer": { + "Digest": "sha256:e8b154ff31e753638a4d96efdaaa0a35d048f04961cd45a513e873672cc146c0", + "DiffID": "sha256:1d017566be3551b67af47235af9dd21b673f3ec7896894a1c4532cd379ef935a" + }, + "FilePath": "opt/venv/lib/python3.12/site-packages/pycparser-3.0.dist-info/METADATA", + "AnalyzedBy": "python-pkg" + }, + { + "Name": "waitress", + "Identifier": { + "PURL": "pkg:pypi/waitress@3.0.2", + "UID": "806e218e1c1e85f" + }, + "Version": "3.0.2", + "Licenses": [ + "ZPL-2.1" + ], + "Layer": { + "Digest": "sha256:e8b154ff31e753638a4d96efdaaa0a35d048f04961cd45a513e873672cc146c0", + "DiffID": "sha256:1d017566be3551b67af47235af9dd21b673f3ec7896894a1c4532cd379ef935a" + }, + "FilePath": "opt/venv/lib/python3.12/site-packages/waitress-3.0.2.dist-info/METADATA", + "AnalyzedBy": "python-pkg" + } + ] + } + ] +} diff --git a/docs/evidence/2026-09-11-container-smoke.json b/docs/evidence/2026-09-11-container-smoke.json new file mode 100644 index 0000000..6d59b2e --- /dev/null +++ b/docs/evidence/2026-09-11-container-smoke.json @@ -0,0 +1,30 @@ +{ + "status": "passed", + "image_id": "sha256:c882d6c10ce61b29eed06594a0eb01abe5b1adb136a0343101f991c2305701b4", + "repo_digests": [ + "informed-decision@sha256:c882d6c10ce61b29eed06594a0eb01abe5b1adb136a0343101f991c2305701b4" + ], + "checks_passed": 11, + "checks": [ + "installed container entrypoint serves with projected configuration", + "non-root read-only runtime has no external network or published port", + "private config/database modes enforced; package installer absent", + "configured review refuses anonymous access and reports audit-unready status", + "a second writer cannot acquire the active evidence volume", + "synthetic uncertain intent and undelivered evidence remain explicit", + "consistent backup created while serving; overwrite refused", + "SIGTERM closes the serving writer without forced kill", + "restart preserves immutable content and unresolved submission without retry", + "separate restored volume preserves exact content and unresolved state", + "incomplete owner configuration exits before serving" + ], + "fixture_only": true, + "external_network": "none", + "content_snapshot_sha256": "08669bc5e86f292e516045483d9889b05881d0278add97c92a905a58d077d69a", + "native_identity_policy_audit_proven": false, + "published": false, + "deployed": false, + "factory_attempts": 0, + "paid_model_calls": 0, + "cleanup_complete": true +} diff --git a/docs/protected-browser-review.md b/docs/protected-browser-review.md index 8b0f7b8..42cfb3b 100644 --- a/docs/protected-browser-review.md +++ b/docs/protected-browser-review.md @@ -108,7 +108,9 @@ needed, so projected rotation does not require storing a token in configuration or SQLite. Runtime loading provisions nothing. Fixed internal `.svc` origins are supported explicitly; public cleartext origins and redirects are refused. The server remains one Waitress process, loopback port 8080, four threads. -Multi-replica sessions/storage and deployment packaging require separate work. +Multi-replica sessions/storage require separate work. Single-writer container +packaging and the review-only deployment packet are now supplied in +[deploy/README.md](../deploy/README.md); publication and native rollout remain open. The audit thread ticks every 30 seconds, generates the declared per-class heartbeats, drains at most ten records per tick and writes a private diff --git a/informed_decision/container.py b/informed_decision/container.py new file mode 100644 index 0000000..7b23670 --- /dev/null +++ b/informed_decision/container.py @@ -0,0 +1,114 @@ +"""Container boundary: private runtime config, local evidence and one writer. + +Kubernetes projected configuration is root-owned and symlinked. Copy its bounded +non-secret JSON into an owned ephemeral file; do not weaken Runtime's private +file checks or copy projected bearer credentials into persistent storage. +""" + +import argparse +from contextlib import contextmanager +import fcntl +import json +import os +from pathlib import Path +import sqlite3 +import signal +import stat +import tempfile + +from .store import Store + + +def private_directory(path): + path = Path(path) + if not path.is_absolute(): + raise ValueError("absolute private directory required") + path.mkdir(mode=0o700, exist_ok=True) + info = path.lstat() + if (not stat.S_ISDIR(info.st_mode) or info.st_uid != os.getuid() + or stat.S_IMODE(info.st_mode) != 0o700): + raise ValueError("private directory has unsafe ownership or mode") + return path + + +def prepare_configuration(source, *, data_root=Path('/data'), run_root=Path('/run/informed-decision')): + with Path(source).open('rb') as handle: + if not stat.S_ISREG(os.fstat(handle.fileno()).st_mode): + raise ValueError('configuration must be a regular projected file') + raw = handle.read(16385) + if len(raw) > 16384: + raise ValueError('configuration too large') + data = json.loads(raw) + evidence = Path(data_root) / 'private' + if not isinstance(data, dict) or data.get('evidence_db') != str(evidence / 'review.sqlite'): + raise ValueError('container evidence must use its private persistent volume') + private_directory(evidence) + destination = private_directory(Path(run_root) / 'private') / 'runtime.json' + fd, temporary = tempfile.mkstemp(prefix='.runtime-', dir=destination.parent) + try: + with os.fdopen(fd, 'wb') as handle: + handle.write(raw); handle.flush(); os.fsync(handle.fileno()) + os.replace(temporary, destination) + finally: + if os.path.exists(temporary): + os.unlink(temporary) + return destination, evidence + + +@contextmanager +def single_writer(evidence): + directory = private_directory(evidence) + fd = os.open(directory / 'serve.lock', os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600) + try: + info = os.fstat(fd) + if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() + or info.st_nlink != 1 or stat.S_IMODE(info.st_mode) != 0o600): + raise ValueError('unsafe service lock') + try: + fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + raise ValueError('another review service holds this evidence volume') from None + yield + finally: + os.close(fd) + + +def main(): + from .web import main as serve + def terminate(signum, frame): + # PID 1 does not get ordinary default signal behavior. Let Waitress + # drain its dispatcher and web.main stop the audit pump on SIGTERM. + raise SystemExit(0) + signal.signal(signal.SIGTERM, terminate) + try: + config, evidence = prepare_configuration(os.environ.get('INFD_CONTAINER_CONFIG', '/configuration/runtime.json')) + with single_writer(evidence): + os.environ['INFD_REVIEW_CONFIG'] = str(config) + os.environ['INFD_LISTEN_HOST'] = '0.0.0.0' + serve() + except (OSError, ValueError, sqlite3.Error): + # Configuration can be supplied by an operator. Do not echo its values + # or an upstream response on startup failure. + raise SystemExit('Container runtime could not start; inspect configuration and private volume admission.') from None + + +def admin(): + parser = argparse.ArgumentParser(description='Local custody operations; no network or approval mutation.') + commands = parser.add_subparsers(dest='command', required=True) + backup = commands.add_parser('backup', help='SQLite-consistent backup to a new private file') + backup.add_argument('--db', type=Path, required=True) + backup.add_argument('--output', type=Path, required=True) + inspect = commands.add_parser('inspect', help='Report delivery/submission counts, never private content') + inspect.add_argument('--db', type=Path, required=True) + args = parser.parse_args() + if not args.db.is_file(): + parser.error('existing evidence database required') + store = Store(args.db) + if args.command == 'backup': + store.backup(args.output) + print(json.dumps({'status': 'backed_up', 'consistent_snapshot': True})) + else: + with store._connection() as db: + print(json.dumps({'schema_version': db.execute('PRAGMA user_version').fetchone()[0], + 'outbox': {r[0]: r[1] for r in db.execute('SELECT state,COUNT(*) FROM outbox GROUP BY state')}, + 'submissions': {r[0]: r[1] for r in db.execute('SELECT state,COUNT(*) FROM submissions GROUP BY state')}})) diff --git a/informed_decision/web.py b/informed_decision/web.py index ffaa5e7..a063549 100644 --- a/informed_decision/web.py +++ b/informed_decision/web.py @@ -213,6 +213,9 @@ class App: def main(): from waitress import serve + host = os.environ.get("INFD_LISTEN_HOST", "127.0.0.1") + if host not in ("127.0.0.1", "0.0.0.0"): + raise ValueError("unsupported listener address") # Waitress does not log request targets; a proxy must also omit callback # query strings and cookies. No debug traceback middleware belongs here. login = KeyCapeLogin(os.environ["INFD_KEYCAPE_ISSUER"]) @@ -224,7 +227,7 @@ def main(): app = App(login, runtime.controller if runtime else None, readiness=runtime.pump.ready if runtime else lambda: False) try: - serve(app, host="127.0.0.1", port=8080, threads=4) + serve(app, host=host, port=8080, threads=4) finally: if runtime: runtime.pump.stop() diff --git a/pyproject.toml b/pyproject.toml index 1eef16d..0585608 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,8 @@ dependencies = ["PyJWT[crypto]>=2.10,<3", "waitress>=3,<4"] [project.scripts] informed-decision-web = "informed_decision.web:main" +informed-decision-container = "informed_decision.container:main" +informed-decision-admin = "informed_decision.container:admin" [project.optional-dependencies] dev = ["pytest>=8", "PyYAML>=6,<7"] diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 0000000..9f836f1 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,164 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml --python-version 3.12 --generate-hashes --output-file requirements.lock +cffi==2.1.1 \ + --hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \ + --hash=sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66 \ + --hash=sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2 \ + --hash=sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0 \ + --hash=sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6 \ + --hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \ + --hash=sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c \ + --hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \ + --hash=sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9 \ + --hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \ + --hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \ + --hash=sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80 \ + --hash=sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f \ + --hash=sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1 \ + --hash=sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29 \ + --hash=sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8 \ + --hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \ + --hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \ + --hash=sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48 \ + --hash=sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813 \ + --hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \ + --hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 \ + --hash=sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6 \ + --hash=sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1 \ + --hash=sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659 \ + --hash=sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688 \ + --hash=sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004 \ + --hash=sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0 \ + --hash=sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062 \ + --hash=sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779 \ + --hash=sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94 \ + --hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \ + --hash=sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab \ + --hash=sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac \ + --hash=sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6 \ + --hash=sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676 \ + --hash=sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1 \ + --hash=sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9 \ + --hash=sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf \ + --hash=sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13 \ + --hash=sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e \ + --hash=sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e \ + --hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \ + --hash=sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527 \ + --hash=sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72 \ + --hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \ + --hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \ + --hash=sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990 \ + --hash=sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd \ + --hash=sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9 \ + --hash=sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94 \ + --hash=sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3 \ + --hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \ + --hash=sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41 \ + --hash=sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5 \ + --hash=sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c \ + --hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \ + --hash=sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4 \ + --hash=sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e \ + --hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \ + --hash=sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98 \ + --hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \ + --hash=sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1 \ + --hash=sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03 \ + --hash=sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af \ + --hash=sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231 \ + --hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \ + --hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \ + --hash=sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836 \ + --hash=sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5 \ + --hash=sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399 \ + --hash=sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96 \ + --hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \ + --hash=sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be \ + --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \ + --hash=sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc \ + --hash=sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455 \ + --hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \ + --hash=sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12 \ + --hash=sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b \ + --hash=sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7 \ + --hash=sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692 \ + --hash=sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54 \ + --hash=sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3 \ + --hash=sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b \ + --hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \ + --hash=sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d \ + --hash=sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358 \ + --hash=sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a \ + --hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \ + --hash=sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc \ + --hash=sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960 \ + --hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \ + --hash=sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb \ + --hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \ + --hash=sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa \ + --hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \ + --hash=sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3 \ + --hash=sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4 \ + --hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264 + # via cryptography +cryptography==50.0.1 \ + --hash=sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71 \ + --hash=sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23 \ + --hash=sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6 \ + --hash=sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e \ + --hash=sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361 \ + --hash=sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054 \ + --hash=sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f \ + --hash=sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6 \ + --hash=sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49 \ + --hash=sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5 \ + --hash=sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149 \ + --hash=sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88 \ + --hash=sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad \ + --hash=sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a \ + --hash=sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f \ + --hash=sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2 \ + --hash=sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20 \ + --hash=sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45 \ + --hash=sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f \ + --hash=sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b \ + --hash=sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527 \ + --hash=sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3 \ + --hash=sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6 \ + --hash=sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367 \ + --hash=sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0 \ + --hash=sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94 \ + --hash=sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239 \ + --hash=sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b \ + --hash=sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a \ + --hash=sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9 \ + --hash=sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5 \ + --hash=sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc \ + --hash=sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648 \ + --hash=sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986 \ + --hash=sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959 \ + --hash=sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0 \ + --hash=sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17 \ + --hash=sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e \ + --hash=sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733 \ + --hash=sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f \ + --hash=sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8 \ + --hash=sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf \ + --hash=sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671 \ + --hash=sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80 \ + --hash=sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558 \ + --hash=sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef + # via pyjwt +pycparser==3.0 \ + --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ + --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 + # via cffi +pyjwt==2.13.0 \ + --hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \ + --hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728 + # via informed-decision (pyproject.toml) +waitress==3.0.2 \ + --hash=sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f \ + --hash=sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e + # via informed-decision (pyproject.toml) diff --git a/tests/test_container_runtime.py b/tests/test_container_runtime.py new file mode 100644 index 0000000..7a61410 --- /dev/null +++ b/tests/test_container_runtime.py @@ -0,0 +1,103 @@ +import json +import os +from pathlib import Path +import stat + +import pytest + +from informed_decision.container import prepare_configuration, private_directory, single_writer +from informed_decision.runtime import Runtime +from informed_decision.store import Store + + +@pytest.fixture +def projected(tmp_path): + data=tmp_path/'data';data.mkdir() + run=tmp_path/'run';run.mkdir() + config={'schema':'informed-decision.review-runtime.v1','evidence_db':str(data/'private/review.sqlite'), + 'approval_origin':'http://approval-engine.approval-engine.svc.cluster.local:8080', + 'policy':{'origin':'http://flex-auth-review.flex-auth.svc.cluster.local:8080', + 'package':'fixture','version':'v1','package_digest':'sha256:'+'a'*64, + 'caller_token_file':str(tmp_path/'not-provisioned-caller')}, + 'audit':{'origin':'http://audit-core.audit-core.svc.cluster.local:8080', + 'sender_token_file':str(tmp_path/'not-provisioned-sender')}} + payload=tmp_path/'projected';payload.write_text(json.dumps(config));payload.chmod(0o444) + link=tmp_path/'runtime.json';link.symlink_to(payload) + return link,data,run,config + + +def test_projected_config_becomes_private_without_reading_or_copying_credentials(projected): + source,data,run,expected=projected + result,evidence=prepare_configuration(source,data_root=data,run_root=run) + assert not result.is_symlink() and stat.S_IMODE(result.stat().st_mode)==0o600 + assert result.stat().st_uid==os.getuid() and json.loads(result.read_text())==expected + assert stat.S_IMODE(evidence.stat().st_mode)==0o700 + runtime=Runtime.from_file(result) + assert not runtime.pump.ready() + assert not Path(expected['policy']['caller_token_file']).exists() + assert not Path(expected['audit']['sender_token_file']).exists() + assert sorted(p.name for p in result.parent.iterdir())==['runtime.json'] + + +@pytest.mark.parametrize('fault',['oversize','outside_db','unsafe_directory','symlink_directory']) +def test_unsafe_container_storage_and_config_refused(projected,tmp_path,fault): + source,data,run,config=projected + source.resolve().chmod(0o644) + if fault=='oversize':source.write_text('x'*16385) + if fault=='outside_db': + config['evidence_db']=str(tmp_path/'outside.sqlite');source.write_text(json.dumps(config)) + if fault=='unsafe_directory':(data/'private').mkdir(mode=0o755) + if fault=='symlink_directory':(data/'private').symlink_to(run) + with pytest.raises((ValueError,FileExistsError)): + prepare_configuration(source,data_root=data,run_root=run) + assert not (data/'private/review.sqlite').exists() + + +def test_second_service_cannot_open_the_same_evidence_volume(tmp_path): + private=private_directory(tmp_path/'private') + with single_writer(private): + with pytest.raises(ValueError,match='another review service'): + with single_writer(private):pass + with single_writer(private):pass # Process/holder release permits restart. + + +@pytest.mark.parametrize('fault',['symlink','hardlink','mode']) +def test_unsafe_service_lock_refused(tmp_path,fault): + private=private_directory(tmp_path/'private');lock=private/'serve.lock' + if fault=='symlink':lock.symlink_to(tmp_path/'outside') + else: + lock.touch(mode=0o600) + if fault=='hardlink':os.link(lock,private/'other') + else:lock.chmod(0o644) + with pytest.raises((ValueError,OSError)): + with single_writer(private):pass + + +def test_reconfiguration_preserves_persisted_evidence(projected): + source,data,run,config=projected + result,_=prepare_configuration(source,data_root=data,run_root=run) + first=Runtime.from_file(result);digest=first.controller.store.put_document(b'private fixture content') + source.resolve().chmod(0o644) + config['policy']['version']='v2';source.write_text(json.dumps(config)) + result,_=prepare_configuration(source,data_root=data,run_root=run) + second=Runtime.from_file(result) + assert second.controller.policy.version=='v2' + with second.controller.store._connection() as db: + assert db.execute('SELECT content FROM documents WHERE digest=?',(digest,)).fetchone()[0]==b'private fixture content' + + +@pytest.mark.parametrize('host',[None,'0.0.0.0','localhost']) +def test_listener_exposure_requires_explicit_supported_setting(monkeypatch,host): + import waitress + from informed_decision import web + monkeypatch.delenv('INFD_REVIEW_CONFIG',raising=False) + monkeypatch.setenv('INFD_KEYCAPE_ISSUER','https://keycape.test') + monkeypatch.delenv('INFD_LISTEN_HOST',raising=False) + if host:monkeypatch.setenv('INFD_LISTEN_HOST',host) + observed=[] + monkeypatch.setattr(waitress,'serve',lambda app,**kwargs:observed.append(kwargs)) + if host=='localhost': + with pytest.raises(ValueError):web.main() + assert not observed + else: + web.main();assert observed[0]['host']==(host or '127.0.0.1') diff --git a/tests/test_deployment_candidate.py b/tests/test_deployment_candidate.py new file mode 100644 index 0000000..ac7742a --- /dev/null +++ b/tests/test_deployment_candidate.py @@ -0,0 +1,77 @@ +import copy +import importlib.util +from pathlib import Path + +import pytest + +spec=importlib.util.spec_from_file_location('render_deployment',Path(__file__).parents[1]/'tools/render_deployment.py') +module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module) + + +def inputs(): + return {'image':'forgejo.coulomb.social/coulomb/informed-decision@sha256:'+'a'*64, + 'policy':{'origin':'http://flex-auth-review.flex-auth.svc.cluster.local:8080','package':'review.example', + 'version':'v1','package_digest':'sha256:'+'b'*64,'pod_name':'flex-auth-review'}, + 'keycape_egress_ips':['92.205.62.239'],'storage_class':'local-path'} + + +def test_candidate_does_not_create_custody_and_uses_one_private_writer(): + result=module.render(inputs());objects=result['items'] + assert not any(o['kind'] in {'Secret','Role','RoleBinding','ClusterRoleBinding','Ingress','Namespace'} for o in objects) + deploy=next(o for o in objects if o['kind']=='Deployment')['spec'] + assert deploy['replicas']==1 and deploy['strategy']=={'type':'Recreate'} + pod=deploy['template']['spec'];container=pod['containers'][0] + assert pod['automountServiceAccountToken'] is False and pod['serviceAccountName']=='review' + assert pod['securityContext']['runAsUser']==pod['securityContext']['fsGroup']==10001 + assert container['securityContext']['readOnlyRootFilesystem'] is True + assert container['securityContext']['capabilities']['drop']==['ALL'] + caller=next(v for v in pod['volumes'] if v['name']=='caller') + assert caller['projected']['sources']==[{'serviceAccountToken':{'path':'token','audience':'flex-auth','expirationSeconds':3600}}] + # Healthy serving remains available for refusal/recovery; application itself + # gates accept on audit /readyz, without a dependency-driven restart cycle. + assert container['readinessProbe']['httpGet']['path']=='/healthz' + assert container['livenessProbe']['httpGet']['path']=='/healthz' + + +def test_policies_and_service_selectors_do_not_include_placeholder_or_whole_namespaces(): + objects=module.render(inputs())['items'] + service=next(o for o in objects if o['kind']=='Service') + assert service['spec']['selector']['app.kubernetes.io/component']=='review' + policy=next(o for o in objects if o['kind']=='NetworkPolicy')['spec'] + for rule in policy['ingress']+policy['egress']: + for peer in rule.get('from',rule.get('to',[])): + if 'namespaceSelector' in peer:assert 'podSelector' in peer + assert policy['egress'][-1]['to']==[{'ipBlock':{'cidr':'92.205.62.239/32'}}] + assert policy['egress'][-1]['ports']==[{'protocol':'TCP','port':443}] + assert policy['egress'][-2]['to'][0]['podSelector']['matchLabels']=={'app.kubernetes.io/name':'traefik'} + assert policy['egress'][-2]['ports']==[{'protocol':'TCP','port':8443}] + peers=[o for o in objects if o['kind']=='NetworkPolicy' and o['metadata']['namespace']!='informed-decision'] + assert {o['metadata']['namespace'] for o in peers}=={'approval-engine','flex-auth'} + for obj in peers: + caller=obj['spec']['ingress'][0]['from'][0] + assert caller['namespaceSelector']['matchLabels']=={'kubernetes.io/metadata.name':'informed-decision'} + assert caller['podSelector']['matchLabels']['app.kubernetes.io/component']=='review' + + +@pytest.mark.parametrize('fault',['tag','other_image','missing_pin','external_pdp','wildcard_pod','unknown_field', + 'broad_egress','loopback','empty_ips','empty_storage','empty_package']) +def test_incomplete_or_broad_deployment_inputs_refuse(fault): + data=inputs() + if fault=='tag':data['image']='forgejo.coulomb.social/coulomb/informed-decision:latest' + if fault=='other_image':data['image']=data['image'].replace('informed-decision','other') + if fault=='missing_pin':del data['policy']['package_digest'] + if fault=='external_pdp':data['policy']['origin']='https://elsewhere.test' + if fault=='wildcard_pod':data['policy']['pod_name']='*' + if fault=='unknown_field':data['token']='not-permitted-inline' + if fault=='broad_egress':data['keycape_egress_ips']=['0.0.0.0/0'] + if fault=='loopback':data['keycape_egress_ips']=['127.0.0.1'] + if fault=='empty_ips':data['keycape_egress_ips']=[] + if fault=='empty_storage':data['storage_class']='' + if fault=='empty_package':data['policy']['package']='' + with pytest.raises(ValueError):module.render(data) + + +def test_policy_revision_changes_immutable_configuration_name(): + first=inputs();second=copy.deepcopy(first);second['policy']['version']='v2' + names=lambda data:[o['metadata']['name'] for o in module.render(data)['items'] if o['kind']=='ConfigMap'] + assert names(first)!=names(second) diff --git a/tools/render_deployment.py b/tools/render_deployment.py new file mode 100644 index 0000000..5159591 --- /dev/null +++ b/tools/render_deployment.py @@ -0,0 +1,131 @@ +"""Render a review-only Kubernetes candidate. Never apply or provision custody.""" + +import argparse +import ipaddress +import json +from pathlib import Path +import re +import hashlib + +NAME = 'informed-decision' +SHA = re.compile(r'sha256:[0-9a-f]{64}') +LABEL = {'app.kubernetes.io/name': NAME} + + +def render(inputs): + if not isinstance(inputs,dict) or set(inputs)!={'image','policy','keycape_egress_ips','storage_class'}: + raise ValueError('exact image, policy, KeyCape egress IPs and storage class inputs required') + if not re.fullmatch(r'forgejo\.coulomb\.social/coulomb/informed-decision@sha256:[0-9a-f]{64}',inputs['image']): + raise ValueError('immutable Informed Decision registry digest required') + policy=inputs['policy'] + if not isinstance(policy,dict) or set(policy)!={'origin','package','version','package_digest','pod_name'}: + raise ValueError('exact policy endpoint, package pins and pod selector required') + if not re.fullmatch(r'http://[a-z0-9-]+\.flex-auth\.svc\.cluster\.local:8080',policy['origin']): + raise ValueError('exact in-cluster Flex Auth endpoint required') + if not re.fullmatch(r'[a-z0-9][a-z0-9.-]{0,62}',policy['pod_name']): + raise ValueError('exact Flex Auth pod label required') + if not SHA.fullmatch(policy['package_digest']) or any( + not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._-]{0,127}',policy[key]) for key in ['package','version']): + raise ValueError('exact policy package/version/digest required') + if not re.fullmatch(r'[a-z0-9][a-z0-9.-]{0,62}',inputs['storage_class']): + raise ValueError('explicit storage class required') + ips=inputs['keycape_egress_ips'] + if not isinstance(ips,list) or not 1<=len(ips)<=4: + raise ValueError('one to four exact admitted KeyCape egress addresses required') + cidrs=[] + for ip in ips: + address=ipaddress.ip_address(ip) + if not address.is_global: + raise ValueError('KeyCape public origin requires exact global addresses') + cidrs.append(str(address)+'/'+str(address.max_prefixlen)) + config={'schema':'informed-decision.review-runtime.v1','evidence_db':'/data/private/review.sqlite', + 'approval_origin':'http://approval-engine.approval-engine.svc.cluster.local:8080', + 'policy':{key:policy[key] for key in ['origin','package','version','package_digest']}, + 'audit':{'origin':'http://audit-core.audit-core.svc.cluster.local:8080', + 'sender_token_file':'/var/run/secrets/informed-decision/audit/token'}} + config['policy']['caller_token_file']='/var/run/secrets/informed-decision/policy/token' + raw=json.dumps(config,sort_keys=True,separators=(',',':')) + config_name=NAME+'-runtime-'+hashlib.sha256(raw.encode()).hexdigest()[:12] + def obj(api,kind,name,**values): + return {'apiVersion':api,'kind':kind,'metadata':{'name':name,'namespace':NAME, + 'annotations':{'informed-decision.coulomb.social/admission':'review-only; INFD-WP-0001-T08'}},**values} + container_security={'allowPrivilegeEscalation':False,'readOnlyRootFilesystem':True,'capabilities':{'drop':['ALL']}} + mount=lambda name,path:{'name':name,'mountPath':path} + readonly=lambda name,path:{**mount(name,path),'readOnly':True} + probe=lambda path:{'httpGet':{'path':path,'port':'http'},'periodSeconds':10,'timeoutSeconds':2} + pod={'automountServiceAccountToken':False,'serviceAccountName':'review','terminationGracePeriodSeconds':45, + 'securityContext':{'runAsNonRoot':True,'runAsUser':10001,'runAsGroup':10001,'fsGroup':10001, + 'fsGroupChangePolicy':'OnRootMismatch','seccompProfile':{'type':'RuntimeDefault'}}, + 'containers':[{'name':NAME,'image':inputs['image'],'securityContext':container_security, + 'env':[{'name':'INFD_KEYCAPE_ISSUER','value':'https://kc.coulomb.social'}, + {'name':'INFD_CONTAINER_CONFIG','value':'/configuration/runtime.json'}], + 'ports':[{'name':'http','containerPort':8080}], + 'startupProbe':{**probe('/healthz'),'failureThreshold':12}, + # Keep read/recovery pages reachable during an audit outage. The + # application gates acceptance on /readyz internally; monitoring + # must scrape it separately. An outage must not restart the writer. + 'readinessProbe':probe('/healthz'),'livenessProbe':probe('/healthz'), + 'resources':{'requests':{'cpu':'50m','memory':'64Mi'},'limits':{'cpu':'500m','memory':'256Mi'}}, + 'volumeMounts':[mount('data','/data'),mount('runtime','/run/informed-decision'),mount('tmp','/tmp'), + readonly('configuration','/configuration'),readonly('caller','/var/run/secrets/informed-decision/policy'), + readonly('audit','/var/run/secrets/informed-decision/audit')]}], + 'volumes':[{'name':'data','persistentVolumeClaim':{'claimName':NAME+'-data'}}, + {'name':'runtime','emptyDir':{'medium':'Memory','sizeLimit':'16Mi'}}, + {'name':'tmp','emptyDir':{'medium':'Memory','sizeLimit':'16Mi'}}, + {'name':'configuration','configMap':{'name':config_name,'defaultMode':0o444}}, + {'name':'caller','projected':{'defaultMode':0o440,'sources':[{'serviceAccountToken':{ + 'path':'token','audience':'flex-auth','expirationSeconds':3600}}]}}, + {'name':'audit','secret':{'secretName':NAME+'-audit','defaultMode':0o440, + 'items':[{'key':'token','path':'token'}]}}]} + peer=lambda namespace,name:{'namespaceSelector':{'matchLabels':{'kubernetes.io/metadata.name':namespace}}, + 'podSelector':{'matchLabels':{'app.kubernetes.io/name':name}}} + http=[{'protocol':'TCP','port':8080}] + objects=[ + obj('v1','ServiceAccount','review',automountServiceAccountToken=False), + obj('v1','PersistentVolumeClaim',NAME+'-data',spec={'accessModes':['ReadWriteOnce'], + 'storageClassName':inputs['storage_class'],'resources':{'requests':{'storage':'1Gi'}}}), + obj('v1','ConfigMap',config_name,immutable=True,data={'runtime.json':raw}), + obj('apps/v1','Deployment',NAME,spec={'replicas':1,'strategy':{'type':'Recreate'},'progressDeadlineSeconds':600, + 'selector':{'matchLabels':LABEL},'template':{'metadata':{'labels':{**LABEL,'app.kubernetes.io/component':'review'}},'spec':pod}}), + obj('v1','Service',NAME,spec={'type':'ClusterIP','selector':{**LABEL,'app.kubernetes.io/component':'review'}, + 'ports':[{'name':'http','port':80,'targetPort':'http','protocol':'TCP'}]}), + obj('networking.k8s.io/v1','NetworkPolicy',NAME+'-review',spec={'podSelector':{'matchLabels':{**LABEL, + 'app.kubernetes.io/component':'review'}},'policyTypes':['Ingress','Egress'], + 'ingress':[{'from':[peer('kube-system','traefik')],'ports':http}], + 'egress':[{'to':[{'namespaceSelector':{'matchLabels':{'kubernetes.io/metadata.name':'kube-system'}}, + 'podSelector':{'matchLabels':{'k8s-app':'kube-dns'}}}], + 'ports':[{'protocol':'UDP','port':53},{'protocol':'TCP','port':53}]}, + {'to':[peer('approval-engine','approval-engine'),peer('audit-core','audit-core'), + peer('flex-auth',policy['pod_name'])],'ports':http}, + # Traefik's observed websecure target is 8443. Include + # its exact peer for CNI enforcement after Service DNAT. + {'to':[peer('kube-system','traefik')],'ports':[{'protocol':'TCP','port':8443}]}, + {'to':[{'ipBlock':{'cidr':cidr}} for cidr in cidrs],'ports':[{'protocol':'TCP','port':443}]}]})] + # Counterparty proposals travel in the review packet. Neither the existing + # Approval Engine namespace gate nor other Flex callers admits this pod. + # Audit Core already owns its prepared exact informed-decision ingress. + for namespace,name,task in [('approval-engine','approval-engine','APPROVAL-WP-0002-T01'), + ('flex-auth',policy['pod_name'],'INFD-WP-0001-T08 policy-owner return')]: + item=obj('networking.k8s.io/v1','NetworkPolicy',NAME+'-review-ingress',spec={ + 'podSelector':{'matchLabels':{'app.kubernetes.io/name':name}},'policyTypes':['Ingress'], + 'ingress':[{'from':[{'namespaceSelector':{'matchLabels':{'kubernetes.io/metadata.name':NAME}}, + 'podSelector':{'matchLabels':{**LABEL,'app.kubernetes.io/component':'review'}}}],'ports':http}]}) + item['metadata']['namespace']=namespace + item['metadata']['annotations']['informed-decision.coulomb.social/owner-review']=task + objects.append(item) + return {'apiVersion':'v1','kind':'List','items':objects} + + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument('--input',type=Path,required=True) + parser.add_argument('--output',type=Path) + args=parser.parse_args() + try: result=render(json.loads(args.input.read_text())) + except (ValueError,TypeError,KeyError,OSError) as exc:parser.error(str(exc)) + raw=json.dumps(result,indent=2)+'\n' + if args.output:args.output.write_text(raw) + else:print(raw,end='') + + +if __name__=='__main__':main() diff --git a/tools/smoke_container.py b/tools/smoke_container.py new file mode 100644 index 0000000..ebec156 --- /dev/null +++ b/tools/smoke_container.py @@ -0,0 +1,150 @@ +"""Exercise a local image, private volumes and restore without external network. + +Only synthetic records/tokens are created. No native issuer, policy, audit or +Approval Engine is contacted. Every container and volume is created by this +process, labeled, and removed in finally; the image is retained for inspection. +""" + +import argparse +import hashlib +import json +import os +from pathlib import Path +import subprocess +import tempfile +import time +import uuid + + +def main(): + parser=argparse.ArgumentParser(description=__doc__) + parser.add_argument('--image',required=True) + parser.add_argument('--receipt',type=Path,required=True) + args=parser.parse_args() + prefix='infd-proof-'+uuid.uuid4().hex[:12] + volumes=[];containers=[];checks=[] + def docker(*words,check=True): + return subprocess.run(['docker',*words],capture_output=True,text=True,check=check,timeout=60) + def command(name,code): + return docker('exec',name,'python','-c',code).stdout.strip() + def admin(name,*args): + return json.loads(docker('exec',name,'informed-decision-admin',*args).stdout) + def stop(name): + docker('stop','--time','15',name) + state=json.loads(docker('inspect',name).stdout)[0]['State'] + assert state['ExitCode']==0, 'service did not stop gracefully: '+str(state['ExitCode']) + def wait_health(name): + for _ in range(40): + result=docker('exec',name,'python','-c', + "import urllib.request; assert urllib.request.urlopen('http://127.0.0.1:8080/healthz',timeout=1).status==200",check=False) + if result.returncode==0:return + state=json.loads(docker('inspect',name).stdout)[0]['State'] + if not state['Running']:raise AssertionError('container exited before health; '+docker('logs',name).stdout) + time.sleep(.2) + raise AssertionError('container did not become healthy') + def volume(suffix): + name=prefix+'-'+suffix + docker('volume','create','--label','informed-decision.fixture='+prefix,name);volumes.append(name) + docker('run','--rm','--network','none','--read-only','--user','0:0','--cap-drop','ALL','--cap-add','CHOWN', + '--mount','type=volume,src='+name+',dst=/data','--entrypoint','python',args.image,'-c', + "import os; os.chown('/data',0,10001); os.chmod('/data',0o2770)") + return name + def start(suffix,data,fixture,backup=None): + name=prefix+'-'+suffix;containers.append(name) + words=['run','-d','--name',name,'--label','informed-decision.fixture='+prefix, + '--network','none','--read-only','--cap-drop','ALL','--security-opt','no-new-privileges', + '--tmpfs','/run/informed-decision:rw,nosuid,nodev,noexec,uid=10001,gid=10001,mode=0700', + '--tmpfs','/tmp:rw,nosuid,nodev,noexec,mode=1777', + '--mount','type=volume,src='+data+',dst=/data', + '--mount','type=bind,src='+str(fixture)+',dst=/configuration,readonly', + '--env','INFD_KEYCAPE_ISSUER=https://kc.coulomb.social'] + if backup:words+=['--mount','type=volume,src='+backup+',dst=/backup'] + docker(*words,args.image);return name + snapshot="""import hashlib,json +from informed_decision.store import Store +s=Store('/data/private/review.sqlite') +with s._connection() as db: + rows={t:[list(r) for r in db.execute('SELECT * FROM '+t+' ORDER BY rowid')] for t in ['memos','presentations','dispositions','submissions','evidence','documents']} + rows['documents']=[[r[0],r[1],hashlib.sha256(r[2]).hexdigest()] for r in rows['documents']] +print(hashlib.sha256(json.dumps(rows,sort_keys=True).encode()).hexdigest()) +""" + seed="""from informed_decision.store import Store +from informed_decision.memo import Memo,BindingSlice,Principal,Scope,BindingLevel,StepKind,PacketItem +from informed_decision.provenance import Claim,Route +from informed_decision.disposition import Actor,ActorKind,Verb +s=Store('/data/private/review.sqlite');digest=s.put_document(b'Synthetic retained content; no real approval.') +m=Memo(id='container-fixture',version=1,question='Synthetic custody exercise?',requested_act='deliver',binding_level=BindingLevel.ORGANIZATIONAL,brief='Fixture only',binding=BindingSlice(Principal('fixture-human','person','Fixture'),Scope('resource','fixture','Fixture')),step_kind=StepKind.APPROVE,packet=(PacketItem('doc','Fixture',digest),),approval_id='fixture',approval_binding_digest='sha256:'+'1'*64) +s.save_memo(m) +p=s.present(m.id,principal_sub='fixture-human',tenant=Claim('tenant:platform',Route.REGISTRATION),principal_type=Claim('human',Route.AUTHENTICATION)) +d=s.record_disposition(p.id,Actor('fixture-human',ActorKind.PERSON),Verb.ACCEPT,operation_id='container-fixture-op') +attempt=s.begin_submission(d.id);s.finish_submission(d.id,attempt) +assert s.submission(d.id)['state']=='unresolved' +""" + try: + with tempfile.TemporaryDirectory(prefix=prefix+'-') as temp: + fixture=Path(temp);fixture.chmod(0o755) + (fixture/'audit-token').write_text('synthetic-audit-token');(fixture/'caller-token').write_text('synthetic-caller-token') + config={'schema':'informed-decision.review-runtime.v1','evidence_db':'/data/private/review.sqlite', + 'approval_origin':'http://127.0.0.1:18082','policy':{'origin':'http://127.0.0.1:18083', + 'package':'container.fixture','version':'v1','package_digest':'sha256:'+'a'*64, + 'caller_token_file':'/configuration/caller-token'}, + 'audit':{'origin':'http://127.0.0.1:18084','sender_token_file':'/configuration/audit-token'}} + (fixture/'runtime.json').write_text(json.dumps(config)) + for file in fixture.iterdir():file.chmod(0o444) + data=volume('data');backup=volume('backup') + first=start('first',data,fixture,backup);wait_health(first) + checks.append('installed container entrypoint serves with projected configuration') + info=json.loads(docker('inspect',first).stdout)[0] + assert info['Config']['User']=='10001:10001' and info['HostConfig']['ReadonlyRootfs'] + assert info['HostConfig']['NetworkMode']=='none' and not info['NetworkSettings']['Ports'].get('8080/tcp') + checks.append('non-root read-only runtime has no external network or published port') + command(first,"import os,stat,importlib.util; assert os.getuid()==10001; assert importlib.util.find_spec('pip') is None; assert stat.S_IMODE(os.stat('/data/private').st_mode)==0o700; assert stat.S_IMODE(os.stat('/data/private/review.sqlite').st_mode)==0o600; assert stat.S_IMODE(os.stat('/run/informed-decision/private/runtime.json').st_mode)==0o600") + checks.append('private config/database modes enforced; package installer absent') + command(first,"import urllib.request,urllib.error\nfor path,status in [('/readyz',503),('/review?memo_id=missing',401)]:\n try: urllib.request.urlopen('http://127.0.0.1:8080'+path)\n except urllib.error.HTTPError as e: assert e.code==status\n else: raise AssertionError(path)") + checks.append('configured review refuses anonymous access and reports audit-unready status') + command(first,"from informed_decision.container import single_writer\ntry:\n with single_writer('/data/private'): pass\nexcept ValueError: pass\nelse: raise AssertionError('second writer admitted')") + checks.append('a second writer cannot acquire the active evidence volume') + command(first,seed) + state=admin(first,'inspect','--db','/data/private/review.sqlite') + assert state['submissions']=={'unresolved':1} and state['outbox'].get('pending',0)>0 + original=command(first,snapshot) + checks.append('synthetic uncertain intent and undelivered evidence remain explicit') + command(first,"from informed_decision.container import private_directory; private_directory('/backup/private')") + result=admin(first,'backup','--db','/data/private/review.sqlite','--output','/backup/private/review.sqlite') + assert result['consistent_snapshot'] is True + repeat=docker('exec',first,'informed-decision-admin','backup','--db','/data/private/review.sqlite','--output','/backup/private/review.sqlite',check=False) + assert repeat.returncode!=0 + checks.append('consistent backup created while serving; overwrite refused') + stop(first) + checks.append('SIGTERM closes the serving writer without forced kill') + docker('start',first);wait_health(first) + assert command(first,snapshot)==original + assert admin(first,'inspect','--db','/data/private/review.sqlite')['submissions']=={'unresolved':1} + checks.append('restart preserves immutable content and unresolved submission without retry') + stop(first) + restored=start('restored',backup,fixture);wait_health(restored) + assert command(restored,snapshot)==original + assert admin(restored,'inspect','--db','/data/private/review.sqlite')['submissions']=={'unresolved':1} + checks.append('separate restored volume preserves exact content and unresolved state') + stop(restored) + (fixture/'runtime.json').chmod(0o644);(fixture/'runtime.json').write_text('{}');(fixture/'runtime.json').chmod(0o444) + invalid=start('invalid',data,fixture) + code=int(docker('wait',invalid).stdout) + assert code!=0 + checks.append('incomplete owner configuration exits before serving') + image=json.loads(docker('image','inspect',args.image).stdout)[0] + receipt={'status':'passed','image_id':image['Id'],'repo_digests':image['RepoDigests'], + 'checks_passed':len(checks),'checks':checks,'fixture_only':True,'external_network':'none', + 'content_snapshot_sha256':original,'native_identity_policy_audit_proven':False, + 'published':False,'deployed':False,'factory_attempts':0,'paid_model_calls':0} + finally: + for name in reversed(containers):docker('rm','-f',name,check=False) + for name in reversed(volumes):docker('volume','rm',name,check=False) + receipt['cleanup_complete']=all(docker('inspect',name,check=False).returncode!=0 for name in containers) + receipt['cleanup_complete'] &= all(docker('volume','inspect',name,check=False).returncode!=0 for name in volumes) + assert receipt['cleanup_complete'] + args.receipt.write_text(json.dumps(receipt,indent=2)+'\n') + print(json.dumps({'status':'passed','checks_passed':len(checks),'cleanup_complete':True,'image_id':receipt['image_id']})) + + +if __name__=='__main__':main() diff --git a/workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md b/workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md index 1d5b5ee..5925ca7 100644 --- a/workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md +++ b/workplans/INFD-WP-0001-founding-specs-and-approver-ui-ownership.md @@ -623,6 +623,50 @@ not be silently claimed by the bounded factory profile. The exact operational setup and remaining limits are in `docs/protected-browser-review.md`. T08 remains `progress`; no residual has been hidden by finishing the workplan. +2026-09-11 — **container and deployment candidate prepared and exercised.** +`Containerfile` and hashed dependency lock build the installed wheel on the +digest-pinned Alpine base. The runtime has no package installer, runs as 10001 +on a read-only root filesystem, and copies bounded projected configuration into +owned ephemeral 0600 storage. It keeps private persistent evidence at 0700/0600 +and takes a process-lifetime lock before opening the serving runtime. This +preserves the store's checks despite Kubernetes projected-file ownership and +prevents a second service from sharing its evidence volume. + +The installed custody CLI creates SQLite-consistent backups and reports only +schema/delivery/submission counts. Eleven real-container checks passed with no +external network or published port: restart and restore onto a second volume +preserved exact content, undelivered evidence and a synthetic unresolved intent. +All created containers/volumes were removed. The full suite passes 371 tests +(27 added). Runtime files in the image match all 22 source modules; dependencies +match the hashed lock. Trivy returned zero HIGH/CRITICAL findings; its Alpine +lifecycle-list warning is retained in the receipt. + +The final lifecycle check exposed a real PID-1 shutdown defect: the first image +needed a forced kill (exit 137). An explicit SIGTERM handler now lets Waitress +drain and the audit pump stop; the final image exits cleanly within 15 seconds. +Recreate rollout and the 45-second pod termination window retain one writer. + +The renderer prepares a Recreate Deployment, scoped projected caller token, +immutable runtime config, PVC/Service and exact network rules. Kubernetes serving +health remains separate from audit acceptance readiness, preserving refusal/ +recovery access during an audit outage. The source now includes exact ingress +proposals for Approval Engine and the admitted PDP, because the existing caller +rules do not automatically admit this pod. No namespace-wide caller label is +added and no Secret, RBAC, policy assignment or Ingress is created. + +Seven objects passed server dry-run in their exact namespaces. The eighth, +Approval Engine ingress, found its namespace absent. Its unchanged policy shape +passed in a representative existing namespace; that is schema validation only. +Observed Traefik label/websecure port, KeyCape public IP and local-path storage +were used to make the candidate concrete, not to claim CNI or custody proof. + +Evidence: `docs/evidence/2026-09-11-container-candidate.json`; operating packet: +`deploy/README.md`. The image is local and unpublished. T08 stays `progress` for +the admitted native policy/caller/assignments, AUDIT-WP-0009-T11 custody, image +publication, owner service/namespace and exact peer admission, registration and +real human/deployed binding, platform backup/restore and product acceptance. +No cluster apply, native secret read, factory attempt or paid call occurred. + ## Known risks - **T02 is a hard gate.** Writing the blueprint before the layer ruling risks