From 2abcff246ce147b9d46ba8923e55a9f1854aad6c Mon Sep 17 00:00:00 2001 From: wayne Date: Tue, 20 Oct 2026 17:11:15 +0800 Subject: [PATCH 1/2] feat(sdk): support explicit HostPath bind mounts Add validated host_path and read_only mount fields for prepared node paths, with read-only defaults and unchanged Image/S3 permissions. Adapt the pinned native Mount serialization hook while retaining its constructor, scheduling and structured permission errors. Cover request encoding, source/mode rejection and mixed mounts, and document the trusted-management boundary and node visibility setup. Server-side authorization remains tracked separately in #110. Signed-off-by: wayne --- AGENTS.md | 9 +++ sdk/python/README.md | 56 ++++++++++++++++- sdk/python/akernel_sdk/_backends/adx.py | 76 +++++++++++++++++------ sdk/python/akernel_sdk/sandbox.py | 4 +- sdk/python/akernel_sdk/types.py | 43 +++++++++++-- sdk/python/examples/hostpath_mounts.py | 57 +++++++++++++++++ sdk/python/tests/unit/test_adx_backend.py | 64 +++++++++++++++++++ sdk/python/tests/unit/test_types.py | 69 ++++++++++++++++++++ 8 files changed, 351 insertions(+), 27 deletions(-) create mode 100644 sdk/python/examples/hostpath_mounts.py diff --git a/AGENTS.md b/AGENTS.md index b2bcd176..60cd32dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -388,6 +388,15 @@ quota or cluster default. A positive limit without an explicit scheduling quota also reserves that amount for scheduling. Keep this distinction in SDK documentation and examples; see `sdk/python/README.md` for runtime support. +`Mount(host_path=...)` binds an existing path visible to the selected node's +runtime. HostPath mounts default to read-only; writable binds require +`read_only=False`. Image/S3 mounts remain read-only. Keep the pinned ADX SDK's +HostPath serialization adapter under `_backends`, and preserve server-side +permission errors. This interface is for trusted management callers: client +validation is not source-path authorization, and #110 remains unresolved. +The SDK does not create host directories, mount node storage, or replicate +node-local data. See the SDK guide and `sdk/python/examples/hostpath_mounts.py`. + Same-node failover and explicit rollback retain the logical sandbox identity: ```python diff --git a/sdk/python/README.md b/sdk/python/README.md index b0830fa0..010fcc1c 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -701,6 +701,59 @@ mount = Mount(target="/opt/tools", image_url="ubuntu:24.04") Firecracker mounts must instead use `type="erofs"` with an S3 object containing a raw EROFS image; it rejects `image_url` and directory-backed mounts. +### Node HostPath bind mounts + +HostPath support requires an SDK revision containing this change; it is not +included in the published `akernel-sdk==0.2.0rc2` package. + +Trusted management clients can bind an existing node-visible path into a +sandbox. For example, expose a prepared log directory on a selected runsc node: + +```python +from akernel_sdk import Mount, Sandbox + +logs = Mount( + target="/apps/logs", # Path inside the sandbox. + host_path="/apps/logs", # Path visible to sandboxd on the selected node. + read_only=False, # Explicitly allow application log writes. +) +with Sandbox(node_id="", mounts=[logs]) as sandbox: + print(sandbox.commands.run("ls -ld /apps/logs").stdout) +``` + +`read_only=True` is the default. HostPath supports `type="bind"` only; +`read_only=False` is rejected for Image/S3 mounts, which remain read-only. +Exactly one of `image_url`, `s3_config`, or `host_path` is required. The source +must be an absolute POSIX path without traversal components or control +characters. The SDK does not inspect the client machine's filesystem or create +the source directory; it sends `create_if_missing=false`. An absent source or +insufficient filesystem permissions is a node-side error. + +In Kubernetes, the node Pod must first see the source, for example through an +operator-managed `hostPath` volume and matching `volumeMount`. An ECS directory +does not become visible to sandboxd merely because its path is in the SDK +request. Directory ownership and permissions must allow the sandbox's process +UID/GID to use it. Separate workers' local directories are separate data; +`node_id` selects the prepared node but does not replicate storage. Data written +through a bind can outlive the sandbox. Runtime-specific mount restrictions +still apply; Firecracker does not support these directory-backed binds. + +**Authorization boundary:** this client API does not authorize node paths. The +current ADX server does not enforce per-tenant HostPath authorization; see +[#110](https://github.com/inclusionAI/AKernel/issues/110). Use only trusted +management credentials and node paths controlled by the operator. SDK path +validation and read-only defaults do not resolve #110. Server permission errors +are returned through the normal `BackendOperationError` fields. + +The maintained example requires an explicit node and source. It lists the +read-only mount by default; `--write` creates one uniquely named log file and +leaves that file on the node: + +```bash +python sdk/python/examples/hostpath_mounts.py \ + --node-id --host-path /apps/logs --write +``` + ## Launch from a Dockerfile Dockerfile direct launch is a supported AKernel SDK capability and will remain @@ -816,6 +869,7 @@ Maintained examples are under [`examples/`](./examples): - `dockerfile_launch.py` - `failover_reload.py` - `gpu_sandbox.py` +- `hostpath_mounts.py` - `named_sandbox.py` - `network_policy.py` - `pty.py` @@ -860,7 +914,7 @@ not part of the default test suite. | `SandboxInfo` | `id`, `state`, `cpu`, `memory`, `image`, `xpu`, `storage_mb` | | `NodeInfo` | `id`, `status`, `capacity`, `allocatable`, `labels` | | `S3Config` | `endpoint`, `bucket`, `object`, optional credentials | -| `Mount` | `target`, one source, and `type` | +| `Mount` | `target`, exactly one of `image_url` / `s3_config` / `host_path`, `type`, `read_only` (default `True`; writable only for HostPath) | | `HttpReverseTunnel` | `target`, `reverse_port`, `listen_port`, `connect_timeout` | | `PortRange` | `first`, `last` | | `NetworkRule` | action, direction, protocol, peer and sandbox port selectors, priority | diff --git a/sdk/python/akernel_sdk/_backends/adx.py b/sdk/python/akernel_sdk/_backends/adx.py index fcb34027..60db70de 100644 --- a/sdk/python/akernel_sdk/_backends/adx.py +++ b/sdk/python/akernel_sdk/_backends/adx.py @@ -19,6 +19,7 @@ import logging import time from collections.abc import Mapping +from dataclasses import dataclass from typing import Any import adx_environment @@ -29,6 +30,7 @@ DNSPolicy, DNSRule, EntryInfo, + Mount, NetworkPolicy, NetworkRule, PortRange, @@ -49,6 +51,60 @@ logger = logging.getLogger(__name__) +@dataclass(frozen=True) +class _NativeHostPathMount(adx_environment.Mount): + """Extend rc2's public Mount serialization hook for its server's Local source. + + The pinned native SDK checks isinstance(Mount) before calling to_dict, but + its stock value type only represents Image/S3. Keep that constructor and + transport intact without replacing native classes or mutating global state. + """ + + host_path: str = "" + read_only: bool = True + + def __post_init__(self) -> None: + self._public_mount() + + def _public_mount(self) -> Mount: + if self.image_url is not None or self.s3_config is not None: + raise ValueError("HostPath adapter requires only a host_path source") + return Mount( + target=self.target, + type=self.type, + host_path=self.host_path, + read_only=self.read_only, + ) + + def to_dict(self) -> dict[str, Any]: + return self._public_mount().to_dict() + + +def _native_mount(mount: Mount) -> Any: + if mount.host_path is not None: + return _NativeHostPathMount( + target=mount.target, + host_path=mount.host_path, + read_only=mount.read_only, + ) + return adx_environment.Mount( + target=mount.target, + image_url=mount.image_url, + s3_config=( + adx_environment.S3Config( + endpoint=mount.s3_config.endpoint, + bucket=mount.s3_config.bucket, + object=mount.s3_config.object, + access_key=mount.s3_config.access_key, + secret_key=mount.s3_config.secret_key, + ) + if mount.s3_config is not None + else None + ), + type=mount.type, + ) + + def _control_pty_connection(connection: Any) -> Any: """PTY direct routing uses the control TLS entrypoint, not the data port.""" @@ -559,25 +615,7 @@ def create(self, spec: SandboxSpec) -> BackendSession: network = None if spec.network_policy is not None: network = _native_network_policy(spec.network_policy) - mounts = [ - adx_environment.Mount( - target=mount.target, - image_url=mount.image_url, - s3_config=( - adx_environment.S3Config( - endpoint=mount.s3_config.endpoint, - bucket=mount.s3_config.bucket, - object=mount.s3_config.object, - access_key=mount.s3_config.access_key, - secret_key=mount.s3_config.secret_key, - ) - if mount.s3_config is not None - else None - ), - type=mount.type, - ) - for mount in spec.mounts - ] + mounts = [_native_mount(mount) for mount in spec.mounts] create_timeout = spec.schedule_timeout + 60 create_args = dict( image=spec.image, diff --git a/sdk/python/akernel_sdk/sandbox.py b/sdk/python/akernel_sdk/sandbox.py index 31b8eb48..dc042d9d 100644 --- a/sdk/python/akernel_sdk/sandbox.py +++ b/sdk/python/akernel_sdk/sandbox.py @@ -199,7 +199,9 @@ def __init__( which uses the image's OCI WORKDIR when ``inherit_entrypoint=True``. port_forwardings: Sandbox TCP ports exposed through the gateway. - mounts: Additional read-only OCI or S3-backed mounts. + mounts: Additional read-only OCI/S3 mounts or explicit node HostPath + bind mounts for trusted management callers. HostPath writes + require ``Mount(..., read_only=False)``. reverse_tunnel: SDK-side HTTP service exposed inside the sandbox. detached: Keep the sandbox alive when this client closes. node_id: Require placement on a specific AKernel node. diff --git a/sdk/python/akernel_sdk/types.py b/sdk/python/akernel_sdk/types.py index 3bf9fe40..a582045f 100644 --- a/sdk/python/akernel_sdk/types.py +++ b/sdk/python/akernel_sdk/types.py @@ -507,31 +507,59 @@ def to_dict(self) -> dict[str, Any]: @dataclass(frozen=True) class Mount: - """Read-only OCI image or S3 object mounted inside a sandbox. + """OCI/S3 read-only mount or a trusted-management node HostPath bind. - Exactly one of ``image_url`` and ``s3_config`` must be supplied. ``type`` - selects a read-only bind mount or an EROFS image mount. + Supply exactly one of ``image_url``, ``s3_config``, or ``host_path``. + HostPath sources must already be visible to the node runtime; they are not + resolved on the client. HostPath supports ``type="bind"`` and defaults to + read-only; writes require ``read_only=False``. Image/S3 sources are always + read-only. This type does not authorize access to the node's filesystem. """ target: str image_url: str | None = None s3_config: S3Config | None = None type: str = "bind" + host_path: str | None = None + read_only: bool = True def __post_init__(self) -> None: if not isinstance(self.target, str) or not self.target.startswith("/"): raise ValueError("target must be an absolute sandbox path") source_count = sum( - source is not None for source in (self.image_url, self.s3_config) + source is not None + for source in (self.image_url, self.s3_config, self.host_path) ) if source_count != 1: - raise ValueError("exactly one of image_url and s3_config must be specified") + raise ValueError( + "exactly one of image_url, s3_config, host_path must be specified" + ) if self.image_url is not None and ( not isinstance(self.image_url, str) or not self.image_url.strip() ): raise ValueError("image_url must be a non-empty string") if self.type not in ("bind", "erofs"): raise ValueError("type must be 'bind' or 'erofs'") + if not isinstance(self.read_only, bool): + raise TypeError("read_only must be a boolean") + if self.host_path is not None: + if ( + not isinstance(self.host_path, str) + or not self.host_path.startswith("/") + or any(part in (".", "..") for part in self.host_path.split("/")) + or any( + ord(char) < 32 or 127 <= ord(char) <= 159 + for char in self.host_path + ) + ): + raise ValueError( + "host_path must be an absolute path without traversal " + "or control characters" + ) + if self.type != "bind": + raise ValueError("host_path supports only type='bind'") + elif not self.read_only: + raise ValueError("image and S3 mounts must be read-only") def to_dict(self) -> dict[str, Any]: """Return the JSON-compatible representation expected by AKernel.""" @@ -539,12 +567,15 @@ def to_dict(self) -> dict[str, Any]: value: dict[str, Any] = { "type": self.type, "target": self.target, - "options": ["ro"], + "options": ["ro" if self.read_only else "rw"], } if self.image_url is not None: value["image_url"] = self.image_url if self.s3_config is not None: value["s3_config"] = self.s3_config.to_dict() + if self.host_path is not None: + value["host_path"] = self.host_path + value["create_if_missing"] = False return value diff --git a/sdk/python/examples/hostpath_mounts.py b/sdk/python/examples/hostpath_mounts.py new file mode 100644 index 00000000..35c424e5 --- /dev/null +++ b/sdk/python/examples/hostpath_mounts.py @@ -0,0 +1,57 @@ +# Copyright (c) 2026 Ant Group Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Bind a prepared node path using trusted management credentials. + +The node runtime must already see the source and allow the sandbox process to +access it. Default mode only lists the read-only mount. --write explicitly +creates one unique log file and leaves it on the node after sandbox deletion. +This SDK interface does not enforce the server-side authorization tracked in +AKernel #110. It is not included in the published SDK 0.2.0rc2 package. +""" + +import argparse +import posixpath +import shlex +import uuid + +from akernel_sdk import Mount, Sandbox + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--node-id", required=True) + parser.add_argument("--host-path", required=True) + parser.add_argument("--target", default="/apps/logs") + parser.add_argument("--write", action="store_true") + args = parser.parse_args() + mount = Mount( + target=args.target, host_path=args.host_path, read_only=not args.write + ) + with Sandbox(node_id=args.node_id, mounts=[mount], cpu=1000, memory=1024) as sb: + command = "ls -ld -- " + shlex.quote(args.target) + if args.write: + marker = "akernel-sdk-" + uuid.uuid4().hex + filename = marker + ".log" + target = shlex.quote(posixpath.join(args.target, filename)) + command = f"printf '%s\\n' {shlex.quote(marker)} > {target} && cat {target}" + print("Log on the selected node:", posixpath.join(args.host_path, filename)) + result = sb.commands.run(command) + if result.exit_code != 0: + raise RuntimeError(result.stderr or result.stdout) + print(result.stdout) + + +if __name__ == "__main__": + main() diff --git a/sdk/python/tests/unit/test_adx_backend.py b/sdk/python/tests/unit/test_adx_backend.py index da92b791..8aaad601 100644 --- a/sdk/python/tests/unit/test_adx_backend.py +++ b/sdk/python/tests/unit/test_adx_backend.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import os import subprocess import tempfile @@ -20,6 +21,7 @@ from types import MappingProxyType, SimpleNamespace from unittest.mock import MagicMock, patch +import httpx from adx_environment._transport import EnvironmentHTTPError from adx_environment.commands import Commands as NativeCommands @@ -69,6 +71,68 @@ def setUp(self): token="secret", ) + def test_host_path_reaches_real_native_constructor_and_preserves_denial(self): + mount = Mount(target="/apps/logs", host_path="/apps/logs", read_only=False) + requests = [] + + def deny(_client, request, **_kwargs): + requests.append(request) + return httpx.Response( + 403, + json={ + "code": 403, "message": "host mount is not authorized", + "error": { + "code": "PERMISSION_DENIED", "retry": "never", + "outcome": "not_started", "requestId": "host-path-test", + }, + }, + request=request, + ) + + # Keep the pinned native constructor, Mount type check, request encoder + # and HTTP error parser. Stop at HTTPX send: no cloud or filesystem call. + with ( + patch.object(httpx.Client, "send", autospec=True, side_effect=deny), + self.assertRaises(BackendOperationError) as raised, + ): + adx.AdxBackend(self.config).create( + _spec(mounts=(mount,), node_id="node-logs") + ) + self.assertEqual(len(requests), 1) + self.assertEqual(requests[0].method, "POST") + self.assertTrue(requests[0].url.path.endswith("/environments")) + body = json.loads(requests[0].content) + self.assertEqual(body["mounts"], [mount.to_dict()]) + self.assertEqual( + body["scheduleAffinities"][0]["labelOps"], + [{"type": 0, "labelKey": "NODE_ID", "labelValues": ["node-logs"]}], + ) + self.assertEqual(raised.exception.code, "PERMISSION_DENIED") + self.assertEqual(raised.exception.retry, "never") + self.assertEqual(raised.exception.outcome, "not_started") + self.assertEqual(raised.exception.status_code, 403) + self.assertIsInstance(raised.exception.__cause__, EnvironmentHTTPError) + + def test_create_converts_mixed_mount_sources_without_changing_permissions(self): + native = MagicMock() + native.id = "default-mounts" + mounts = ( + Mount(target="/logs", host_path="/apps/logs", read_only=False), + Mount(target="/inputs", host_path="/apps/inputs"), + Mount(target="/tools", image_url="tools:v1"), + Mount( + target="/weights", type="erofs", + s3_config=S3Config("https://s3.example", "data", "weights.img"), + ), + ) + with patch.object(adx, "_OwnedSandbox", return_value=native) as create: + adx.AdxBackend(self.config).create(_spec(mounts=mounts)) + converted = create.call_args.kwargs["mounts"] + for source, actual in zip(mounts, converted, strict=True): + with self.subTest(target=source.target): + self.assertIsInstance(actual, adx.adx_environment.Mount) + self.assertEqual(actual.to_dict(), source.to_dict()) + def test_create_preserves_public_error_context_and_cause(self): error = EnvironmentHTTPError( 504, {}, "CPU cannot fit", code="DEADLINE_EXCEEDED", diff --git a/sdk/python/tests/unit/test_types.py b/sdk/python/tests/unit/test_types.py index 48c20fce..8f758461 100644 --- a/sdk/python/tests/unit/test_types.py +++ b/sdk/python/tests/unit/test_types.py @@ -169,6 +169,75 @@ def test_mount_requires_absolute_target(self): with self.assertRaisesRegex(ValueError, "absolute"): Mount(target="relative", image_url="ubuntu:24.04") + def test_host_path_mount_defaults_to_read_only(self): + # This path belongs to a remote node; it need not exist on the client. + mount = Mount(target="/logs", host_path="/node-only/apps/logs") + self.assertEqual( + mount.to_dict(), + { + "type": "bind", + "target": "/logs", + "host_path": "/node-only/apps/logs", + "options": ["ro"], + "create_if_missing": False, + }, + ) + + def test_host_path_mount_requires_explicit_writable_mode(self): + mount = Mount( + target="/apps/logs", host_path="/apps/logs", read_only=False + ) + self.assertEqual(mount.to_dict()["options"], ["rw"]) + self.assertFalse(mount.to_dict()["create_if_missing"]) + + def test_host_path_cannot_be_combined_with_other_mount_sources(self): + s3 = S3Config("https://s3.example.com", "data", "weights.img") + for extra in ( + {"image_url": "tools:v1"}, + {"s3_config": s3}, + {"image_url": "tools:v1", "s3_config": s3}, + ): + with self.subTest(extra=extra), self.assertRaisesRegex( + ValueError, "exactly one" + ): + Mount(target="/logs", host_path="/apps/logs", **extra) + + def test_host_path_rejects_relative_traversal_and_control_paths(self): + for source in ( + "", "apps/logs", "~/logs", "/apps/../logs", "/apps/./logs", + "/apps/\0logs", "/apps/\nlogs", "/apps/\x85logs", 123, + ): + with self.subTest(source=source), self.assertRaisesRegex( + ValueError, "host_path" + ): + Mount(target="/logs", host_path=source) + + def test_host_path_supports_only_bind_mounts(self): + with self.assertRaisesRegex(ValueError, "bind"): + Mount(target="/logs", host_path="/apps/logs", type="erofs") + + def test_mount_read_only_must_be_boolean(self): + for read_only in (0, 1, None, "false"): + with self.subTest(read_only=read_only), self.assertRaisesRegex( + TypeError, "read_only" + ): + Mount(target="/logs", host_path="/apps/logs", read_only=read_only) + + def test_image_and_s3_mounts_remain_read_only(self): + s3 = S3Config("https://s3.example.com", "data", "weights.img") + for source in ({"image_url": "tools:v1"}, {"s3_config": s3}): + with self.subTest(source=source), self.assertRaisesRegex( + ValueError, "read.only" + ): + Mount(target="/tools", read_only=False, **source) + self.assertEqual( + Mount("/tools", "tools:v1", None, "bind").to_dict(), + { + "type": "bind", "target": "/tools", "options": ["ro"], + "image_url": "tools:v1", + }, + ) + def test_http_reverse_tunnel_supports_http_and_https(self): self.assertEqual( HttpReverseTunnel("http://127.0.0.1:8000").url, From 6c77083420a43f0588713888f9fab7b1014511cf Mon Sep 17 00:00:00 2001 From: wayne Date: Tue, 20 Oct 2026 17:32:15 +0800 Subject: [PATCH 2/2] docs(sdk): focus HostPath guidance on current usage Describe prepared node paths, mount permissions and caller requirements without embedding issue discussions or release history in product guidance. Keep README, agent instructions, example help and adapter documentation consistent without changing mount behavior. Signed-off-by: wayne --- AGENTS.md | 5 +++-- sdk/python/README.md | 14 +++++--------- sdk/python/akernel_sdk/_backends/adx.py | 2 +- sdk/python/examples/hostpath_mounts.py | 4 ++-- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 60cd32dd..73d987a7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -392,8 +392,9 @@ documentation and examples; see `sdk/python/README.md` for runtime support. runtime. HostPath mounts default to read-only; writable binds require `read_only=False`. Image/S3 mounts remain read-only. Keep the pinned ADX SDK's HostPath serialization adapter under `_backends`, and preserve server-side -permission errors. This interface is for trusted management callers: client -validation is not source-path authorization, and #110 remains unresolved. +permission errors. Restrict this interface to trusted management callers and +operator-controlled source paths; client validation is not authorization to +access the node's filesystem. The SDK does not create host directories, mount node storage, or replicate node-local data. See the SDK guide and `sdk/python/examples/hostpath_mounts.py`. diff --git a/sdk/python/README.md b/sdk/python/README.md index 010fcc1c..35dfc5cf 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -703,9 +703,6 @@ a raw EROFS image; it rejects `image_url` and directory-backed mounts. ### Node HostPath bind mounts -HostPath support requires an SDK revision containing this change; it is not -included in the published `akernel-sdk==0.2.0rc2` package. - Trusted management clients can bind an existing node-visible path into a sandbox. For example, expose a prepared log directory on a selected runsc node: @@ -738,12 +735,11 @@ UID/GID to use it. Separate workers' local directories are separate data; through a bind can outlive the sandbox. Runtime-specific mount restrictions still apply; Firecracker does not support these directory-backed binds. -**Authorization boundary:** this client API does not authorize node paths. The -current ADX server does not enforce per-tenant HostPath authorization; see -[#110](https://github.com/inclusionAI/AKernel/issues/110). Use only trusted -management credentials and node paths controlled by the operator. SDK path -validation and read-only defaults do not resolve #110. Server permission errors -are returned through the normal `BackendOperationError` fields. +Restrict HostPath creation to trusted management clients and source paths +controlled by the operator. These mounts do not provide per-tenant source-path +isolation; SDK path validation and read-only defaults do not authorize access +to the node's filesystem. Server permission errors are returned through the +normal `BackendOperationError` fields. The maintained example requires an explicit node and source. It lists the read-only mount by default; `--write` creates one uniquely named log file and diff --git a/sdk/python/akernel_sdk/_backends/adx.py b/sdk/python/akernel_sdk/_backends/adx.py index 60db70de..ddf3be63 100644 --- a/sdk/python/akernel_sdk/_backends/adx.py +++ b/sdk/python/akernel_sdk/_backends/adx.py @@ -53,7 +53,7 @@ @dataclass(frozen=True) class _NativeHostPathMount(adx_environment.Mount): - """Extend rc2's public Mount serialization hook for its server's Local source. + """Adapt node HostPath sources through the pinned native Mount serializer. The pinned native SDK checks isinstance(Mount) before calling to_dict, but its stock value type only represents Image/S3. Keep that constructor and diff --git a/sdk/python/examples/hostpath_mounts.py b/sdk/python/examples/hostpath_mounts.py index 35c424e5..679fa375 100644 --- a/sdk/python/examples/hostpath_mounts.py +++ b/sdk/python/examples/hostpath_mounts.py @@ -17,8 +17,8 @@ The node runtime must already see the source and allow the sandbox process to access it. Default mode only lists the read-only mount. --write explicitly creates one unique log file and leaves it on the node after sandbox deletion. -This SDK interface does not enforce the server-side authorization tracked in -AKernel #110. It is not included in the published SDK 0.2.0rc2 package. +Use operator-controlled source paths; SDK validation does not authorize access +to the node's filesystem. """ import argparse