Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docker/master-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ start_embedded_challenges() {
"PRISM_DOCKER_BACKEND=${PRISM_DOCKER_BACKEND:-cli}"
# HOTPATCH allowlist: OpenRouter plagiarism + worker plane.
# PROD POLICY: Prism eval never runs on master — CPU_REEXEC must stay false;
# miners supply Lium pods; admission_requires_worker must stay true.
# miners supply Lium pods; admission_requires_worker default false (unattested/host path).
"PRISM_WORKER_PLANE__CPU_REEXEC_TEST_MODE=${PRISM_WORKER_PLANE__CPU_REEXEC_TEST_MODE:-false}"
"PRISM_WORKER_PLANE__ADMISSION_REQUIRES_WORKER=${PRISM_WORKER_PLANE__ADMISSION_REQUIRES_WORKER:-true}"
"PRISM_WORKER_PLANE__ADMISSION_REQUIRES_WORKER=${PRISM_WORKER_PLANE__ADMISSION_REQUIRES_WORKER:-false}"
Comment on lines 212 to +215

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Confirm the admission_requires_worker=false default is intentional for prod, not just the unattested rollout.

This flips embedded Prism from worker-gated admission to open admission for every deployment that doesn't explicitly set the var, including ones running with the unattested flag off. Consider defaulting from the unattested flag instead (e.g. false only when CHALLENGE_UNATTESTED_EXECUTION/NO_PHALA is on) so the attested path keeps its previous guarantee.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker/master-entrypoint.sh` around lines 212 - 215, Update the defaulting
logic for PRISM_WORKER_PLANE__ADMISSION_REQUIRES_WORKER in the master entrypoint
so it is false only for unattested/NO_PHALA deployments and remains worker-gated
for attested deployments when the variable is unset. Preserve explicit
environment overrides and the existing CPU_REEXEC_TEST_MODE behavior.

"PRISM_WORKER_PLANE__MASTER_BASE_URL=${PRISM_WORKER_PLANE__MASTER_BASE_URL:-http://127.0.0.1:8081}"
"PRISM_PLAGIARISM_LLM_ENABLED=${PRISM_PLAGIARISM_LLM_ENABLED:-false}"
"PRISM_PLAGIARISM_LLM_REQUIRED=${PRISM_PLAGIARISM_LLM_REQUIRED:-false}"
Expand Down Expand Up @@ -254,7 +254,8 @@ start_embedded_challenges() {
# Operator overrides win over the defaults above. Prefixes stay disjoint per
# challenge so the env -i isolation is preserved.
load_challenge_env_file prism_env "${PRISM_ENV_FILE}" \
PRISM_ PHALA_ DSTACK_ OPENROUTER_API_KEY
PRISM_ PHALA_ DSTACK_ OPENROUTER_API_KEY \
CHALLENGE_UNATTESTED_EXECUTION CHALLENGE_NO_PHALA NO_PHALA
load_challenge_env_file ac_env "${AC_ENV_FILE}" \
CHALLENGE_ BASE_CHALLENGE_ PHALA_ DSTACK_ OPENROUTER_API_KEY

Expand Down
23 changes: 23 additions & 0 deletions packages/challenges/prism/src/prism_challenge/ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
verify_execution_proof,
)
from .queue import PrismWorker, WorkerFinalizationError
from .unattested_execution import (
is_unattested_execution_enabled,
mark_result_unattested,
)

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -164,6 +168,13 @@ def to_response(self) -> dict[str, Any]:
payload["reason"] = self.reason
if self.attestation_mode is not None:
payload["attestation_mode"] = self.attestation_mode
# T21: when unattested mode is on, force the unforgeable honesty mark.
# Miner-supplied attested:true / verified status cannot appear here.
if is_unattested_execution_enabled():
marked = mark_result_unattested(payload)
payload["attested"] = marked["attested"]
payload["attestation_status"] = marked["attestation_status"]
payload["execution_mode"] = marked["execution_mode"]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return payload


Expand Down Expand Up @@ -497,6 +508,18 @@ def _evaluate_constation_gate(
)

if bundle is None:
# T20: narrow unattested path — admit scoring without a constation
# bundle ONLY when the explicit flag is on. constation_ok stays False
# so elevation still requires a real bundle + six-check path.
if is_unattested_execution_enabled():
return _ConstationGate(
admit=True,
constation_ok=False,
reason="unattested:missing_constation_bundle",
message=(
"unattested execution: scoring without constation bundle; not TEE/verified"
),
)
return _ConstationGate(
admit=False,
constation_ok=False,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ async def push_once(
if epoch is not None
else int(self.epoch_fn())
if self.epoch_fn is not None
else int(now.timestamp()) // 3600
else int(now.timestamp()) // 360
)
revision = (
int(force_revision)
Expand Down Expand Up @@ -625,14 +625,44 @@ def maybe_build_push_client_from_settings(
token = str(shared) if shared else None
if not token:
return None
# Challenge scoring epoch (architecture crowns) stays on settings.epoch_seconds.
epoch_seconds = int(getattr(settings, "epoch_seconds", 3600) or 3600)
# Master weight-seal identity uses BASE_MASTER epoch interval (default 360s),
# NOT the challenge scoring epoch. Wall-clock can lag force-advanced open
# epochs, so advance past last acknowledged push when behind.
master_epoch_seconds = int(
getattr(settings, "raw_weight_master_epoch_seconds", 0)
or __import__("os").environ.get("PRISM_RAW_WEIGHT_MASTER_EPOCH_SECONDS", 0)
or __import__("os").environ.get("BASE_MASTER__EPOCH_INTERVAL_SECONDS", 0)
or 360
)
# VAL-RESLAB-008: raw_weight_push settings defaults match get_weights 0.50/0.50.
arch = float(getattr(settings, "architecture_reward_weight", 0.50))
train = float(getattr(settings, "training_reward_weight", 0.50))
interval_hint = float(getattr(settings, "raw_weight_push_interval_seconds", 30.0))
db_path = str(getattr(database, "path", "") or "")

def _epoch() -> int:
return int(datetime.now(UTC).timestamp()) // max(epoch_seconds, 1)
import sqlite3

wall = int(datetime.now(UTC).timestamp()) // max(int(master_epoch_seconds), 1)
last = None
try:
if db_path:
con = sqlite3.connect(db_path)
try:
row = con.execute(
"SELECT last_epoch FROM raw_weight_push_ledger WHERE id = 1"
).fetchone()
if row and row[0] is not None:
last = int(row[0])
finally:
con.close()
except Exception:
last = None
if last is not None and wall <= last:
return last + 1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return wall

client = RawWeightPushClient(
database=database,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
"""Unattested-execution flag + unforgeable mark for prism (T19/T20/T21).

Prism does **not** depend on ``agent_challenge`` (separate workspace package;
importing it would couple challenge packages and risk circular install graphs).
This module is a **thin duplicate** of the env precedence and unforgeable mark
in ``agent_challenge.evaluation.no_phala``:

1. ``CHALLENGE_UNATTESTED_EXECUTION`` if present (canonical)
2. else ``CHALLENGE_NO_PHALA`` if present (deprecated alias)
3. else ``NO_PHALA`` if present (operator convenience / deprecated alias)
4. else ``False`` — never inferred from missing keys

Keep this file in lockstep with T19/T21. Do not invent a second flag name.
Default remains **off** (fail-closed constation gate when bundle missing).

T21: :func:`mark_result_unattested` always forces ``attested=False`` /
``attestation_status=unattested`` / ``execution_mode=no_phala_host``. Miner-
supplied ``attested:true`` cannot survive. Unlike agent-challenge, prism keeps
worker ``execution_proof`` (sr25519 tier-0 proof) — only TEE-looking claim keys
are stripped.
"""

from __future__ import annotations

import os
from collections.abc import Mapping
from typing import Any, Final

#: Canonical challenge-prefixed env for unattested host execution.
CHALLENGE_UNATTESTED_EXECUTION_ENV: Final = "CHALLENGE_UNATTESTED_EXECUTION"
#: Deprecated alias for :data:`CHALLENGE_UNATTESTED_EXECUTION_ENV` (same flag).
CHALLENGE_NO_PHALA_ENV: Final = "CHALLENGE_NO_PHALA"
#: Operator-facing plain env (also deprecated alias).
NO_PHALA_ENV: Final = "NO_PHALA"

_TRUTHY: Final = frozenset({"1", "true", "yes", "on"})
_FALSY: Final = frozenset({"0", "false", "no", "off", ""})

#: Explicit attestation status — never "attested" / "verified" from this module.
ATTESTATION_STATUS_UNATTESTED: Final = "unattested"
#: Wire / stored execution mode label for host-local unattested runs (T21 parity).
EXECUTION_MODE_NO_PHALA_HOST: Final = "no_phala_host"

RESULT_KEY_ATTESTED: Final = "attested"
RESULT_KEY_ATTESTATION_STATUS: Final = "attestation_status"
RESULT_KEY_EXECUTION_MODE: Final = "execution_mode"

#: TEE / Phala-looking claim keys stripped on mark. Worker ``execution_proof`` is
#: intentionally NOT listed — prism requires it for tier-0 verification.
_ATTESTED_LOOKING_KEYS: Final = frozenset(
{
"attestation_binding",
"tdx_quote",
"phala_attestation",
"tdx_quote_b64",
"gpu_eat_jwt",
}
)


def _parse_bool_env(raw: str | None) -> bool | None:
"""Return True/False if ``raw`` is a recognized boolean token, else None."""

if raw is None:
return None
text = str(raw).strip().lower()
if text in _TRUTHY:
return True
if text in _FALSY:
return False
return None


def resolve_unattested_execution_from_environ(
environ: Mapping[str, str] | None = None,
) -> bool:
"""Resolve unattested-execution mode from env (T19 precedence)."""

env = os.environ if environ is None else environ
if CHALLENGE_UNATTESTED_EXECUTION_ENV in env:
parsed = _parse_bool_env(env.get(CHALLENGE_UNATTESTED_EXECUTION_ENV))
return bool(parsed)
if CHALLENGE_NO_PHALA_ENV in env:
parsed = _parse_bool_env(env.get(CHALLENGE_NO_PHALA_ENV))
return bool(parsed)
if NO_PHALA_ENV in env:
parsed = _parse_bool_env(env.get(NO_PHALA_ENV))
return bool(parsed)
return False


def is_unattested_execution_enabled(
*,
settings_flag: bool | None = None,
environ: Mapping[str, str] | None = None,
) -> bool:
"""Return whether unattested (non-TEE) execution mode is active.

Prefer ``settings_flag`` when the process already resolved the flag at
startup. Otherwise read env via :func:`resolve_unattested_execution_from_environ`.
"""

if settings_flag is not None:
return bool(settings_flag)
return resolve_unattested_execution_from_environ(environ)


def mark_result_unattested(payload: Mapping[str, Any]) -> dict[str, Any]:
"""Return a result envelope explicitly marked unattested (unforgeable).

Always sets ``attested=False`` and ``attestation_status=unattested`` and
``execution_mode=no_phala_host``. Callers cannot pass ``attested=True``
through this function — the boolean is hard-coded (T21).

Strips TEE/Phala-looking claim keys. Preserves prism worker
``execution_proof`` (not a TEE claim).
"""

out = dict(payload)
for key in _ATTESTED_LOOKING_KEYS:
out.pop(key, None)
# Hard-coded False — never accept a caller-supplied True.
out[RESULT_KEY_ATTESTED] = False
out[RESULT_KEY_ATTESTATION_STATUS] = ATTESTATION_STATUS_UNATTESTED
out[RESULT_KEY_EXECUTION_MODE] = EXECUTION_MODE_NO_PHALA_HOST
return out


def project_public_attestation(
*,
stored: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
"""Unforgeable public projection of attestation fields (server-side only).

When unattested execution is active, always returns the same mark as
:func:`mark_result_unattested`. Never promotes miner-supplied claims.
"""

del stored # never trust client/stored claims under unattested mode path
if is_unattested_execution_enabled():
return {
RESULT_KEY_ATTESTED: False,
RESULT_KEY_ATTESTATION_STATUS: ATTESTATION_STATUS_UNATTESTED,
RESULT_KEY_EXECUTION_MODE: EXECUTION_MODE_NO_PHALA_HOST,
}
# Flag off: honest default is unattested/standard — never invent TEE.
return {
RESULT_KEY_ATTESTED: False,
RESULT_KEY_ATTESTATION_STATUS: ATTESTATION_STATUS_UNATTESTED,
RESULT_KEY_EXECUTION_MODE: "standard",
}


__all__ = [
"ATTESTATION_STATUS_UNATTESTED",
"CHALLENGE_NO_PHALA_ENV",
"CHALLENGE_UNATTESTED_EXECUTION_ENV",
"EXECUTION_MODE_NO_PHALA_HOST",
"NO_PHALA_ENV",
"RESULT_KEY_ATTESTATION_STATUS",
"RESULT_KEY_ATTESTED",
"RESULT_KEY_EXECUTION_MODE",
"is_unattested_execution_enabled",
"mark_result_unattested",
"project_public_attestation",
"resolve_unattested_execution_from_environ",
]
105 changes: 105 additions & 0 deletions packages/challenges/prism/tests/test_raw_weight_push.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,108 @@ class _Enabled(_Settings):
)
assert client is not None
assert client.master_base_url == "http://master.test"


def test_maybe_build_push_client_uses_master_epoch_interval(
database: Database, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Seal identity uses master 360s interval, not challenge scoring epoch."""

class _Settings:
raw_weight_push_enabled = True
master_base_url = "http://master.test"
worker_plane = type("WP", (), {"master_base_url": None})()
slug = SLUG
epoch_seconds = 21600 # challenge scoring — must NOT drive seal epoch
architecture_reward_weight = 0.5
training_reward_weight = 0.5
raw_weight_push_interval_seconds = 5.0
raw_weight_master_epoch_seconds = 360

def internal_token(self) -> str:
return TOKEN

monkeypatch.delenv("PRISM_RAW_WEIGHT_MASTER_EPOCH_SECONDS", raising=False)
monkeypatch.delenv("BASE_MASTER__EPOCH_INTERVAL_SECONDS", raising=False)

fixed_now = datetime(2026, 7, 29, 12, 0, 0, tzinfo=UTC)
monkeypatch.setattr(
"prism_challenge.raw_weight_push.datetime",
type(
"DT",
(),
{
"now": staticmethod(lambda tz=None: fixed_now),
"UTC": UTC,
},
),
)

client = maybe_build_push_client_from_settings(
settings=_Settings(), database=database, repository=object()
)
assert client is not None
assert client.epoch_fn is not None
expected = int(fixed_now.timestamp()) // 360
# Challenge 21600-scale would be far smaller — prove we use 360.
challenge_scale = int(fixed_now.timestamp()) // 21600
assert expected != challenge_scale
assert client.epoch_fn() == expected


@pytest.mark.asyncio
async def test_maybe_build_push_client_advances_past_ledger_epoch(
database: Database, monkeypatch: pytest.MonkeyPatch
) -> None:
"""When sealer force-advances ahead of wall clock, push past last ack."""

class _Settings:
raw_weight_push_enabled = True
master_base_url = "http://master.test"
worker_plane = type("WP", (), {"master_base_url": None})()
slug = SLUG
epoch_seconds = 21600
architecture_reward_weight = 0.5
training_reward_weight = 0.5
raw_weight_push_interval_seconds = 5.0
raw_weight_master_epoch_seconds = 360

def internal_token(self) -> str:
return TOKEN

monkeypatch.delenv("PRISM_RAW_WEIGHT_MASTER_EPOCH_SECONDS", raising=False)
monkeypatch.delenv("BASE_MASTER__EPOCH_INTERVAL_SECONDS", raising=False)

fixed_now = datetime(2026, 7, 29, 12, 0, 0, tzinfo=UTC)
monkeypatch.setattr(
"prism_challenge.raw_weight_push.datetime",
type(
"DT",
(),
{
"now": staticmethod(lambda tz=None: fixed_now),
"UTC": UTC,
},
),
)

store = RawWeightPushStore(database, challenge_slug=SLUG)
await store.init()
wall = int(fixed_now.timestamp()) // 360
last_epoch = wall + 50
await store.acknowledge(
epoch=last_epoch,
revision=1,
payload_digest="a" * 64,
snapshot_id="snap-ledger",
canonical_payload="{}",
nonce="n-ledger",
acknowledged_at=fixed_now.isoformat(),
)

client = maybe_build_push_client_from_settings(
settings=_Settings(), database=database, repository=object()
)
assert client is not None
assert client.epoch_fn is not None
assert client.epoch_fn() == last_epoch + 1
Loading
Loading