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
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
AGENT_ENV_ALLOWLIST: frozenset[str] = frozenset(
{
"LLM_COST_LIMIT",
# VAL-LLM-MODEL: the packaged agent fails closed without a concrete
# model id ("set LLM_MODEL"). Not secret-shaped, never a URL/host.
"LLM_MODEL",
"OPENROUTER_API_KEY",
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,14 @@ def _terminal_bench_env(
"BASE_BENCHMARK_DATASET": settings.terminal_bench_dataset,
**TERMINAL_BENCH_WRITABLE_ENV,
}
# VAL-LLM-MODEL: the packaged agent fails closed without a concrete model id
# ("A concrete model id is required: set LLM_MODEL"). The platform holds it
# as CHALLENGE_LLM_MODEL, which the agent never reads, so publish it under
# the name the agent expects. A miner-supplied LLM_MODEL overrides this in
# the sanitized merge below. Left unset when the operator blanks it, so the
# agent still fails loudly rather than running an unmeasured default.
if settings.llm_model:
env["LLM_MODEL"] = settings.llm_model
for name in settings.harbor_forward_env_vars:
value = os.environ.get(name)
if value and name not in TERMINAL_BENCH_CONTROL_ENV_KEYS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
{
"OPENROUTER_API_KEY",
"LLM_COST_LIMIT",
# VAL-LLM-MODEL: a miner may bring their own key *and* their own model
# id. Not a URL/host/proxy name, and URL-shaped values stay rejected by
# ``looks_like_url_value``, so this opens no endpoint-injection hole.
"LLM_MODEL",
Comment on lines +49 to +52

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Locate and inspect miner_env.py"
fd -a 'miner_env\.py$' . | sed 's#^\./##'
file="$(fd 'miner_env\.py$' . | head -n1)"
if [ -n "${file:-}" ]; then
  echo "--- $file ($(wc -l < "$file") lines) ---"
  cat -n "$file"
fi

echo
echo "## Search for MINER_ENV_PRODUCT_ALLOWLIST and related validators"
rg -n --hidden --glob '*.py' 'MINER_ENV_PRODUCT_ALLOWLIST|is_token_or_key_env_name|looks_like_url_value|LLM_MODEL|model' packages/challenges/agent-challenge/src packages/challenges/agent-challenge || true

Repository: BaseIntelligence/base

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="packages/challenges/agent-challenge/src/agent_challenge/submissions/miner_env.py"
echo "--- miner_env.py ---"
cat -n "$file"

echo
echo "--- relevant call sites ---"
sed -n '150,220p' packages/challenges/agent-challenge/src/agent_challenge/evaluation/own_runner/isolation.py | cat -n
sed -n '1628,1648p' packages/challenges/agent-challenge/src/agent_challenge/evaluation/runner.py | cat -n
sed -n '1988,2002p' packages/challenges/agent-challenge/src/agent_challenge/evaluation/runner.py | cat -n

echo
echo "--- behavioral probe of miner_env validation ---"
python3 - <<'PY'
import sys
sys.path.insert(0, 'packages/challenges/agent-challenge/src')
from agent_challenge.submissions.miner_env import (
    MINER_ENV_PRODUCT_ALLOWLIST,
    is_token_or_key_env_name,
    looks_like_url_value,
)

cases = {
    "LLM_MODEL": "x-ai/grok-4.5",
    "OPENROUTER_API_KEY": "sk-or-prod-secret",
    "LLM_MODEL_URL_HOST": "https://evil.internal.example/model",
    "LLM_MODEL_BEARER": "Bearer token=123",
    "X_VALID_KEY": "abc123",
    "PORT": "80",
}
for name, value in case := cases.items():
    print(name, {
        "is_allowed_token_or_key": is_token_or_key_env_name(name),
        "looks_like_url": looks_like_url_value(value),
        "upper_allowed": name.upper() in MINER_ENV_PRODUCT_ALLOWLIST,
        "value": value,
    })
PY

Repository: BaseIntelligence/base

Length of output: 13298


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "--- behavioral probe of miner_env validation ---"
python3 - <<'PY'
import sys
sys.path.insert(0, 'packages/challenges/agent-challenge/src')
from agent_challenge.submissions.miner_env import (
    MINER_ENV_PRODUCT_ALLOWLIST,
    is_token_or_key_env_name,
    looks_like_url_value,
)

cases = {
    "LLM_MODEL": "x-ai/grok-4.5",
    "LLM_MODEL_SECRET": "x-ai/grok-4.5",
    "OPENROUTER_API_KEY": "sk-or-prod-secret",
    "LLM_MODEL_URL_HOST": "https://evil.internal.example/model",
    "LLM_MODEL_BEARER": "Bearer token=123",
    "X_VALID_KEY": "abc123",
    "PORT": "80",
}
for name, value in cases.items():
    print(name, {
        "is_allowed_token_or_key": is_token_or_key_env_name(name),
        "looks_like_url": looks_like_url_value(value),
        "upper_allowed": name.upper() in MINER_ENV_PRODUCT_ALLOWLIST,
        "value": value,
    })
PY

echo
echo "--- submit/env route / API route env handling slices ---"
file="packages/challenges/agent-challenge/src/agent_challenge/api/routes.py"
sed -n '2820,2890p' "$file" | cat -n
sed -n '2898,2918p' "$file" | cat -n
rg -n "validate_miner_env|sanitize_miner_env_for_job|miner_env|SUBMIT.*ENV|env" "$file" -A 4 -B 4

Repository: BaseIntelligence/base

Length of output: 33243


Do not add LLM_MODEL to the secret allowlist.

MINER_ENV_PRODUCT_ALLOWLIST is the “API key / token / measured product secret” gate; LLM_MODEL is an OpenRouter model identifier, not a secret. Keeping it here lets miners submit and persist arbitrary model names through validate_miner_env()/sanitize_miner_env_for_job(); use a separate narrowly scoped model-id validator/allowlist instead.

🤖 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
`@packages/challenges/agent-challenge/src/agent_challenge/submissions/miner_env.py`
around lines 49 - 52, Remove LLM_MODEL from MINER_ENV_PRODUCT_ALLOWLIST and keep
it out of the secret validation path used by validate_miner_env() and
sanitize_miner_env_for_job(). Handle miner-provided model identifiers through a
separate narrowly scoped model-ID validator or allowlist, preserving rejection
of URL-shaped or endpoint-like values.

Source: Coding guidelines

"EVAL_RUN_TOKEN",
"REVIEW_SESSION_TOKEN",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,14 @@ async def test_deepseek_no_longer_routes_through_master_gateway(
assert "BASE_LLM_GATEWAY_URL" not in env
assert "BASE_GATEWAY_TOKEN" not in env
assert "DEEPSEEK_API_KEY" not in env
assert "LLM_MODEL" not in env
# VAL-LLM-MODEL: LLM_MODEL is no longer gateway-derived. It is supplied
# from measured platform settings because the packaged agent fails closed
# without a concrete model id, so its presence is NOT gateway injection.
# Assert it carries the settings value and that nothing from the residual
# gateway (base_url / token) leaks into the job env.
assert env.get("LLM_MODEL") == "x-ai/grok-4.5"
assert "master-gateway.test" not in str(env)
assert "scoped-assignment-token" not in str(env)
serialized = json.dumps(env, sort_keys=True)
assert "api.deepseek.com" not in serialized

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""The LLM model id must reach the agent (VAL-LLM-MODEL).

The packaged agent fails closed when ``LLM_MODEL`` is unset::

agent run failed: A concrete model id is required: set LLM_MODEL
(the measured review harness supplies it under .rules).

Three independent layers dropped it, so *every* evaluation scored 0 and every
``result.json`` carried ``"model_name": null``:

1. the runner never set ``LLM_MODEL`` -- the platform only holds
``CHALLENGE_LLM_MODEL`` (settings ``llm_model``), which the agent never reads;
2. :func:`sanitize_miner_env_for_job` stripped a miner-supplied ``LLM_MODEL``
because the name is not token/key shaped;
3. :data:`AGENT_ENV_ALLOWLIST` admitted only ``LLM_COST_LIMIT`` and
``OPENROUTER_API_KEY``.

A miner may bring their own key *and* their own model; when they supply neither,
the measured platform model is used.
"""

from __future__ import annotations

from agent_challenge.core.config import settings
from agent_challenge.evaluation.own_runner.isolation import (
AGENT_ENV_ALLOWLIST,
filter_agent_env,
)
from agent_challenge.evaluation.runner import _terminal_bench_env
from agent_challenge.submissions.miner_env import (
sanitize_miner_env_for_job,
validate_miner_env,
)


def test_agent_env_allowlist_admits_llm_model() -> None:
"""Layer 3: the agent sandbox must be allowed to see the model id."""
assert "LLM_MODEL" in AGENT_ENV_ALLOWLIST


def test_filter_agent_env_keeps_llm_model() -> None:
"""Layer 3: the final filter must not strip the model id."""
kept = filter_agent_env({"LLM_MODEL": "x-ai/grok-4.5", "OPENROUTER_API_KEY": "sk-or-x"})
assert kept["LLM_MODEL"] == "x-ai/grok-4.5"
assert kept["OPENROUTER_API_KEY"] == "sk-or-x"


def test_runner_injects_platform_llm_model() -> None:
"""Layer 1: with no miner env, the measured platform model is supplied."""
env = _terminal_bench_env()
assert env["LLM_MODEL"] == settings.llm_model
assert env["LLM_MODEL"], "platform model id must not be empty"


def test_miner_may_supply_own_llm_model() -> None:
"""Layer 2: a miner bringing their own key may also choose their model."""
assert sanitize_miner_env_for_job({"LLM_MODEL": "openai/gpt-5"}) == {
"LLM_MODEL": "openai/gpt-5"
}
assert validate_miner_env({"LLM_MODEL": "openai/gpt-5"}) == {"LLM_MODEL": "openai/gpt-5"}


def test_miner_llm_model_overrides_platform_default() -> None:
"""A miner-supplied model wins over the platform default."""
env = _terminal_bench_env({"LLM_MODEL": "openai/gpt-5"})
assert env["LLM_MODEL"] == "openai/gpt-5"


def test_llm_model_value_must_not_be_a_url() -> None:
"""Admitting the name must not open an endpoint-injection hole."""
assert sanitize_miner_env_for_job({"LLM_MODEL": "http://evil.example/v1"}) == {}


def test_llm_model_is_not_treated_as_a_secret() -> None:
"""The model id is not secret-shaped, so it must not be flagged as one."""
from agent_challenge.evaluation.own_runner.isolation import disallowed_secret_keys

assert "LLM_MODEL" not in disallowed_secret_keys({"LLM_MODEL": "x-ai/grok-4.5"})
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,13 @@ def test_terminal_bench_env_ignores_miner_base_log_stream_override() -> None:
# --------------------------------------------------------------------------- #
# VAL-ACLOCK-005 / 006 — agent sandbox allowlist + chokepoint
# --------------------------------------------------------------------------- #
def test_agent_env_allowlist_exactly_or_key_and_cost_limit() -> None:
assert AGENT_ENV_ALLOWLIST == frozenset({"OPENROUTER_API_KEY", "LLM_COST_LIMIT"})
def test_agent_env_allowlist_exactly_key_cost_limit_and_model() -> None:
# VAL-LLM-MODEL: LLM_MODEL joins the sandbox allowlist because the packaged
# agent fails closed without a concrete model id. The exact pin is
# deliberate: nothing else enters this set without an explicit decision.
assert AGENT_ENV_ALLOWLIST == frozenset(
{"OPENROUTER_API_KEY", "LLM_COST_LIMIT", "LLM_MODEL"}
)


def test_filter_agent_env_strips_url_proxy_and_extra_secrets() -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,12 @@ def test_allowlist_excludes_base_gateway_vars() -> None:
assert "BASE_LLM_GATEWAY_URL" not in AGENT_ENV_ALLOWLIST
assert "BASE_GATEWAY_TOKEN" not in AGENT_ENV_ALLOWLIST
assert "LLM_COST_LIMIT" in AGENT_ENV_ALLOWLIST
assert AGENT_ENV_ALLOWLIST == frozenset({"LLM_COST_LIMIT", "OPENROUTER_API_KEY"})
# VAL-LLM-MODEL: the packaged agent fails closed without a concrete model
# id, so LLM_MODEL is admitted alongside the key and cost limit. The
# gateway/URL/host/proxy exclusions above remain the invariant.
assert AGENT_ENV_ALLOWLIST == frozenset(
{"LLM_COST_LIMIT", "LLM_MODEL", "OPENROUTER_API_KEY"}
)


def test_harness_control_keys_are_not_secrets() -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,14 @@ async def test_worker_broker_path_scrubs_token_and_signature_metadata_and_keeps_
"hello-world",
]
benchmark_spec = executor.specs[1]
# VAL-LLM-MODEL: the measured model id now ships in the job env (the packaged
# agent fails closed without a concrete LLM_MODEL). It is a plain model name,
# not a secret -- the assert_no_untrusted_secret guards below still apply.
assert benchmark_spec.env == {
"BASE_AGENT_PATH": "/workspace/agent",
"BASE_BENCHMARK_DATASET": "terminal-bench/terminal-bench-2-1",
"HOME": "/tmp",
"LLM_MODEL": "x-ai/grok-4.5",
"XDG_CACHE_HOME": "/tmp/.cache",
}
assert_no_untrusted_secret(
Expand Down
Loading