-
Notifications
You must be signed in to change notification settings - Fork 2
test: integration tests for FileExecutor allowed_paths using Docker #2117
Description
Summary
Add Docker-based integration tests that verify FileExecutor file system access controls: allowed paths inside cwd, denied paths outside cwd, ~ path resolution, symlink boundaries, and path traversal attempts. Currently these behaviors are only testable in live agent sessions — CI has no automated coverage.
Relates to: #2115 (~ in allowed_paths silently blocks all file access)
Motivation
FileExecutor enforces allowed_paths config at runtime, but:
- Unit tests can only mock the FS — they don't catch real path resolution bugs (as fix(tools): FileExecutor does not expand ~ in allowed_paths, blocking all file access #2115 proved)
- The
~expansion bug was only found during a live CI session, not by any test - Path traversal and symlink boundary checks are security-critical and need real FS verification
- Docker provides a fully controlled, reproducible filesystem hierarchy for exactly this kind of test
Proposed Solution
Use the testcontainers crate (already in the project for Qdrant) to spin up a containerized environment per test, then run FileExecutor (or a minimal zeph agent) against it.
Docker image
Build a test-only image zeph-test-fs that:
- Contains a pre-baked directory structure:
/workspace/ ← simulated cwd (allowed) project/ src/main.rs Cargo.toml README.md /outside/ ← outside cwd (must be denied by default) secret.txt /tmp/allowed/ ← explicit extra allowed_path data.json /etc/passwd ← must always be denied (path traversal target) - Has a non-root user
zephwith limited permissions - Shares the
zephbinary or library under test via volume mount or multi-stage build
The image should be built in CI as part of the existing Docker build pipeline and pushed to the project registry for reuse.
Test structure
New crate or test file: tests/integration/file_access.rs (behind #[cfg(feature = "integration")] or #[ignore]).
use testcontainers::{clients::Cli, images::generic::GenericImage};
/// FileExecutor allows reads within cwd.
#[tokio::test]
#[ignore = "requires Docker"]
async fn file_executor_allows_read_inside_cwd() { ... }
/// FileExecutor denies reads outside cwd when allowed_paths is empty.
#[tokio::test]
#[ignore = "requires Docker"]
async fn file_executor_denies_read_outside_cwd() { ... }
/// FileExecutor resolves ~ in allowed_paths correctly (regression for #2115).
#[tokio::test]
#[ignore = "requires Docker"]
async fn file_executor_tilde_in_allowed_paths_resolves() { ... }
/// FileExecutor blocks path traversal attempts (../../etc/passwd).
#[tokio::test]
#[ignore = "requires Docker"]
async fn file_executor_blocks_path_traversal() { ... }
/// FileExecutor does not follow symlinks outside allowed boundary.
#[tokio::test]
#[ignore = "requires Docker"]
async fn file_executor_symlink_escape_blocked() { ... }
/// FileExecutor allows an explicitly configured extra allowed_path outside cwd.
#[tokio::test]
#[ignore = "requires Docker"]
async fn file_executor_explicit_allowed_path_outside_cwd() { ... }CI integration
Add a new GitHub Actions job integration-fs in .github/workflows/:
integration-fs:
runs-on: ubuntu-latest
needs: [build] # reuse the already-built Docker image
services:
docker:
image: docker:dind
steps:
- uses: actions/checkout@v4
- name: Pull test image
run: docker pull ghcr.io/bug-ops/zeph-test-fs:${{ github.sha }}
- name: Run FS integration tests
run: cargo nextest run --config-file .github/nextest.toml
--workspace --features integration --lib --bins
-E 'test(file_executor)'
env:
ZEPH_TEST_FS_IMAGE: ghcr.io/bug-ops/zeph-test-fs:${{ github.sha }}The image tag is pinned to the commit SHA so tests always run against the matching binary.
Implementation Plan
Phase 1 — Docker image + fixture FS (1 PR)
- Add
docker/test-fs/Dockerfilewith the fixture directory structure - Add build step to CI: build and push
zeph-test-fsimage - Add
ZEPH_TEST_FS_IMAGEenv var support to test harness
Phase 2 — Test harness + basic tests (1 PR)
- Add
tests/integration/file_access.rs - Implement
ZephFsTestContainerhelper (wrapstestcontainers::GenericImage) - Write tests: allow-inside-cwd, deny-outside-cwd, explicit-allowed-path
- Add
integrationfeature flag to workspace
Phase 3 — Security tests (1 PR)
- Tilde expansion regression test (covers fix(tools): FileExecutor does not expand ~ in allowed_paths, blocking all file access #2115)
- Path traversal tests
- Symlink escape tests
- Add to CI gate (block merge if these fail)
Acceptance Criteria
-
cargo nextest run --features integration -E 'test(file_executor)'passes in CI - Tilde expansion in
allowed_pathsis covered by a dedicated test (regression for fix(tools): FileExecutor does not expand ~ in allowed_paths, blocking all file access #2115) - Path traversal (
../../etc/passwd) is denied and covered by a test - Symlink escape is denied and covered by a test
- Tests are marked
#[ignore]locally but run automatically in theintegration-fsCI job - Docker image build is part of the existing CI pipeline (no extra build time on PRs that don't touch tools)
- Test output is human-readable: clearly states which path was tested, what was expected, what happened
Notes
testcontainersis already in the dependency tree for Qdrant tests — reuse that pattern- Tests should clean up containers on panic (testcontainers handles this automatically)
- Consider
docker composefor multi-container scenarios (e.g., agent + mock LLM + FS container) - Long-term: the same Docker infrastructure can be extended for
ShellExecutorsandbox tests