Enforce per-task file restrictions in implement phase - #911
Enforce per-task file restrictions in implement phase#911james-in-a-box[bot] wants to merge 28 commits into
Conversation
Analyzes the architecture for enforcing per-task file restrictions from the planner in the implement phase. Recommends session-scoped allowed_files with warn-then-block escalation. Authored-by: egg
Container 0824c59d8c2a0c2a28f6b4ea6c798c291d77e89eac3fb7a95d42f79ac75716f3 exited with uncommitted changes. This commit preserves the agent's work-in-progress. Authored-by: egg
Assess 13 risks across correctness, implementation, security, compatibility, and operational categories. Overall risk: LOW-MEDIUM. Key findings: pattern matching inconsistency across validation layers, directory expansion semantics mismatch, lock file conflicts in parallel dispatch. No blocking risks identified. Authored-by: egg
Container 21026274c0a6e0a861c76b4746aaf29dc38083276623b609e86b605e1f933b31 exited with uncommitted changes. This commit preserves the agent's work-in-progress. Authored-by: egg
Container 20e89c65d0b3a9afd0b1025ffdeaf90bfafb46859595a7702885248a1249fd79 exited with uncommitted changes. This commit preserves the agent's work-in-progress. Authored-by: egg
Extends the gateway Session dataclass with allowed_files (list[str] | None) for per-task file restrictions in the implement phase. The field is persisted to disk, deserialized on load, and threaded through: - SessionManager.register_session() accepts allowed_files parameter - Gateway /api/v1/sessions/create validates and passes allowed_files - GatewayClient.register_session() includes allowed_files in request - ContainerSpawner computes allowed_files from contract files_affected Also adds Session.add_allowed_file() for dynamic allowlist expansion and SessionManager.update_session_allowed_files() for persistence. Issue: #805 (Phase 1: TASK-1-1 through TASK-1-5) Authored-by: egg
Implements warn-then-block semantics for per-task file boundaries: - build_session_file_restriction() in phase_filter.py creates a PhaseFileRestriction from session allowed_files intersected with phase-level blocked patterns (phase blocks always win) - Fourth push validation layer in gateway.py checks session allowed_files after role, agent, and phase checks - First push with out-of-scope files logs warning and succeeds; second push with same file returns 403 - Strict mode (EGG_TASK_FILE_RESTRICTIONS_ENFORCE=true) blocks immediately; threshold configurable via EGG_TASK_FILE_WARN_THRESHOLD - Post-agent auto-commit filters by session allowed_files, restoring out-of-scope files with clear log messages - Checkpoint pushes and non-implement phases exempt Issue: #805 (Phase 2: TASK-2-1 through TASK-2-3) Authored-by: egg
Implements the request-file escape hatch allowing agents to request access to files outside their task's allowlist: - egg-contract request-file --path <file> --reason <why> CLI command - POST /api/v1/sessions/request-file gateway endpoint - Default mode: auto-approves and adds file + parent dir glob to session allowed_files with structured audit logging - Strict mode (EGG_TASK_FILE_RESTRICTIONS_ENFORCE=true): queues HITL decision, returns 202 pending approval Issue: #805 (Phase 3: TASK-3-1, TASK-3-2, TASK-3-3) Authored-by: egg
The files: field in task definitions is now enforced by the gateway during the implement phase. Updates the template with: - Explanation that files: entries are enforced boundaries - Guidelines for generous file listing with globs - Directory-sibling expansion documentation - Escape hatch reference (egg-contract request-file) - YAML example showing glob patterns and config file inclusion Issue: #805 (Phase 4: TASK-4-1, TASK-4-2) Authored-by: egg
Tests cover: - build_session_file_restriction: allowed patterns, phase blocked pattern inheritance, intersection semantics, unknown phase - check_session_file_restrictions: empty/None inputs, in-scope/ out-of-scope files, mixed files, glob matching, phase override - Session model: allowed_files creation, _warned_files tracking, add_allowed_file with dedup and directory expansion - Persistence: allowed_files round-trip, None handling, _warned_files not persisted, update_session_allowed_files All 24 new tests pass. No regressions in existing tests. Issue: #805 (TASK-1-6, TASK-2-4, TASK-3-4) Authored-by: egg
Update gateway README with new Per-Task File Restrictions section covering session-level allowlist enforcement, warn-then-block semantics, escape hatch (egg-contract request-file), and the new POST /api/v1/sessions/request-file endpoint. Update SDLC pipeline guide structural enforcement list, multi-agent orchestration section, and post-agent auto-commit description. Add request-file command to sandbox contract CLI rules. Update STRUCTURE.md to reflect phase_filter.py changes and new test file. Authored-by: egg
Test gaps addressed: - Warn-then-block semantics: violation counting, threshold behavior - Session.add_allowed_file edge cases: root-level files, deep nesting - build_session_file_restriction: PR/refine phases, double-star globs - compute_allowed_files_from_contract: contract parsing, directory expansion, fallbacks, deduplication, root-level files - Spawner wiring: allowed_files passed to register_session - Post-agent commit session filtering: out-of-scope blocking, combined phase+session filtering, load helper function - SessionManager.update_session_allowed_files: invalid token case 63 new tests across 3 files, all 149 related tests pass. Authored-by: egg
- Remove unused imports in test files (types, json, os, sys, pytest, MagicMock, patch, FileRestrictionResult) - Fix import sorting in test_compute_allowed_files.py - Apply ruff formatting to 5 files - Remove stale type: ignore comment in post_agent_commit.py - Fix make_success call with unsupported status_code kwarg in gateway.py escape hatch endpoint (use make_response instead)
All checks passed: lint (ruff, mypy, shellcheck), tests (7035 passed), security (skipped - bandit not installed).
1. Fix keyword argument mismatch: load_contract() takes repo_root, not
repo_path. Also convert string path to Path object since the function
expects pathlib.Path.
2. Handle ValueError from int() parsing of EGG_TASK_FILE_WARN_THRESHOLD
env var to prevent crashes on misconfigured values.
3. Implement actual HITL decision queueing in strict mode via the
orchestrator /api/v1/pipelines/{id}/decisions endpoint. Falls back
to auto-approve with audit logging when orchestrator is unavailable.
4. Add integration tests that exercise real load_contract call path
(no mocks) to catch argument mismatches and type errors.
Update documentation across architecture, gateway, orchestrator, and structure docs to reflect the new per-session allowed_files feature: - architecture/README.md: Add per-task file boundaries to access control list - gateway/README.md: Add test_post_agent_commit_session_filter.py to test listing - gateway/post_agent_commit.py: Update auto_commit_worktree docstring to describe session-level filtering via allowed_files - orchestrator/README.md: Note allowed_files computation in container spawning - docs/development/STRUCTURE.md: Update container_spawner.py and session_manager.py descriptions to mention new responsibilities - docs/index.md: Bump last-updated date
New test file: gateway/tests/test_session_file_restriction_endpoints.py (30 tests) - session_request_file endpoint: auto-approve, strict HITL, validation, auth - session_create allowed_files input validation - Push handler warn-then-block integration via gateway endpoint - EGG_TASK_FILE_WARN_THRESHOLD ValueError handling verification - Session file restriction edge cases (union semantics, nested globs) - Security documentation tests (path traversal, null bytes in paths) Extended: orchestrator/tests/test_compute_allowed_files.py (+5 tests) - Contract with None phases/tasks (null safety) - Task with no files_affected or files attribute - Very large file lists - Mixed glob and regular file patterns
All checks passed: lint, test (7076 passed), security (skipped).
This comment has been minimized.
This comment has been minimized.
Autofix tracking{} |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
No agent-mode design concerns. This PR aligns well with the core principle ("the sandbox is the constraint") by moving file boundary enforcement from prompt-level guidance to gateway-level infrastructure. Agents retain full freedom to explore and read files; the restriction only constrains push/commit operations, which is the correct enforcement point. The warn-then-block escalation and escape hatch (egg-contract request-file) preserve the "guide, don't cage" philosophy.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Review: Per-task file restriction enforcement
Thorough review of all changed source files, tests, and documentation. The architecture is sound — session-scoped allowlists with warn-then-block is the right approach. However, there are several issues ranging from a correctness bug that defeats the stated purpose to security gaps and missing functionality.
1. CRITICAL — compute_allowed_files_from_contract collects ALL phases, not per-agent
File: orchestrator/container_spawner.py:98-103
The function collects files_affected from every task in every plan phase:
all_files: list[str] = []
for plan_phase in getattr(contract, "phases", []) or []:
for task in getattr(plan_phase, "tasks", []) or []:
files = getattr(task, "files", None) or getattr(task, "files_affected", None) or []
all_files.extend(files)This means Agent A (assigned to phase 1: src/auth/) and Agent B (assigned to phase 2: src/payments/) will both get the union of all files across all phases. The entire point of this PR — preventing cross-contamination in Tier 3 parallel dispatch — is undermined because every agent gets the same unrestricted allowlist.
The function needs to accept a plan_phase_index or assigned_tasks parameter and filter to only the tasks assigned to this specific agent. The spawn_agent_container caller knows which tasks/phase the agent is assigned to.
2. CRITICAL — Global config allowlist is not implemented
Files: orchestrator/container_spawner.py, gateway/phase_filter.py
The PR description states: "Config files (pyproject.toml, Makefile, etc.) are implicitly allowed." The architect output (AD-5) specifies a hardcoded default list: pyproject.toml, setup.py, setup.cfg, package.json, package-lock.json, yarn.lock, Makefile, requirements.txt, requirements-dev.txt, .gitignore, tsconfig.json, Cargo.toml, go.mod, go.sum.
This is not implemented anywhere. Neither compute_allowed_files_from_contract nor build_session_file_restriction nor check_session_file_restrictions adds implicit config file patterns. If a plan's files_affected doesn't list pyproject.toml, agents will be warned/blocked on dependency updates. This will cause agents to spin, which the architect specifically identified as a risk.
3. SECURITY — No path validation in session_request_file endpoint
File: gateway/gateway.py:3936-3940
The /api/v1/sessions/request-file endpoint accepts any string as path with no sanitization:
path = data.get("path")
if not path or not isinstance(path, str):
return make_error("Missing or invalid 'path'")
# No traversal validation — path goes straight to add_allowed_file()Path traversal (../../etc/passwd) and absolute paths (/etc/passwd) are accepted and added to the allowlist. While PhaseFileRestriction._normalize_path() rejects traversal during push validation (so these paths won't actually match git-committed files), the allowlist is polluted and the parent directory glob expansion in add_allowed_file creates concerning entries:
../../etc/passwd→ also adds../../etc/*/etc/passwd→ also adds/etc/*
The endpoint should reject paths containing .. or starting with /. This is a defense-in-depth issue — the current mitigation relies on downstream _normalize_path which raises ValueError that must be caught correctly at every call site.
4. CORRECTNESS — fnmatch.fnmatch with * matches across directory separators
File: gateway/phase_filter.py:244
_matches_pattern uses fnmatch.fnmatch for wildcard patterns:
if "*" in pattern:
return fnmatch.fnmatch(file_path, pattern)Python's fnmatch.fnmatch('src/deep/nested/file.py', 'src/*') returns True — the * matches / separators. This means the "directory-sibling expansion" that adds src/auth/* actually matches src/auth/sub/deep/file.py, not just immediate children. While this makes the restriction more permissive (not a security hole in the blocking direction), it contradicts the documented behavior:
- AD-4: "Subdirectory expansion (src/auth/utils/helpers.py) is not included by default"
- Test at line 3694:
test_single_star_does_not_match_nestedhas a conditional assertion that documents but doesn't actually assert the expected behavior
This is a pre-existing issue in _matches_pattern, but it directly affects the semantics this PR relies on. The distinction between * (one level) and ** (recursive) that the docs describe doesn't actually exist.
5. DESIGN — build_session_file_restriction accesses private internals
File: gateway/phase_filter.py:919-921
pf = get_phase_filter()
pf._load_permissions()
phase_restriction = pf._phase_file_restrictions.get(phase)This reaches into private _load_permissions() and private _phase_file_restrictions dict. While functionally correct (the method is idempotent with a _loaded guard), this creates coupling to internal implementation details. A public method like get_phase_restriction(phase) -> PhaseFileRestriction | None would be cleaner and wouldn't break if the internal storage changes.
6. MINOR — Comment says "non-implement phases are exempt" but code doesn't check phase
File: gateway/gateway.py:2042-2045
# Checkpoint pushes and non-implement phases are exempt.
if not is_checkpoint_push and hasattr(g, "session") and g.session:
session_allowed_files = getattr(g.session, "allowed_files", None)
if session_allowed_files and session_phase:The comment says non-implement phases are exempt, but the code checks if session_phase (truthy), not if session_phase == "implement". The exemption currently works because the orchestrator only sets allowed_files for implement phase, but the comment implies a guarantee the code doesn't enforce. Either add the explicit check or fix the comment.
7. MINOR — _warned_files in-memory limitation is documented but worth flagging
The warn-then-block counter lives in _warned_files on the in-memory Session object. This works correctly within a single gateway process (since validate_session returns the same object from the cache). However, if the gateway restarts mid-session, the counter resets and the agent gets another free warning. This is documented as intentional (fail-open), which is a reasonable choice. Just noting that the "warn-then-block" protection is weaker than it appears — it's really "warn-then-block-within-a-single-gateway-lifetime."
8. TESTING — test_single_star_does_not_match_nested doesn't assert expected behavior
File: gateway/tests/test_task_file_restrictions.py:3685-3699
def test_single_star_does_not_match_nested(self):
"""Single-star glob should not match nested subdirectories."""
result = check_session_file_restrictions(
["src/*"], "implement", ["src/deep/nested/file.py"],
)
# ...
if not result.allowed:
assert "src/deep/nested/file.py" in result.blocked_filesThis test name and docstring claim * should not match nested paths, but the assertion is conditional — it passes whether or not the file is blocked. Since fnmatch does match nested paths with *, this test always passes via the implicit else (no assertion). This should either assert the actual expected behavior or be renamed to test_single_star_matches_nested_due_to_fnmatch.
Summary
| # | Severity | Issue |
|---|---|---|
| 1 | Critical | Allowlist collects all phases, not per-agent — defeats Tier 3 isolation |
| 2 | Critical | Global config allowlist not implemented despite being documented |
| 3 | Security | No path validation in request-file endpoint |
| 4 | Correctness | fnmatch * matches across / — contradicts documented sibling-only expansion |
| 5 | Design | Accesses private PhaseFilter internals |
| 6 | Minor | Comment/code mismatch on phase exemption |
| 7 | Minor | _warned_files in-memory limitation |
| 8 | Testing | Test doesn't assert its stated behavior |
Issues 1 and 2 need to be fixed before merge. Issue 3 should be fixed (defense in depth). The rest are advisory.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Fix critical, security, and correctness issues from review: 1. CRITICAL: compute_allowed_files_from_contract now accepts plan_phase_id to filter files to the agent's assigned phase, preventing cross-phase allowlist leakage in Tier 3 parallel dispatch. _run_and_wait passes plan_phase_id through to spawn_agent_container. 2. CRITICAL: Add GLOBAL_CONFIG_ALLOWLIST constant (pyproject.toml, Makefile, package.json, etc.) and merge it into build_session_file_restriction so agents aren't blocked on dependency/config updates. 3. SECURITY: Reject path traversal (..) and absolute paths (/) in the session_request_file endpoint before adding to allowlist. 4. CORRECTNESS: Fix _matches_pattern to distinguish single-star (one level) from double-star (recursive) glob semantics. Single * no longer matches across directory separators via fnmatch. 5. DESIGN: Add public get_phase_file_restriction() method to PhaseFilter; build_session_file_restriction now uses it instead of private internals. 6. MINOR: Fix misleading comment about non-implement phase exemption. 7. TESTING: test_single_star_does_not_match_nested now asserts the correct behavior. Update test using ** for recursive matching. Fix mock sessions missing allowed_files=None.
There was a problem hiding this comment.
Review feedback addressed
All actionable items from the review have been fixed in commit 639b4d0:
1. CRITICAL — Per-agent phase filtering ✅
compute_allowed_files_from_contract now accepts a plan_phase_id parameter and filters tasks to only the assigned phase. _run_and_wait passes plan_phase_id through to spawn_agent_container. In Tier 3 dispatch, each agent now gets only its own phase's files.
2. CRITICAL — Global config allowlist ✅
Added GLOBAL_CONFIG_ALLOWLIST constant with the AD-5 specified files (pyproject.toml, package.json, Makefile, etc.). build_session_file_restriction merges these into every session's allowed patterns automatically.
3. SECURITY — Path validation ✅
session_request_file endpoint now rejects paths containing .. components or starting with / before they reach the allowlist. Returns 400 Bad Request.
4. CORRECTNESS — Single-star glob semantics ✅
_matches_pattern now distinguishes * (single directory level) from ** (recursive). Single-star patterns split path and pattern into segments and match component-by-component. This makes src/* match src/file.py but NOT src/deep/nested/file.py.
5. DESIGN — Public API for phase restrictions ✅
Added get_phase_file_restriction(phase) public method to PhaseFilter. build_session_file_restriction now uses this instead of accessing _load_permissions() and _phase_file_restrictions directly.
6. MINOR — Comment accuracy ✅
Updated comment to accurately state that non-implement phases are "implicitly exempt because the orchestrator only sets allowed_files for implement-phase sessions" rather than claiming the code checks for it.
7. MINOR — _warned_files in-memory limitation
Acknowledged. This is documented as intentional fail-open behavior. No code change needed.
8. TESTING — Proper assertions ✅
test_single_star_does_not_match_nested now asserts not result.allowed instead of using a conditional assertion. Updated test_phasefilerestriction_is_file_allowed_blocked_priority to use ** for recursive patterns. Fixed mock sessions missing allowed_files=None.
All 7076 tests pass. Lint clean.
— Authored by egg
There was a problem hiding this comment.
All 8 items from the previous review have been addressed in the delta (commit 639b4d0):
- #1 Per-agent phase filtering:
compute_allowed_files_from_contractnow filters byplan_phase_id, threaded through_spawn_and_wait— Tier 3 isolation works correctly. - #2 Global config allowlist:
GLOBAL_CONFIG_ALLOWLISTadded and merged into every session viabuild_session_file_restriction. - #3 Path validation:
session_request_filerejects..and absolute paths. - #4 Single-star semantics:
_matches_patternnow splits on/for single-star patterns, correctly distinguishing*(one level) from**(recursive). - #5 Public API:
get_phase_file_restriction()replaces direct access to private internals. - #6–#8: Comment fixed, test assertions made unconditional, mock sessions updated.
No new agent-mode design concerns in the delta. The enforcement remains correctly at the gateway/infrastructure layer — agents retain full read access and are only constrained at push/commit time, which aligns with "the sandbox is the constraint."
— Authored by egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: Per-task file restriction enforcement (commit 639b4d0)
Reviewed delta from 11f43ee to 639b4d0. All 8 issues from the prior review have been addressed. The fixes are substantive, not cosmetic. Detailed verification below, followed by one new advisory note.
Prior issues — verification
1. CRITICAL — Per-agent phase filtering: Fixed. compute_allowed_files_from_contract now accepts plan_phase_id and filters to the assigned phase. All Tier 3 _spawn_and_wait call sites (coder, tester, documenter, checker, reviewer) pass plan_phase_id=phase_id. Tier 1 and Tier 2 paths correctly omit it (defaulting to None = all phases), which is the right behavior since they don't have per-phase agent isolation.
2. CRITICAL — Global config allowlist: Fixed. GLOBAL_CONFIG_ALLOWLIST constant added with the AD-5 specified files. build_session_file_restriction merges them into combined_allowed before creating the restriction.
3. SECURITY — Path validation: Fixed. session_request_file now rejects .. components (via path.split("/")) and absolute paths (path.startswith("/")). Returns 400. Defense in depth is properly layered with the downstream _normalize_path check.
4. CORRECTNESS — Single-star glob semantics: Fixed. _matches_pattern now splits single-star patterns into path segments and matches component-by-component. src/* correctly fails to match src/deep/nested/file.py because segment counts differ (2 != 4). Double-star patterns (**) still use fnmatch.fnmatch directly, which correctly matches across / separators for suffix patterns like tests/**.
5. DESIGN — Public API for phase restrictions: Fixed. New get_phase_file_restriction(phase) public method on PhaseFilter. build_session_file_restriction uses it instead of reaching into _load_permissions() and _phase_file_restrictions.
6. MINOR — Comment accuracy: Fixed. Comment now correctly states non-implement phases are "implicitly exempt because the orchestrator only sets allowed_files for implement-phase sessions."
7. MINOR — _warned_files in-memory limitation: Acknowledged as intentional. No change needed.
8. TESTING — Proper assertions: Fixed. test_single_star_does_not_match_nested now unconditionally asserts not result.allowed and checks the blocked file. test_phasefilerestriction_is_file_allowed_blocked_priority updated to use ** for recursive matching. Mock sessions updated with allowed_files=None.
New observations (advisory)
A. _matches_pattern prefix fallback is overly broad for exact file entries
_matches_pattern (phase_filter.py:281) falls through to file_path.startswith(pattern) for non-glob, non-directory patterns. This means a GLOBAL_CONFIG_ALLOWLIST entry like "pyproject.toml" also matches "pyproject.toml.bak", and "Makefile" matches "MakefileFoo".
This is pre-existing behavior (not introduced by this commit), and since it affects allowlist patterns (permissive direction only), it's not a security issue — it just means the allowlist is slightly wider than intended. However, now that GLOBAL_CONFIG_ALLOWLIST depends on this method, it's worth noting for a future cleanup: the fallback should use exact match (==) for non-directory, non-glob patterns, with startswith reserved for patterns ending in /.
This is non-blocking. The current behavior errs on the permissive side, which is the right failure mode for an allowlist.
No blocking issues found. The critical fixes are correctly implemented and the threading of plan_phase_id through the Tier 3 dispatch path is complete. The test updates properly assert the expected behavior.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Use == instead of startswith for non-directory, non-glob patterns in PhaseFileRestriction._matches_pattern(). This prevents 'pyproject.toml' from matching 'pyproject.toml.bak' or 'Makefile' from matching 'MakefileFoo'. Directory patterns (ending with /) still use startswith. Addresses advisory item A from the re-review. Authored-by: egg
Advisory item A addressedFixed in aa0a567. This ensures Updated the test All 289 tests across the affected test files pass. Lint clean. — Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-reviewed delta from 639b4d0 to aa0a567 (1 commit). The advisory item A from my previous re-review has been correctly addressed — _matches_pattern now uses exact match (==) for non-directory, non-glob patterns while preserving startswith for directory patterns. Test updated to assert the corrected behavior.
No agent-mode design concerns. The enforcement model remains correctly at the gateway/infrastructure layer.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: Advisory item A fix (commit aa0a567)
Reviewed delta from 639b4d0 to aa0a567. The single change addresses the advisory from the previous review.
Advisory A — _matches_pattern prefix fallback: Fixed.
The startswith fallback for non-glob, non-directory patterns is now exact match (==). This correctly prevents "pyproject.toml" from matching "pyproject.toml.bak" and "Makefile" from matching "MakefileFoo". The test test_exact_file_does_not_match_similar_name is updated to assert the corrected behavior.
Verified that no existing patterns in the codebase depend on the old startswith behavior for non-directory, non-glob entries:
- All directory patterns use trailing
/(handled by theendswith("/")branch) - All wildcard patterns contain
*(handled by the glob branches) - All plain-string patterns are root-level filenames (
pyproject.toml,Makefile, etc.) where exact match is the intended behavior
Minor documentation nits (non-blocking)
-
Stale docstring in
_matches_pattern(phase_filter.py:256): Still says "Exact prefix match" — should add "Exact file match" to the supported list since non-glob non-directory patterns now use==instead ofstartswith. -
Stale test docstring at
test_exact_file_match(test_task_file_restrictions.py:514): Still says "matches via prefix matching" — should say "matches via exact match".
Neither of these affect behavior.
CI passes (lint, unit tests, security scan all green).
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Update _matches_pattern docstring to list both directory prefix match and exact file match as separate supported modes, replacing the old "Exact prefix match" bullet that predates the startswith-to-== change. Update test_exact_file_match docstring to say "exact match" instead of "prefix matching". Authored-by: egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: Docstring fixes (commit afc57ea)
Delta since aa0a567: 2 files, 3 insertions, 2 deletions. Documentation-only changes.
Changes verified
Both non-blocking suggestions from my prior review are correctly addressed:
-
_matches_patterndocstring (phase_filter.py:253-260): "Exact prefix match" replaced with "Directory prefix match" and "Exact file match" as separate entries. Accurately reflects the two code paths at lines 278-285 (startswithfor directory patterns,==for plain strings). -
test_exact_file_matchdocstring (test_task_file_restrictions.py:514): "prefix matching" → "exact match". Matches the test's assertion.
No behavioral changes. No new issues.
— Authored by egg
There was a problem hiding this comment.
Re-reviewed delta since aa0a567 (branch rebased to afc57ea). The only change is a docstring fix in _matches_pattern and test_exact_file_match to accurately reflect the exact-match semantics introduced in the prior round. No new code, no new agent-mode design concerns.
The overall design remains well-aligned: file boundary enforcement lives at the gateway/infrastructure layer, agents retain full read access, and constraints are only applied at push/commit time.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| egg is addressing review feedback... |
|
egg feedback addressed. View run logs 15 previous review(s) hidden. |
Enforce per-task file boundaries from the planner's
files_affectedlists during the implement phase, preventing cross-contamination when multiple agents work in parallel (Tier 3 dispatch).Today the implement phase blocks
.egg-state/directories but allows modification of any code file. This means an agent working on task A can accidentally modify files belonging to task B. This PR wires the plan's per-taskfiles_affectedlists through session registration so the gateway enforces them at push time.Key changes:
gateway/session_manager.py): Addedallowed_filesfield toSessionwithregister_session()support and a/sessions/{id}/allowed-filesmanagement endpointgateway/gateway.py): Warn-then-block enforcement — first out-of-scope file per session triggers a structured warning, repeated violations block the push with an actionable errorgateway/phase_filter.py): Newcheck_session_file_restrictions()with glob and directory-sibling expansion (listingdir/foo.pyimplicitly allows other files indir/)gateway/post_agent_commit.py): Filters uncommitted changes against the session's allowlist — out-of-scope files are restored, not silently dropped, with clear loggingorchestrator/container_spawner.py):compute_allowed_files()collects and expandsfiles_affectedfrom assigned tasks, auto-adding directory-level and common config patternssandbox/egg_lib/contract_cli.py):egg-contract request-file --path <file> --reason <why>for agents that legitimately need a file outside scopedocs/templates/plan.md): Documents thatfiles:is an enforced boundary with guidance on generous glob-based listingGraceful fallback: Empty
files_affectedmeans no per-file restriction (only phase-level). Missing or malformed entries are skipped, not fatal. Config files (pyproject.toml,Makefile, etc.) are implicitly allowed.Test coverage: ~1,500 lines of new tests across gateway push validation, warn-then-block escalation, directory-sibling expansion, post-agent commit filtering, and container spawner file computation.
Closes #805
Test plan:
pytest gateway/tests/test_task_file_restrictions.py— core per-session restriction logicpytest gateway/tests/test_session_file_restriction_endpoints.py— API endpoint integrationpytest gateway/tests/test_post_agent_commit_session_filter.py— post-agent commit filteringpytest orchestrator/tests/test_compute_allowed_files.py— spawner file computation and expansionfiles_affected= no per-file restrictionAuthored-by: egg