Extending SecureBench
This guide covers extension points in the active codebase. Follow existing module-per-family conventions so the directory tree stays navigable.
Extension overview
| Goal | Where to implement |
|---|---|
| New benchmark family schema | securebench/families/<name>.py + registry |
| New verifier | securebench/verifiers/<name>.py + registry |
| New harness type | securebench/harnesses/<name>.py + registry + tester_config.py |
| New sandbox backend | securebench/sandboxes/<name>.py |
| Eval visibility mapping | benchmark_compiler.EVAL_VISIBILITY |
| Path / materialization rules | workspaces/path_policy.py, workspaces/materialization.py |
Always add tests under tests/.
Add a benchmark family
1. Define the family contract
In families/registry.py:
FAMILY_CONTRACTS["my_family"] = FamilyContract(
"my_family",
"text", # text | code | patch | workspace
requires_workspace=False,
)2. Add row validation
Create families/my_family.py:
def validate(row, context: str) -> None:
reject_unknown(row.input, {"prompt"}, f"{context}.input")
reject_unknown(row.eval, {"answer"}, f"{context}.eval")
required_non_empty_string(row.input, "prompt", f"{context}.input")
# ...Register in FAMILY_VALIDATORS.
3. Map eval visibility
In benchmark_compiler.py:
EVAL_VISIBILITY["my_family"] = {
"answer": "hidden",
"checker": "evaluation_inputs",
}Unknown eval keys default to hidden.
4. Implement a verifier
See below. Without a verifier, tasks compile but score as pending.
5. Document threat model
Before marking the family production-ready, document agent-permitted mutations, network needs, and tamper tests in docs/SECURITY.md.
Add a verifier
1. Implement Verifier
# verifiers/my_family.py
class MyFamilyVerifier(Verifier):
def verify(self, task, candidate, **context):
# Read hidden data via resource_value(task, "answer")
# Never trust candidate content without validation
return VerificationResult(
task_id=task.id,
status="passed",
passed=True,
score=1.0,
metadata={"verifier": "my_family"},
)2. Register
In verifiers/registry.py:
if task_type == "my_family":
return MyFamilyVerifier()3. Export (optional)
Add to verifiers/__init__.py if part of public API.
4. Safe patterns
- Use
DockerSandbox(network="none")for untrusted code paths - Materialize eval inputs with
VisibilityAwareMaterializerat verification time - Accept
verification_policyfrom context when using dangerous commands - Inject
sandbox_factoryin tests to avoid real Docker
Add a harness type
1. Implement CandidateProducer
class MyHarnessProducer(CandidateProducer):
def produce(self, task: SecureBenchTask, **context) -> CandidateArtifact:
# Materialize agent resources only
# Run DockerSandbox
# extract_candidate(...)Use helpers from harnesses/shared.py and candidates/extraction.py.
2. Parse harness config
Add config parser (e.g. my_harness_config()) with reject_unknown_fields.
3. Wire registry
In harnesses/registry.py and tester_config.py:
- Add to
HarnessTypeandHARNESS_TYPES - Extend
build_harness_producer()
4. Network egress
If the harness needs outbound network, use docker_egress_policy() from harnesses/network.py with an explicit domain allowlist.
Do not reintroduce host-execution harness modes — project direction is Docker-only for candidate production (docs/next.md).
Add a sandbox backend
Implement the Sandbox ABC in sandboxes/base.py:
class MySandbox(Sandbox):
def run(self, command, *, workdir=None, timeout=None) -> CommandResult: ...
def write_file(self, path, content): ...
def read_file(self, path) -> str: ...
def extract_file(self, path) -> bytes: ...Export from sandboxes/__init__.py. Wrap with PolicySandbox when command auditing is needed.
Reuse resolve_sandbox_host_path() for host-backed roots.
Add validators (schema tightening)
Family validators are the primary schema layer. For cross-cutting asset object validation (path, mount, read_only, type), see planned work in docs/next.md.
To add shared validation:
- Extend helpers in
families/base.py - Call from family
validate()functions - Add tests in
tests/test_family_schemas.py
Add benchmark adapters
The active pipeline uses benchmark packs, not Hugging Face dataset adapters.
Legacy adapters live in legacy/securebench/adapters/ and are not wired to the CLI. For new integrations:
- Convert external datasets to manifest + JSONL rows
- Or build an offline conversion tool that emits
tasks.jsonl
Do not extend legacy adapter registry unless reviving that path intentionally.
Add tools / capabilities
SecureBench does not expose a general agent tool registry in the active package. Provider CLI harnesses (Codex, Claude Code) are the capability surface for agentic runs — they run with provider-defined tools inside the benchmark container.
For custom tool access:
- Use the
commandharness with a fixed wrapper script you trust, or - Integrate a new provider CLI harness following
harnesses/codex.pypatterns (overlay, egress policy, extraction)
Capability-based design intent: prefer narrow, tester-configured commands over unrestricted shell in verification paths.
Testing extensions
.venv/bin/python -m pytest tests/test_my_family_verifier.py -qFollow existing patterns:
tests/test_family_schemas.pyfor row validationtests/test_harnesses.py,tests/test_docker_sandbox.pyfor sandbox behaviortests/test_smoke_benchmark_packs.pyfor end-to-end pack smoke
Checklist before merging
- Family validator rejects unknown fields
- Eval visibility mapped in compiler
- Verifier registered (or explicitly documented as pending)
- Agent payload excludes hidden/eval secrets
- Tests cover pass, fail, and at least one tamper attempt
- Security notes updated in repo
docs/SECURITY.mdfor adversarial-facing families