code-sandboxes 1.9.4__tar.gz → 1.9.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/CHANGELOG.md +42 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/PKG-INFO +1 -1
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/__version__.py +1 -1
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/adapters/datalayer.py +14 -4
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/attest.py +13 -3
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/bases.py +38 -4
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/image_import.py +89 -39
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/policy.py +22 -1
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/resolve.py +108 -22
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_attest.py +48 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_bases.py +25 -2
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_datalayer_builder.py +11 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_image_import.py +85 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_resolve.py +101 -6
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.github/workflows/build.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.github/workflows/environments-live.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.github/workflows/py-code-style.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.github/workflows/py-tests.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.github/workflows/py-typing.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.github/workflows/reusable-python.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.gitignore +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.licenserc.yaml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.pre-commit-config.yaml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/.vscode/settings.json +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/Dockerfile +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/LICENSE +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/Makefile +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/README.md +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/build.log +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/__init__.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/base.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/bridge_mount.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/builds.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/cli.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/client.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/cloudflare_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/commands.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/console.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/contents.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/coreweave_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/daytona_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/docker_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/e2b_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/__init__.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/__main__.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/accounts.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/adapters/__init__.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/adapters/daytona.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/adapters/e2b.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/adapters/managed.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/adapters/modal.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/breaker.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/build_secrets.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/builders.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/canonical.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/conformance.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/constraints/sandbox-contract-v1.txt +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/constraints/wheelhouse/README.md +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/constraints/wheelhouse/jupyter_server-2.21.0+datalayer.1-py3-none-any.whl +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/contract.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/doctor/__init__.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/doctor/build.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/doctor/datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/doctor/report.schema.json +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/errors.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/files.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/lifecycle.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/redact.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/schema.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/spec.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/eval_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/exceptions.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/filesystem.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/google_colab.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/google_colab_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/interfaces.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/jupyter_ingress.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/jupyter_server_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/kaggle.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/kaggle_execute.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/kaggle_live.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/kaggle_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/lifecycle.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/manage.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/modal_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/models.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/monty_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/provider_ingress.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/providers.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docker/Dockerfile +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/.gitignore +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/LICENSE +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/Makefile +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/README.md +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/babel.config.js +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/api-reference/_category_.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/api-reference/index.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/cli/_category_.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/cli/index.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/contribute/_category_.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/contribute/index.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/environments/_category_.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/environments/contract.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/environments/index.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/environments/specification.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/environments/spike-2026-09.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/examples/_category_.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/examples/index.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/guide/_category_.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/guide/index.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/index.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/install/_category_.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/install/index.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/provider-ingress.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/_category_.yml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/cloudflare.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/coreweave.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/datalayer.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/daytona.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/docker.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/e2b.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/eval.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/google-colab.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/index.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/jupyter-server.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/kaggle.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/modal.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docs/providers/monty.mdx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/docusaurus.config.js +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/package.json +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/sidebars.js +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/components/HomepageRow1.js +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/components/HomepageRow1.module.css +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/components/HomepageRow2.js +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/components/HomepageRow2.module.css +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/components/HomepageRow3.js +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/components/HomepageRow3.module.css +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/css/custom.css +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/pages/index.module.css +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/pages/markdown-page.md +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/theme/CustomDocItem.tsx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/src/theme/IconsTitle.tsx +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/bricks.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/building-construction.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/cloud-sun.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/cloud.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/collaboration.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/copy-left.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/datalayer/logo.png +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/datalayer/logo.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/datalayer/personas/business.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/datalayer/personas/data-scientist.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/datalayer/personas/devops.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/datalayer/project/project-line.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/favicon.ico +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/jupyter.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/open-source.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/react-js.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/react-js2.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/ringed-planet.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/robot.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/rocket.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/shield.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/sparkles.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/streamlit.gif +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/super-hero.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/target.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/docs/static/img/thumbs-up.svg +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/README.md +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/Makefile +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/exec/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/Makefile +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/cloudflare_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/coreweave_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/datalayer_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/daytona_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/docker_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/e2b_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/eval_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/google_colab_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/jupyter_server_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/kaggle_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/modal_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/examples/repl/monty_sandbox_example.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/pyproject.toml +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/schemas/environment-v1alpha1.json +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/__init__.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/conftest.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_a_datalayer_sandbox_can_be_interrupted.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_bridge_mount.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_builds.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_cli_exec.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_cli_repl.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_client.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_client_contents.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_client_files.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_client_outcome_error_output.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_cloudflare.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_commands_envelope.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_console.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_contents_contract.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_contents_live_matrix.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_coreweave.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_datalayer_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_daytona.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_e2b.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_accounts.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_breaker.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_build_secrets.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_builders.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_canonical.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_conformance.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_constraints.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_contract.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_cross_variant_report.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_daytona_builder.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_doctor.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_e2b_builder.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_errors.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_lifecycle.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_live_matrix.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_managed_builders.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_modal_builder.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_redact.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_environment_spec.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_eval.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_every_variant_has_a_real_interrupt.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_factory.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_google_colab.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_integration.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_jupyter_ingress.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_jupyter_server.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_jupyter_server_streaming.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_kaggle.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_kaggle_execute.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_kaggle_live.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_kaggle_session.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_kernel_client_compatibility.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_lifecycle_vocabulary.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_manage.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_manage_datalayer.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_modal_google_colab_sandbox.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_modal_session.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_models.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_output_line_fidelity.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_provider_catalog_names.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_provider_ingress.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_providers.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_sandboxes.py +0 -0
- {code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/tests/test_the_kill_relaunch_drill.py +0 -0
|
@@ -8,6 +8,48 @@
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 1.9.6
|
|
12
|
+
|
|
13
|
+
- **`datalayer/python-cpu`'s `2026.09` channel points at the patched base**
|
|
14
|
+
(`environments.bases`; PLAN_ENV.md E1-05, E1-08). `jupyter-python` 0.2.2
|
|
15
|
+
upgrades the Ubuntu security packages and conda's own OpenSSL, and drops
|
|
16
|
+
JupyterLab's staging `yarn.lock` — the 31 fixable-critical findings that
|
|
17
|
+
blocked every build from the prior digest under the default scan policy.
|
|
18
|
+
|
|
19
|
+
## 1.9.5
|
|
20
|
+
|
|
21
|
+
- **Attest waits for a just-pushed image's scan to exist**
|
|
22
|
+
(`environments.attest`; PLAN_ENV.md E1-08). Enhanced scanning starts after
|
|
23
|
+
the push, so the first answers for a new image are
|
|
24
|
+
`ScanNotFoundException`. Attest read that as an image with no scan and
|
|
25
|
+
failed the build at once (found live on r1, 2026-09-14). It is now waited
|
|
26
|
+
for like a running scan, within the same bound.
|
|
27
|
+
- **apt is pinned at, and installed from, the base channel's Ubuntu snapshot**
|
|
28
|
+
(`environments.bases`, `environments.resolve`,
|
|
29
|
+
`environments.adapters.datalayer`; PLAN_ENV.md E1-04). A channel records
|
|
30
|
+
the `snapshot.ubuntu.com` id its image was upgraded at. The solve runs
|
|
31
|
+
`apt-get` with `--snapshot` at that id, the lock records it
|
|
32
|
+
(`# datalayer-apt-snapshot:`), and the Datalayer builder installs its pins
|
|
33
|
+
from the same snapshot. This replaces a `deb` line that named only `main`
|
|
34
|
+
(`gdal-bin` is in `universe`) and left the live mirror enabled beside it.
|
|
35
|
+
`DATALAYER_APT_SNAPSHOT` must now be a snapshot id.
|
|
36
|
+
- **A private registry's credential is resolved and used** (`environments
|
|
37
|
+
.image_import`, `environments.resolve`; PLAN_ENV.md E3-04's second half).
|
|
38
|
+
`image.credentialSecretId` used to only clear the allowlist check; nothing
|
|
39
|
+
fetched or used the credential it named. It is now fetched through the
|
|
40
|
+
same IAM route a build secret is (E3-05), and sent as HTTP Basic on the
|
|
41
|
+
registry's own token exchange — the way `docker login` authenticates a
|
|
42
|
+
private pull — never to the registry named in the reference, and never
|
|
43
|
+
logged. `resolve_environment` and `resolve_image_base` take `owner_uid`
|
|
44
|
+
and `resolve_secret` to reach it; unused by a public registry or any other
|
|
45
|
+
source.
|
|
46
|
+
- **The Datalayer base channel's own five ESM-locked advisories are allowed**
|
|
47
|
+
(`environments.policy`; PLAN_ENV.md E1-08). Amazon Inspector marks all five
|
|
48
|
+
fixable, but the fix is an Ubuntu ESM package version a plain
|
|
49
|
+
`apt-get upgrade` cannot reach. Reviewed and added to `DEFAULT_POLICY
|
|
50
|
+
.allowed`, 2026-09-14, so they are recorded as allowed rather than missed,
|
|
51
|
+
and any other critical still blocks.
|
|
52
|
+
|
|
11
53
|
## 1.9.4
|
|
12
54
|
|
|
13
55
|
- **A pushed Environment image is decided by its linux/amd64 image's scan**
|
{code_sandboxes-1.9.4 → code_sandboxes-1.9.6}/code_sandboxes/environments/adapters/datalayer.py
RENAMED
|
@@ -68,7 +68,13 @@ from ..errors import (
|
|
|
68
68
|
EnvironmentsError,
|
|
69
69
|
)
|
|
70
70
|
from ..files import files_step
|
|
71
|
-
from ..resolve import
|
|
71
|
+
from ..resolve import (
|
|
72
|
+
WHEELHOUSE_IMAGE_PATH,
|
|
73
|
+
WHEELHOUSE_PATH,
|
|
74
|
+
apt_pins_in,
|
|
75
|
+
apt_snapshot_in,
|
|
76
|
+
locked_versions,
|
|
77
|
+
)
|
|
72
78
|
from ..spec import BuildSecret, Environment, command_names_secret
|
|
73
79
|
|
|
74
80
|
__all__ = [
|
|
@@ -310,14 +316,18 @@ class Builder:
|
|
|
310
316
|
lines.append(f"ENV {name}={shlex.quote(spec.env[name])}")
|
|
311
317
|
if apt:
|
|
312
318
|
# Pinned, and from the lock: `apt-get install <name>` a month later
|
|
313
|
-
# is a different artifact.
|
|
319
|
+
# is a different artifact. From the snapshot the pins were taken
|
|
320
|
+
# from, when the lock names one: a pinned version can leave the
|
|
321
|
+
# live mirror (D-9).
|
|
314
322
|
pinned = " ".join(f"{name}={apt[name]}" for name in sorted(apt))
|
|
323
|
+
snapshot = apt_snapshot_in(request.lock_text)
|
|
324
|
+
option = f" --snapshot {snapshot}" if snapshot else ""
|
|
315
325
|
lines.extend(
|
|
316
326
|
[
|
|
317
327
|
"USER root",
|
|
318
328
|
"RUN --mount=type=cache,target=/var/cache/apt,sharing=locked "
|
|
319
|
-
"apt-get update -qq \\\n"
|
|
320
|
-
f" && apt-get install -y --no-install-recommends {pinned} \\\n"
|
|
329
|
+
f"apt-get update -qq{option} \\\n"
|
|
330
|
+
f" && apt-get install -y --no-install-recommends{option} {pinned} \\\n"
|
|
321
331
|
" && rm -rf /var/lib/apt/lists/*",
|
|
322
332
|
]
|
|
323
333
|
)
|
|
@@ -530,10 +530,20 @@ def _is_missing(error: BaseException) -> bool:
|
|
|
530
530
|
return _error_code(error) in {
|
|
531
531
|
"ImageNotFoundException",
|
|
532
532
|
"RepositoryNotFoundException",
|
|
533
|
-
"ScanNotFoundException",
|
|
534
533
|
}
|
|
535
534
|
|
|
536
535
|
|
|
537
536
|
def _is_in_progress(error: BaseException) -> bool:
|
|
538
|
-
"""ECR answers a scan that has not finished as an error, not a status.
|
|
539
|
-
|
|
537
|
+
"""ECR answers a scan that has not finished as an error, not a status.
|
|
538
|
+
|
|
539
|
+
`ScanNotFoundException` is one of those answers: enhanced scanning starts
|
|
540
|
+
after the push, so an image pushed a moment ago has no scan yet (found
|
|
541
|
+
live on r1, 2026-09-14, the first build to read its image's scan rather
|
|
542
|
+
than its index's). It is waited for like a running scan, within the same
|
|
543
|
+
bound.
|
|
544
|
+
"""
|
|
545
|
+
return _error_code(error) in {
|
|
546
|
+
"ScanInProgressException",
|
|
547
|
+
"ScanNotFoundException",
|
|
548
|
+
"LimitExceededException",
|
|
549
|
+
}
|
|
@@ -36,6 +36,7 @@ __all__ = [
|
|
|
36
36
|
"BaseChannelUnpublishedError",
|
|
37
37
|
"approved_base",
|
|
38
38
|
"approved_repositories",
|
|
39
|
+
"channel_snapshot",
|
|
39
40
|
"is_approved_repository",
|
|
40
41
|
"resolve_base",
|
|
41
42
|
]
|
|
@@ -44,6 +45,8 @@ __all__ = [
|
|
|
44
45
|
ECR_BASE_PREFIX = "environments/base/"
|
|
45
46
|
|
|
46
47
|
_DIGEST = re.compile(r"sha256:[0-9a-f]{64}")
|
|
48
|
+
#: A snapshot.ubuntu.com id: the moment, to the second, in UTC.
|
|
49
|
+
_SNAPSHOT = re.compile(r"\d{8}T\d{6}Z")
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
class ApprovedBase(BaseModel):
|
|
@@ -59,6 +62,11 @@ class ApprovedBase(BaseModel):
|
|
|
59
62
|
#: Channel, then variant, to the digest resolution pins. A channel with no
|
|
60
63
|
#: variant is approved and not yet published.
|
|
61
64
|
channels: dict[str, dict[str, str]] = Field(default_factory=dict)
|
|
65
|
+
#: Channel to the Ubuntu snapshot its apt versions are pinned against
|
|
66
|
+
#: (D-9): a ``snapshot.ubuntu.com`` id, taken just after the channel's
|
|
67
|
+
#: image upgraded its packages, so no pin is older than what the image
|
|
68
|
+
#: already carries. A channel with none pins against the base's mirror.
|
|
69
|
+
snapshots: dict[str, str] = Field(default_factory=dict)
|
|
62
70
|
|
|
63
71
|
@field_validator("channels")
|
|
64
72
|
@classmethod
|
|
@@ -75,6 +83,17 @@ class ApprovedBase(BaseModel):
|
|
|
75
83
|
for channel, digests in channels.items()
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
@field_validator("snapshots")
|
|
87
|
+
@classmethod
|
|
88
|
+
def _snapshot_ids_only(cls, snapshots: dict[str, str]) -> dict[str, str]:
|
|
89
|
+
for channel, snapshot in snapshots.items():
|
|
90
|
+
if not _SNAPSHOT.fullmatch(snapshot):
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f"channel {channel!r} pins apt to {snapshot!r}, "
|
|
93
|
+
"which is not a snapshot id like 20260914T150000Z"
|
|
94
|
+
)
|
|
95
|
+
return snapshots
|
|
96
|
+
|
|
78
97
|
@property
|
|
79
98
|
def name(self) -> str:
|
|
80
99
|
"""The base's name without its namespace: ``python-cpu``."""
|
|
@@ -89,9 +108,15 @@ class ApprovedBase(BaseModel):
|
|
|
89
108
|
APPROVED_BASES: dict[str, ApprovedBase] = {
|
|
90
109
|
base.ref: base
|
|
91
110
|
for base in (
|
|
92
|
-
# E1-05: jupyter-python:0.2.
|
|
93
|
-
#
|
|
94
|
-
#
|
|
111
|
+
# E1-05: jupyter-python:0.2.2 (Ubuntu security packages and conda's own
|
|
112
|
+
# OpenSSL upgraded, JupyterLab's staging yarn.lock dropped) plus the
|
|
113
|
+
# contract layer, released 2026-09-14 to environments/base/python-cpu.
|
|
114
|
+
# One image, so every variant pins the same digest until a variant
|
|
115
|
+
# needs a base of its own. The prior digest, jupyter-python:0.2.1,
|
|
116
|
+
# carried 31 fixable-critical findings the scan (E1-08) blocks every
|
|
117
|
+
# build on; this one carries none the default policy does not already
|
|
118
|
+
# name (`policy.py`'s own `_BASE_CHANNEL_ALLOWED`, five ESM-locked
|
|
119
|
+
# ffmpeg/libcjson1 advisories, reviewed the same day).
|
|
95
120
|
ApprovedBase(
|
|
96
121
|
ref="datalayer/python-cpu",
|
|
97
122
|
python_versions=("3.13",),
|
|
@@ -100,9 +125,10 @@ APPROVED_BASES: dict[str, ApprovedBase] = {
|
|
|
100
125
|
# `.spec.VARIANTS`, spelled out: `spec` imports from this
|
|
101
126
|
# module, so importing it back here would be circular.
|
|
102
127
|
("datalayer", "e2b", "daytona", "modal"),
|
|
103
|
-
"sha256:
|
|
128
|
+
"sha256:334adf6c2714c8919ef60beeca1db12e3531a391c9dde41932c782f81c432b36",
|
|
104
129
|
)
|
|
105
130
|
},
|
|
131
|
+
snapshots={"2026.09": "20260914T150000Z"},
|
|
106
132
|
),
|
|
107
133
|
# E2-17: jupyter-python-cuda plus the same layer.
|
|
108
134
|
ApprovedBase(
|
|
@@ -180,6 +206,14 @@ def resolve_base(
|
|
|
180
206
|
return digest
|
|
181
207
|
|
|
182
208
|
|
|
209
|
+
def channel_snapshot(
|
|
210
|
+
ref: str, channel: str, bases: dict[str, ApprovedBase] = APPROVED_BASES
|
|
211
|
+
) -> str:
|
|
212
|
+
"""The Ubuntu snapshot ``ref`` at ``channel`` pins apt against, or ``""`` for none (D-9)."""
|
|
213
|
+
base = bases.get(ref)
|
|
214
|
+
return "" if base is None else base.snapshots.get(channel, "")
|
|
215
|
+
|
|
216
|
+
|
|
183
217
|
def approved_repositories(bases: dict[str, ApprovedBase] = APPROVED_BASES) -> tuple[str, ...]:
|
|
184
218
|
"""Every repository name an approved base is published under."""
|
|
185
219
|
names: list[str] = []
|
|
@@ -13,20 +13,26 @@ path. Nothing downstream needs to know a build started from an import rather
|
|
|
13
13
|
than an approved base; the only difference is where the `FROM` digest comes
|
|
14
14
|
from.
|
|
15
15
|
|
|
16
|
-
**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
**A public registry needs no credential; a private one names its own**
|
|
17
|
+
(E3-04's second half). `credentialSecretId` on the spec (the same `dlsec_...`
|
|
18
|
+
shape a `BuildSecret` uses) is resolved through the same IAM route a build
|
|
19
|
+
secret is (E3-05) — the value is fetched once, at the moment the digest is
|
|
20
|
+
resolved, and handed to the registry's own token exchange as HTTP Basic,
|
|
21
|
+
never logged, never stored, never sent to the registry named in the
|
|
22
|
+
reference. A per-organization allowlist is still E3-06's own organization
|
|
23
|
+
policy document to hold; `DEFAULT_ALLOWED_REGISTRIES` below is a bootstrap
|
|
24
|
+
standing in for it before it exists, not a policy of its own — a registry
|
|
25
|
+
outside this list is refused unless a credential was referenced for it, on
|
|
26
|
+
the read that naming one is a deliberate private registry rather than a
|
|
27
|
+
mistake. When E3-06 lands, an organization's own list replaces this one
|
|
28
|
+
rather than adding to it.
|
|
24
29
|
|
|
25
30
|
@module code_sandboxes.environments.image_import
|
|
26
31
|
"""
|
|
27
32
|
|
|
28
33
|
from __future__ import annotations
|
|
29
34
|
|
|
35
|
+
import base64
|
|
30
36
|
import hashlib
|
|
31
37
|
import re
|
|
32
38
|
from dataclasses import dataclass
|
|
@@ -175,10 +181,11 @@ def refuse_unless_allowed(
|
|
|
175
181
|
A registry named alongside `credentialSecretId` (E3-04's private half) is
|
|
176
182
|
presumed a deliberate private one, its governance E3-06's own allowlist
|
|
177
183
|
to hold once it exists — this bootstrap list is only ever the *public*
|
|
178
|
-
default, never the only door.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
default, never the only door. The credential itself is resolved and used
|
|
185
|
+
at `resolve_image_digest` (E3-05's mechanism); a spec that clears this
|
|
186
|
+
check but names a secret IAM will not give, or that the registry refuses,
|
|
187
|
+
fails honestly there instead, the same as an approved base with no digest
|
|
188
|
+
published.
|
|
182
189
|
"""
|
|
183
190
|
if has_credential or image_registry_allowed(image, allowlist):
|
|
184
191
|
return
|
|
@@ -199,21 +206,68 @@ def _challenge(header: str) -> dict[str, str]:
|
|
|
199
206
|
return dict(_CHALLENGE_FIELD.findall(header or ""))
|
|
200
207
|
|
|
201
208
|
|
|
209
|
+
def _token_for_challenge(client: Any, header: str, *, registry: str, credential: str | None) -> str:
|
|
210
|
+
"""The pull token a `WWW-Authenticate: Bearer ...` challenge answers with.
|
|
211
|
+
|
|
212
|
+
With a `credential` (`username:password`), sent as HTTP Basic on this
|
|
213
|
+
request alone — exactly what `docker login` sends here — so the realm
|
|
214
|
+
mints a token scoped to what that credential can pull; with none, the
|
|
215
|
+
same request answers with a public, anonymous-scope token. Either way,
|
|
216
|
+
the credential itself never reaches the registry named in the reference,
|
|
217
|
+
only this one realm, and only as this one request's header.
|
|
218
|
+
"""
|
|
219
|
+
challenge = _challenge(header)
|
|
220
|
+
realm = challenge.get("realm")
|
|
221
|
+
if not realm:
|
|
222
|
+
raise EnvironmentsError(
|
|
223
|
+
PROVIDER_ERROR,
|
|
224
|
+
f"{registry} refused a manifest request with no way to ask for a token",
|
|
225
|
+
detail={"registry": registry, "status": 401},
|
|
226
|
+
)
|
|
227
|
+
headers = {}
|
|
228
|
+
if credential:
|
|
229
|
+
encoded = base64.b64encode(credential.encode("utf-8")).decode("ascii")
|
|
230
|
+
headers["Authorization"] = f"Basic {encoded}"
|
|
231
|
+
response = client.get(
|
|
232
|
+
realm,
|
|
233
|
+
params={
|
|
234
|
+
key: value
|
|
235
|
+
for key, value in (
|
|
236
|
+
("service", challenge.get("service")),
|
|
237
|
+
("scope", challenge.get("scope")),
|
|
238
|
+
)
|
|
239
|
+
if value
|
|
240
|
+
},
|
|
241
|
+
headers=headers,
|
|
242
|
+
)
|
|
243
|
+
response.raise_for_status()
|
|
244
|
+
return str(response.json().get("token") or "")
|
|
245
|
+
|
|
246
|
+
|
|
202
247
|
def resolve_image_digest(
|
|
203
248
|
image: ImageReference,
|
|
204
249
|
*,
|
|
205
250
|
transport: Any = None,
|
|
206
251
|
timeout: float = 10.0,
|
|
252
|
+
credential: str | None = None,
|
|
207
253
|
) -> str:
|
|
208
|
-
"""The reference's digest, over the registry's own v2 API
|
|
209
|
-
since only a public registry reaches this (§ this module's own docstring).
|
|
254
|
+
"""The reference's digest, over the registry's own v2 API.
|
|
210
255
|
|
|
211
256
|
Already pinned by digest, this is free: an operator who wrote `@sha256:…`
|
|
212
257
|
meant exactly that image, and nothing is asked of the registry. A tag is
|
|
213
|
-
resolved with one
|
|
214
|
-
`WWW-Authenticate` challenge for the
|
|
215
|
-
|
|
216
|
-
|
|
258
|
+
resolved with one manifest request, following the registry's own
|
|
259
|
+
`WWW-Authenticate` challenge for the pull token every registry implementing
|
|
260
|
+
the distribution spec answers with (D-9's base digests are pinned the same
|
|
261
|
+
way, just from a table Datalayer keeps instead of asked live).
|
|
262
|
+
|
|
263
|
+
With no `credential`, the challenge is answered anonymously — the only
|
|
264
|
+
path a public registry needs. With one, `username:password` (E3-04's
|
|
265
|
+
private half), it is sent as HTTP Basic on the token request the challenge
|
|
266
|
+
names, exactly what `docker login` sends there: the registry mints a
|
|
267
|
+
token scoped to what that credential can pull, private repositories
|
|
268
|
+
included, rather than the public-only anonymous one. The credential itself
|
|
269
|
+
is never sent to the registry named in the reference, and never appears in
|
|
270
|
+
a log line or an exception message — only the token that answers it.
|
|
217
271
|
"""
|
|
218
272
|
if image.pinned:
|
|
219
273
|
return image.digest
|
|
@@ -234,28 +288,12 @@ def resolve_image_digest(
|
|
|
234
288
|
try:
|
|
235
289
|
response = client.get(url, headers=headers)
|
|
236
290
|
if response.status_code == 401:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
f"{image.registry} refused an anonymous manifest request with no "
|
|
243
|
-
"way to ask for a token",
|
|
244
|
-
detail={"registry": image.registry, "status": 401},
|
|
245
|
-
)
|
|
246
|
-
token_response = client.get(
|
|
247
|
-
realm,
|
|
248
|
-
params={
|
|
249
|
-
key: value
|
|
250
|
-
for key, value in (
|
|
251
|
-
("service", challenge.get("service")),
|
|
252
|
-
("scope", challenge.get("scope")),
|
|
253
|
-
)
|
|
254
|
-
if value
|
|
255
|
-
},
|
|
291
|
+
token = _token_for_challenge(
|
|
292
|
+
client,
|
|
293
|
+
response.headers.get("www-authenticate", ""),
|
|
294
|
+
registry=image.registry,
|
|
295
|
+
credential=credential,
|
|
256
296
|
)
|
|
257
|
-
token_response.raise_for_status()
|
|
258
|
-
token = str(token_response.json().get("token") or "")
|
|
259
297
|
response = client.get(url, headers={**headers, "Authorization": f"Bearer {token}"})
|
|
260
298
|
except httpx.HTTPError as error:
|
|
261
299
|
raise EnvironmentsError(
|
|
@@ -263,6 +301,18 @@ def resolve_image_digest(
|
|
|
263
301
|
f"{image.registry} could not be reached to resolve {image}: {error}",
|
|
264
302
|
detail={"registry": image.registry, "reference": str(image)},
|
|
265
303
|
) from error
|
|
304
|
+
if response.status_code in (401, 403):
|
|
305
|
+
raise EnvironmentsError(
|
|
306
|
+
PROVIDER_ERROR,
|
|
307
|
+
f"{image.registry} refused the credential for {image}"
|
|
308
|
+
if credential
|
|
309
|
+
else f"{image} needs a credential to resolve at {image.registry}",
|
|
310
|
+
detail={
|
|
311
|
+
"registry": image.registry,
|
|
312
|
+
"reference": str(image),
|
|
313
|
+
"status": response.status_code,
|
|
314
|
+
},
|
|
315
|
+
)
|
|
266
316
|
if response.status_code == 404:
|
|
267
317
|
raise EnvironmentsError(
|
|
268
318
|
PROVIDER_ERROR,
|
|
@@ -129,8 +129,29 @@ class ScanPolicy:
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
|
|
132
|
+
#: Advisories allowed on the Datalayer base channel itself (D-9), reviewed and
|
|
133
|
+
#: accepted 2026-09-14 rather than silently missed: all five are in `ffmpeg`
|
|
134
|
+
#: (`CVE-2024-35366`, `CVE-2024-35367`, `CVE-2024-35368`, `CVE-2026-40962`) and
|
|
135
|
+
#: `libcjson1` (`CVE-2025-57052`, pulled in only by `librist4`, itself only
|
|
136
|
+
#: needed by `ffmpeg`), on `datalayer/python-cpu:2026.09`. Amazon Inspector
|
|
137
|
+
#: reports each as fixable, but the fix is an Ubuntu ESM (Extended Security
|
|
138
|
+
#: Maintenance) package version — `7:6.1.1-3ubuntu5+esm5` and the like — not
|
|
139
|
+
#: reachable by a plain `apt-get upgrade`, only by a Pro subscription token
|
|
140
|
+
#: this deployment does not hold. `ffmpeg` has no other installed package
|
|
141
|
+
#: depending on it (`apt-cache rdepends --installed`), so it is kept for what
|
|
142
|
+
#: it is: an image-wide convenience (matplotlib's `FFMpegWriter` and the like),
|
|
143
|
+
#: not something the sandbox contract's own doctor checks for. Revisit once
|
|
144
|
+
#: Ubuntu ships a non-ESM fix, or the channel drops `ffmpeg`.
|
|
145
|
+
_BASE_CHANNEL_ALLOWED: tuple[str, ...] = (
|
|
146
|
+
"CVE-2024-35366",
|
|
147
|
+
"CVE-2024-35367",
|
|
148
|
+
"CVE-2024-35368",
|
|
149
|
+
"CVE-2026-40962",
|
|
150
|
+
"CVE-2025-57052",
|
|
151
|
+
)
|
|
152
|
+
|
|
132
153
|
#: What every owner is scanned against until an organization says otherwise.
|
|
133
|
-
DEFAULT_POLICY = ScanPolicy()
|
|
154
|
+
DEFAULT_POLICY = ScanPolicy(allowed=_BASE_CHANNEL_ALLOWED)
|
|
134
155
|
|
|
135
156
|
|
|
136
157
|
@dataclass(frozen=True)
|