Files
ironclaw/crates
Nick Stebbings 81bb705716 feat(jobs): per-job MCP server filtering and max_iterations cap (#1243)
* feat(jobs): per-job MCP server filtering and max_iterations cap

Add mcp_servers and max_iterations optional params to create_job.
mcp_servers filters which MCP servers are mounted into worker
containers (gated behind MCP_PER_JOB_ENABLED, default false).
max_iterations caps the worker agent loop (default 50, max 500).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address review feedback on per-job MCP filtering

- Fix max_iterations dead code: add env = "IRONCLAW_MAX_ITERATIONS"
  to clap arg so worker CLI reads the env var injected by orchestrator
- Fix max_iterations: 0 allowed: use .clamp(1, 500) instead of .min(500)
- Replace hardcoded /tmp/ironclaw-mcp-configs with std::env::temp_dir()
- Make MCP server name matching case-insensitive
- Add test for case-insensitive matching
- Add test verifying max_iterations env var name matches clap definition

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Copilot review feedback on per-job MCP filtering

- Guard IRONCLAW_MAX_ITERATIONS injection to Worker mode only (ClaudeCode uses max_turns)
- Extract WORKER_MCP_CONFIG_PATH as constant (no more hardcoded path)
- Fix TOCTOU race in cleanup_job: use remove_file directly, match on NotFound
- Fix schema_version default: 0 → 1 to match McpServersFile default
- Propagate serialization errors instead of silently writing empty config
- Add type validation warnings for mcp_servers and max_iterations params

* test: add regression tests and security hardening for per-job MCP filtering

Add 5 regression tests covering CI-required scenarios:
- Filtered config contains only the requested server (no leaks)
- Feature flag disabled skips MCP filtering entirely
- Temp file cleanup removes per-job config
- cleanup_job is idempotent (no panic on missing file/handle)
- Temp directory has restrictive 0o700 permissions (unix)

Security: set 0o700 permissions on /tmp/ironclaw-mcp-configs/ to prevent
other users on the host from reading filtered MCP server configs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address code review — server-side clamp, JobCreationParams, name validation

Critical:
1. Server-side max_iterations clamp in create_job_inner — defense no longer
   relies solely on tool parameter parsing. Uses MAX_WORKER_ITERATIONS constant
   (matching worker/job.rs) so the cap is enforced even for direct API calls.

2. Introduce JobCreationParams struct to bundle credential_grants, mcp_servers,
   and max_iterations. Removes #[allow(clippy::too_many_arguments)] from both
   create_job and execute_sandbox (7→5 and 9→7 positional args).

Important:
3. Validate MCP server names: reject path separators (/\), null bytes, and
   names longer than 128 chars to prevent future misuse.

5. Add test verifying max_iterations is NOT injected for ClaudeCode mode.
   Add test verifying server-side clamp uses MAX_WORKER_ITERATIONS constant.
   Add test verifying name validation rejects path separators and null bytes.

* fix: async I/O in generate_worker_mcp_config, shared MAX_WORKER_ITERATIONS

1. Convert generate_worker_mcp_config from sync std::fs to async tokio::fs.
   The function is called from async create_job_inner — sync I/O was blocking
   the tokio runtime thread. All test callers converted to #[tokio::test].

2. Move MAX_WORKER_ITERATIONS (500) to ironclaw_common as single source of
   truth. Both src/orchestrator/job_manager.rs and src/worker/job.rs now
   import from the shared crate, preventing drift.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 16:58:35 +02:00
..