Skip to content

Commit 2d08971

Browse files
committed
feat(validation): enforce HF certification isolation
1 parent 7ef96d3 commit 2d08971

5 files changed

Lines changed: 273 additions & 2 deletions

File tree

docs/source/guides/runtime-providers.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ provider = DockerSwarmProvider()
164164

165165
Runs a registry image in a Hugging Face Sandbox. Pooled mode is the default and
166166
is appropriate for trusted same-user rollout fan-out. Dedicated mode allocates
167-
one VM per sandbox and is suited to untrusted or GPU workloads.
167+
one VM per sandbox and is suited to untrusted or GPU workloads. Official
168+
validation requires dedicated mode and rejects pooled execution.
168169

169170
```python
170171
from openenv.core.containers.runtime.hf_sandbox_provider import HFSandboxProvider
@@ -178,7 +179,8 @@ provider.wait_for_ready(base_url)
178179
```
179180

180181
Plain `env_vars` are visible as Job environment metadata. Use the dedicated
181-
mode's `secrets=` argument for encrypted runtime secrets.
182+
mode's `secrets=` argument for encrypted runtime secrets. Official validation
183+
does not forward coordinator secrets through either channel.
182184

183185
### KubernetesProvider
184186

src/openenv/core/containers/runtime/hf_sandbox_provider.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import asyncio
1212
import hashlib
13+
import re
1314
import socket
1415
import threading
1516
import time
@@ -39,6 +40,28 @@
3940
_SERVER_COMMAND = _POOLED_SERVER_COMMAND
4041
_MAX_WS_MESSAGE_SIZE = 100 * 1024 * 1024
4142
_MAX_HTTP_RESPONSE_SIZE = 100 * 1024 * 1024
43+
_CREDENTIAL_ENV_NAME = re.compile(
44+
r"(?i)(?:^|[_-])(?:auth(?:orization)?|credentials?|password|passwd|"
45+
r"private[_-]?key|secret|token|api[_-]?key|access[_-]?key(?:[_-]?id)?)"
46+
r"(?:$|[_-])|(?:^|[_-])(?:jwt|pat|askpass)(?:$|[_-])"
47+
)
48+
_CREDENTIAL_ENV_ALIASES = frozenset(
49+
{"PGPASSWORD", "PGPASSFILE", "MYSQL_PWD", "GIT_ASKPASS", "NETRC"}
50+
)
51+
_CREDENTIAL_ENV_VALUE = (
52+
re.compile(r"(?i)\bbearer\s+\S+"),
53+
re.compile(r"(?i)\b(?:hf_|sk-|github_pat_|gh[pousr]_)[A-Za-z0-9_-]{8,}\b"),
54+
re.compile(r"\b(?:AKIA|ASIA)[0-9A-Z]{16}\b"),
55+
re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
56+
re.compile(r"(?i)\b[a-z][a-z0-9+.-]*://[^/\s@]*:[^/\s@]+@"),
57+
re.compile(
58+
r"(?i)\b[a-z][a-z0-9+.-]*://[^\s#]*[?&]"
59+
r"(?:auth(?:orization)?|credentials?|password|passwd|token|"
60+
r"api[_-]?key|access[_-]?key|key|sig(?:nature)?|awsaccesskeyid|"
61+
r"googleaccessid|x[-_]amz[-_](?:credential|security[-_]token|signature)|"
62+
r"x[-_]goog[-_](?:credential|signature))=[^&#\s]+"
63+
),
64+
)
4265
_HOP_BY_HOP_HEADERS = {
4366
"connection",
4467
"content-encoding",
@@ -56,6 +79,21 @@
5679
_POOL_LOCK = threading.Lock()
5780

5881

82+
def credential_environment_names(env_vars: dict[str, str] | None) -> list[str]:
83+
"""Return names whose name or value appears to carry credentials."""
84+
flagged: list[str] = []
85+
for raw_name, raw_value in (env_vars or {}).items():
86+
name = str(raw_name)
87+
value = str(raw_value)
88+
if (
89+
name.upper() in _CREDENTIAL_ENV_ALIASES
90+
or _CREDENTIAL_ENV_NAME.search(name)
91+
or any(pattern.search(value) for pattern in _CREDENTIAL_ENV_VALUE)
92+
):
93+
flagged.append(name)
94+
return sorted(flagged)
95+
96+
5997
class _NoRedirectWebSocketConnect(ws_connect):
6098
def process_redirect(self, exc: Exception) -> Exception | str:
6199
return exc
@@ -300,6 +338,10 @@ def __init__(
300338
self._secrets = dict(secrets) if secrets is not None else None
301339
self.flavor = flavor
302340
self._mode = mode
341+
self._official_certification = False
342+
self._official_image: str | None = None
343+
self._official_flavor: str | None = None
344+
self._official_env_vars: dict[str, str] | None = None
303345
self._sandbox: Any = None
304346
self._server_process: Any = None
305347
self._proxy: _LocalAuthProxy | None = None
@@ -326,6 +368,34 @@ def isolation_mode(self) -> Literal["pooled", "dedicated", "unknown"]:
326368
return "unknown"
327369
return "dedicated" if host_id is None else "pooled"
328370

371+
def _lock_for_official_certification(self) -> None:
372+
"""Freeze the settings that cross the official runner trust boundary."""
373+
if self._sandbox is not None:
374+
raise RuntimeError(
375+
"Official certification must validate and lock the provider "
376+
"before a Hugging Face sandbox is started."
377+
)
378+
self._official_certification = True
379+
self._official_image = self.image
380+
self._official_flavor = self.flavor
381+
self._official_env_vars = (
382+
dict(self._env_vars) if self._env_vars is not None else None
383+
)
384+
385+
def _verify_official_runtime_isolation(self) -> None:
386+
try:
387+
host_id = self._sandbox.host_id
388+
except (AttributeError, RuntimeError) as exc:
389+
raise RuntimeError(
390+
"Official certification could not verify dedicated isolation "
391+
"for the created Hugging Face sandbox."
392+
) from exc
393+
if host_id is not None:
394+
raise RuntimeError(
395+
"Official certification requires a dedicated Hugging Face sandbox; "
396+
"the created sandbox is attached to a shared host."
397+
)
398+
329399
def _create_sandbox(self, *, image: str, env_vars: dict[str, str] | None) -> Any:
330400
if self.mode == "dedicated":
331401
Sandbox = _get_sandbox_cls()
@@ -360,12 +430,37 @@ def start_container(
360430
f"HFSandboxProvider only supports port {_DEFAULT_PORT} (got {port})."
361431
)
362432

433+
if self._official_certification:
434+
if (
435+
self.image != self._official_image
436+
or self.flavor != self._official_flavor
437+
or self.mode != "dedicated"
438+
or self._env_vars != self._official_env_vars
439+
or (image is not None and image != self._official_image)
440+
):
441+
raise RuntimeError(
442+
"Official certification does not allow execution-setting changes "
443+
"after the provider trust check."
444+
)
445+
if env_vars is not None:
446+
raise RuntimeError(
447+
"Official certification does not allow environment overrides "
448+
"after the provider trust check."
449+
)
450+
credential_names = credential_environment_names(self._env_vars)
451+
if credential_names or self._secrets:
452+
raise RuntimeError(
453+
"Official subject sandboxes cannot receive coordinator credentials."
454+
)
455+
363456
effective_image = self.image if image is None else image
364457
effective_env = self._env_vars if env_vars is None else env_vars
365458
self._sandbox = self._create_sandbox(
366459
image=effective_image, env_vars=effective_env
367460
)
368461
try:
462+
if self._official_certification:
463+
self._verify_official_runtime_isolation()
369464
if not hasattr(self._sandbox, "proxy_url_for") or not hasattr(
370465
self._sandbox, "proxy_headers"
371466
):

src/openenv/validation/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
OPENENV_VALIDATION_POLICY,
2525
VALIDATION_POLICY_VERSION,
2626
)
27+
from .security import ensure_official_hf_sandbox
2728

2829
__all__ = [
2930
"CheckOutcome",
@@ -42,6 +43,7 @@
4243
"ValidationSeverity",
4344
"ValidationStatus",
4445
"build_validation_plan",
46+
"ensure_official_hf_sandbox",
4547
"execute_validation_plan",
4648
"format_shared_validation_report",
4749
"run_local_validation",

src/openenv/validation/security.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
3+
"""Fail-closed trust-boundary guards used by remote certification."""
4+
5+
from __future__ import annotations
6+
7+
from typing import Any
8+
9+
from openenv.core.containers.runtime.hf_sandbox_provider import (
10+
credential_environment_names,
11+
)
12+
13+
14+
def ensure_official_hf_sandbox(provider: Any) -> None:
15+
"""Reject shared HF pools for official certification workloads."""
16+
if getattr(provider, "isolation_mode", None) != "dedicated":
17+
raise RuntimeError(
18+
"Official certification requires a dedicated Hugging Face sandbox; "
19+
"pooled execution is a same-user trust boundary."
20+
)
21+
if getattr(provider, "secrets", None):
22+
raise RuntimeError(
23+
"Official subject sandboxes cannot receive coordinator secrets."
24+
)
25+
env_vars = getattr(provider, "env_vars", None) or {}
26+
credential_names = credential_environment_names(env_vars)
27+
if credential_names:
28+
raise RuntimeError(
29+
"Official subject sandboxes cannot receive credential-like "
30+
f"environment variables: {', '.join(credential_names)}"
31+
)
32+
lock = getattr(provider, "_lock_for_official_certification", None)
33+
if not callable(lock):
34+
raise RuntimeError(
35+
"Official certification requires a provider that can lock its "
36+
"validated execution settings."
37+
)
38+
lock()
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
3+
"""Tests for validation certification trust-boundary enforcement."""
4+
5+
from __future__ import annotations
6+
7+
from unittest.mock import MagicMock, patch
8+
9+
import pytest
10+
from openenv.core.containers.runtime import hf_sandbox_provider as provider_module
11+
from openenv.core.containers.runtime.hf_sandbox_provider import HFSandboxProvider
12+
from openenv.validation.security import ensure_official_hf_sandbox
13+
14+
15+
def test_official_certification_rejects_pooled_hf_sandbox() -> None:
16+
pooled = HFSandboxProvider(image="example")
17+
dedicated = HFSandboxProvider(image="example", mode="dedicated")
18+
19+
with pytest.raises(RuntimeError, match="dedicated"):
20+
ensure_official_hf_sandbox(pooled)
21+
22+
ensure_official_hf_sandbox(dedicated)
23+
24+
25+
@pytest.mark.parametrize(
26+
"env_vars",
27+
[
28+
{"HF_TOKEN": "secret"},
29+
{"HF_AUTH": "hf_1234567890abcdef"},
30+
{"AUTH": "Bearer coordinator-token"},
31+
{"AWS_ACCESS_KEY_ID": "AKIA1234567890ABCDEF"},
32+
{"MODE": "ASIA1234567890ABCDEF"},
33+
{"DATABASE_URL": "https://alice:hunter2@example.com/database"},
34+
{"DATABASE_URL": "postgresql://alice:hunter2@db/app"},
35+
{"CACHE_URL": "redis://:hunter2@cache/0"},
36+
{"PGPASSWORD": "secret"},
37+
{"MYSQL_PWD": "secret"},
38+
{"CI_JOB_JWT": "secret"},
39+
{"GIT_ASKPASS": "/tmp/helper"},
40+
{"GOOGLE_APPLICATION_CREDENTIALS": "/tmp/google.json"},
41+
{"NETRC": "/tmp/netrc"},
42+
{"MODE": "postgresql://db/app?password=hunter2"},
43+
{"MODE": "redis://cache/0?token=hunter2"},
44+
{"MODE": "https://service.example/path?api_key=hunter2"},
45+
{
46+
"DOWNLOAD_URL": (
47+
"https://service.example/object?X-Amz-Credential=scope&"
48+
"X-Amz-Signature=deadbeef"
49+
)
50+
},
51+
{
52+
"DOWNLOAD_URL": (
53+
"https://service.example/object?X-Goog-Credential=scope&"
54+
"X-Goog-Signature=deadbeef"
55+
)
56+
},
57+
{"MODE": "hf_1234567890abcdef"},
58+
],
59+
)
60+
def test_official_certification_rejects_forwarded_credentials(
61+
env_vars: dict[str, str],
62+
) -> None:
63+
provider = HFSandboxProvider(
64+
image="example",
65+
mode="dedicated",
66+
env_vars=env_vars,
67+
)
68+
69+
with pytest.raises(RuntimeError, match="credential-like"):
70+
ensure_official_hf_sandbox(provider)
71+
72+
73+
def test_official_certification_locks_start_environment() -> None:
74+
provider = HFSandboxProvider(
75+
image="example",
76+
mode="dedicated",
77+
env_vars={"MODE": "certify"},
78+
)
79+
ensure_official_hf_sandbox(provider)
80+
81+
with pytest.raises(RuntimeError, match="environment overrides"):
82+
provider.start_container(env_vars={"HF_TOKEN": "secret"})
83+
84+
85+
def test_official_certification_detects_post_lock_environment_mutation() -> None:
86+
provider = HFSandboxProvider(
87+
image="example",
88+
mode="dedicated",
89+
env_vars={"MODE": "certify"},
90+
)
91+
ensure_official_hf_sandbox(provider)
92+
assert provider._env_vars is not None
93+
provider._env_vars["MODE"] = "changed"
94+
95+
with pytest.raises(RuntimeError, match="execution-setting changes"):
96+
provider.start_container()
97+
98+
99+
def test_official_start_rejects_unverifiable_runtime_isolation() -> None:
100+
class SandboxWithoutHostIdentity:
101+
proxy_headers: dict[str, str] = {}
102+
103+
def run(self, *args: object, **kwargs: object) -> MagicMock:
104+
return MagicMock()
105+
106+
def proxy_url_for(self, port: int, path: str) -> str:
107+
return "https://sandbox.example/proxy"
108+
109+
def kill(self) -> None:
110+
self._killed = True
111+
112+
sandbox = SandboxWithoutHostIdentity()
113+
sandbox_cls = MagicMock()
114+
sandbox_cls.create.return_value = sandbox
115+
provider = HFSandboxProvider(image="example", mode="dedicated")
116+
ensure_official_hf_sandbox(provider)
117+
118+
with patch.object(provider_module, "_get_sandbox_cls", return_value=sandbox_cls):
119+
with pytest.raises(RuntimeError, match="verify dedicated isolation"):
120+
provider.start_container()
121+
122+
assert provider._sandbox is None
123+
124+
125+
def test_official_certification_cannot_lock_active_sandbox() -> None:
126+
provider = HFSandboxProvider(image="example", mode="dedicated")
127+
sandbox = MagicMock()
128+
sandbox.host_id = None
129+
provider._sandbox = sandbox
130+
131+
with pytest.raises(RuntimeError, match="before.*started"):
132+
ensure_official_hf_sandbox(provider)
133+
134+
assert provider._official_certification is False

0 commit comments

Comments
 (0)