Implement Phase 2: Phase-based operation restrictions - #243
Conversation
Add phase-based operation filtering to the SDLC pipeline. Each phase (refine, plan, implement, pr) now has a defined set of permitted and blocked operations. The gateway enforces these restrictions to prevent operations like git push during refine/plan phases. Key changes: - Add phase-permissions.schema.json defining the schema for phase permissions - Add phase-permissions.json configuration with default permissions - Add gateway/phase_filter.py for operation filtering logic - Add gateway/phase_transition.py for phase transition validation - Add gateway/phase_api.py with REST endpoints for phase operations - Add comprehensive unit tests for all new modules (69 new tests) This prevents incidents like #202 by making it technically impossible to push code during the planning phase. Issue: #133 Authored-by: egg
This comment has been minimized.
This comment has been minimized.
- Remove unused imports in gateway/phase_api.py: FilterResult, TransitionRequest, TransitionResult, create_audit_entry, validate_transition - Remove unused 're' import in gateway/phase_filter.py - Fix import sorting in gateway/phase_transition.py - Remove unused 'json' and 'tempfile' imports in tests/test_phase_api.py - Fix import sorting and remove unused imports in tests/test_phase_filter.py - Fix import sorting in tests/test_phase_transition.py Authored-by: egg
This comment has been minimized.
This comment has been minimized.
|
egg review failed. View run logs |
This comment has been minimized.
This comment has been minimized.
Apply ruff formatting to phase_filter.py and related test files. Remove unused format_error_message import from phase_filter.py. Authored-by: egg
This comment has been minimized.
This comment has been minimized.
|
egg review failed. View run logs |
There was a problem hiding this comment.
Code Review: Phase-Based Operation Restrictions
I've performed a thorough review of this PR. There are several issues that need to be addressed before this can be merged.
Critical Issues
1. Role Mapping Bug in phase_api.py:231-233 — Reviewer Cannot Advance from Implement Phase
mutation_result = apply_mutation(
contract=contract,
role=Role.HUMAN if role == TransitionRole.HUMAN else Role.REVIEWER,
...
)Problem: The current_phase field is not in FIELD_OWNERSHIP (see shared/egg_contracts/roles.py:27-46), so it defaults to Role.SYSTEM ownership. Per the can_modify function:
Role.HUMANcan modify everythingRole.REVIEWERcan only modify fields owned byRole.REVIEWER
This means when a TransitionRole.REVIEWER tries to advance from implement→pr, the apply_mutation call will fail with a permission error because Role.REVIEWER cannot modify current_phase.
Impact: The primary use case (reviewer advancing from implement→pr) is broken in production. Tests pass only because apply_mutation is mocked.
Fix: Either:
- Add
"current_phase": Role.REVIEWERtoFIELD_OWNERSHIPin roles.py (preferred — matches intent) - Always use
Role.HUMANfor phase transitions (security concern — defeats role hierarchy)
2. Path Traversal Vulnerability in phase_api.py:169, 313, 384
repo_path = Path(data.get("repo_path", "."))User-controlled repo_path is passed directly to load_contract() and save_contract() without validation. An attacker could:
- Read contracts from arbitrary paths:
../../../etc/some_contract.json - Potentially write contracts to arbitrary paths during phase advancement
Fix: Validate repo_path against an allowlist or ensure it's within a known safe directory:
repo_path = Path(data.get("repo_path", ".")).resolve()
if not repo_path.is_relative_to(ALLOWED_BASE_PATH):
return make_phase_error("Invalid repo_path", status_code=400)Design Issues
3. Inconsistent Pattern Matching Between JSON Config and Defaults
In .egg/phase-permissions.json (PR phase):
"pattern": "pr create*"In phase_filter.py:230 (default PR phase permissions):
Operation(OperationType.GH, "pr create *", "Create PRs"), # Note the space before *The pattern "pr create*" matches "pr creates" but the default "pr create *" does not. This inconsistency means behavior changes depending on whether the JSON file is loaded.
Impact: When the JSON config is missing (fallback to defaults), gh pr create commands may be unexpectedly blocked because the pattern requires a trailing argument.
Fix: Standardize patterns. Use "pr create*" (no space) if pr create with no args should match, or explicitly test both variants.
4. Global Singleton State in phase_filter.py:359
_filter: PhaseFilter | None = NoneThis module-level singleton is:
- Not thread-safe
- Loaded once and never refreshed (config changes require restart)
- Difficult to test without resetting global state (
phase_filter._filter = Nonein tests)
While not critical for correctness, consider adding a reset() function or using dependency injection for the Flask app context.
Missing Test Coverage
5. No Integration Test for Reviewer Advancing Implement→PR
The test_advance_phase_success test mocks apply_mutation, so it doesn't verify the actual mutation succeeds. There should be a test that:
- Creates a real contract in implement phase
- Calls advance with reviewer role
- Verifies the contract was actually updated (not mocked)
This test would have caught issue #1.
6. No Tests for Path Traversal Attacks
Add tests verifying that malicious repo_path values are rejected:
def test_advance_phase_path_traversal(self, client, auth_headers):
response = client.post(
"/api/v1/phase/advance",
headers=auth_headers,
json={
"issue_number": 123,
"repo_path": "../../../etc",
},
)
assert response.status_code == 4007. No Tests for Pattern Edge Cases
Missing tests for:
pr createwithout arguments (matchespr create*but notpr create *)- Commands that partially match blocked patterns but shouldn't (e.g.,
git push-status)
Minor Issues
8. Redundant PipelinePhase Definitions
There are now two PipelinePhase enums:
phase_filter.PipelinePhaseegg_contracts.models.PipelinePhase
This duplication means:
- Updates must be made in two places
- Type conversions are needed:
PipelinePhase(contract.current_phase.value)atphase_api.py:197
Suggestion: Import from egg_contracts.models instead of redefining.
9. Conftest Import Replacement Comment is Wrong
In gateway/tests/conftest.py:211-217:
# phase_filter imports from error_messages
phase_filter = _load_module_with_replaced_imports(
"phase_filter",
GATEWAY_DIR / "phase_filter.py",
import_replacements={
"from .error_messages import": "from error_messages import",
},
)But phase_filter.py doesn't import from error_messages. The replacement is a no-op.
10. Logging Inconsistency in phase_api.py:349
Blocked operations are logged at INFO level:
logger.info(
"Operation blocked by phase filter",
...
)But denied phase transitions are logged at WARNING level (line 211). Blocked operations should probably also be WARNING for consistency and alerting.
Summary
| Severity | Count | Items |
|---|---|---|
| Critical | 2 | Role mapping bug, path traversal |
| Design | 2 | Pattern inconsistency, global singleton |
| Testing | 3 | Missing integration/security/edge tests |
| Minor | 3 | Enum duplication, conftest comment, log levels |
The critical issues (#1 and #2) must be fixed before merge. The role mapping bug (#1) means the core use case doesn't work in production.
— Authored by egg
|
egg review completed. View run logs |
|
@james-in-a-box address the above issues |
Critical fixes: - Fix role mapping bug: Add current_phase to FIELD_OWNERSHIP as Role.REVIEWER to allow reviewers to advance from implement→pr phase - Fix path traversal vulnerability: Add validate_repo_path() to prevent access to contracts outside allowed directories Design improvements: - Standardize pattern matching: Use "pr create*" consistently in defaults to match JSON config behavior - Add reset_phase_filter() function for testing and config reload - Update blocked operation logging from INFO to WARNING for consistency Testing: - Add integration test for reviewer phase transition (non-mocked) - Add path traversal attack prevention tests - Add pattern edge case tests (pr create variants, partial matches) Minor: - Document PipelinePhase enum duplication intent - Fix conftest comment for phase_filter imports - Update test for current_phase ownership change Authored-by: egg
Review Feedback AddressedI've pushed fixes for all issues raised in the code review: Critical Issues (Fixed)
Design Issues (Fixed)
Testing (Added)
Minor Issues (Fixed)
Test Results
— Authored by egg |
|
egg is investigating the Lint check failure... |
|
egg finished successfully. View run logs |
|
egg autofix completed. View run logs |
|
egg review failed. View run logs |
There was a problem hiding this comment.
Re-Review: Changes Since Commit 3ce2638
I've reviewed the delta from commit 3ce2638cda646c62d6ffa67b896f66d76a2f4356 to 28dbb9a (1 commit: "Fix ruff formatting in test_phase_api.py").
Previous Issues — Status
All critical and design issues from my previous review have been properly addressed:
✅ Critical Issue #1: Role Mapping Bug (Fixed)
Change: Added "current_phase": Role.REVIEWER to FIELD_OWNERSHIP in roles.py:42
The fix is correct. Reviewers can now modify current_phase, which enables the implement→pr transition. The approach of making the reviewer the owner is appropriate since:
- The phase transition graph already requires reviewer role for implement→pr
- This aligns field-level permissions with the transition permission model
Verification: The new integration test test_reviewer_can_advance_implement_to_pr at test_phase_api.py:545-626 uses real contract mutations (not mocked apply_mutation) and confirms the fix works end-to-end.
✅ Critical Issue #2: Path Traversal Vulnerability (Fixed)
Change: Added validate_repo_path() function at phase_api.py:85-112 with validation applied to all three endpoints.
The implementation is sound:
- Uses
Path.resolve()to canonicalize paths (follows symlinks, resolves..) - Checks resolved path against allowlist using
is_relative_to() - Allows current directory (
.) as a special case — acceptable since CWD is container-controlled - Catches
OSError/ValueErrorfromresolve()for malformed paths
Edge cases verified:
../../../etc→ blocked (resolves outside allowed bases)/etc/passwd→ blocked (absolute path not in allowlist)../../from/home/egg/repos/egg→ blocked (goes to/home/egg, outside allowed bases)/home/egg/repos-evil→ blocked (is_relative_tocorrectly rejects sibling directories with matching prefixes)
✅ Design Issue #3: Pattern Inconsistency (Fixed)
Change: Standardized pattern from "pr create *" to "pr create*" at phase_filter.py:234
Now matches the JSON config format. The fnmatch pattern "pr create*" correctly matches:
"pr create"(no args)"pr create --title foo"(with args)
✅ Design Issue #4: Global Singleton (Fixed)
Change: Added reset_phase_filter() function at phase_filter.py:374-382
This enables proper test isolation and potential config reloading.
✅ Minor Issues (All Fixed)
- Enum duplication: Added explanatory comment at
phase_filter.py:28-31 - Conftest comment: Fixed incorrect comment at
conftest.py:211 - Logging consistency: Changed blocked operation logging from
INFOtoWARNINGatphase_api.py:398
New Code Quality
The new tests are well-structured:
-
Path traversal tests (
test_phase_api.py:465-530): Cover all three endpoints with traversal patterns and verify valid paths work. -
Integration test (
test_phase_api.py:538-626): Good approach — creates a real contract in a temp directory, patchesALLOWED_REPO_BASESto include the temp dir, and verifies the actual mutation persists. -
Pattern edge case tests (
test_phase_filter.py:326-376): Cover the important fnmatch edge cases. -
Reset function tests (
test_phase_filter.py:300-323): Verify the singleton reset behavior.
No Blocking Issues Found
All critical issues from the previous review have been properly addressed. The implementation is correct and the new tests provide good coverage.
LGTM — Ready for human review and merge.
— Authored by egg
|
egg review completed. View run logs |
|
egg review completed. View run logs |
Update gateway and architecture documentation to reflect the phase-based operation filtering system introduced in PR #243. This adds: - Phase-based policy rules table showing allowed/blocked operations per phase - New phase API endpoints (/api/v1/phase/*) - Updated file listing with phase_filter.py, phase_transition.py, phase_api.py - Phase-based access control in architecture overview These updates ensure the documentation accurately reflects the new phase restrictions that prevent operations like git push during refine/plan phases and gh pr create during implement phase.
* docs: Document phase-based operation restrictions Update gateway and architecture documentation to reflect the phase-based operation filtering system introduced in PR #243. This adds: - Phase-based policy rules table showing allowed/blocked operations per phase - New phase API endpoints (/api/v1/phase/*) - Updated file listing with phase_filter.py, phase_transition.py, phase_api.py - Phase-based access control in architecture overview These updates ensure the documentation accurately reflects the new phase restrictions that prevent operations like git push during refine/plan phases and gh pr create during implement phase. * Fix issue_number parameter documentation for /phase/filter endpoint The issue_number parameter is required, not optional, per the implementation in gateway/phase_api.py:350-351. Moved it to the front of the parameter list and removed the ? suffix to indicate required. * Add contract system documentation from PR #217 - Document contract API endpoints in gateway README - Add contract_api.py, auth.py, test_contract_api.py to file listings - Add SDLC Contracts section to architecture docs - Document role-based field ownership (implementer, reviewer, human) - Add egg_contracts library to components table Authored-by: egg * Fix documentation accuracy for phase permissions and role ownership - Add footnote noting egg-contract show is allowed in all phases - Add missing implement phase operations (update-notes, mark-phase) - Add actor? parameter to /api/v1/phase/advance endpoint docs - Complete role-based field ownership lists: - implementer: add files_affected - reviewer: add review_feedback, current_phase - human: add resolution, resolved_by, resolved_at Authored-by: egg --------- Co-authored-by: jwbron <8340608+jwbron@users.noreply.github.com>
Summary
Implements Phase 2 of the SDLC pipeline (issue #133): Phase-Based Operation Restrictions. This phase adds operation filtering to enforce that certain actions are only allowed during specific pipeline phases.
Key features:
.egg/schemas/phase-permissions.schema.json): Defines the schema for phase permission configuration.egg/phase-permissions.json): Default permission configuration for all phasesgateway/phase_filter.py): Core logic for filtering operations against phase permissionsgateway/phase_transition.py): Validates phase transitions against role requirementsgateway/phase_api.py): REST endpoints for phase operations:POST /api/v1/phase/advance- Advance to next phasePOST /api/v1/phase/filter- Check if operation is allowedGET /api/v1/phase/current/<issue>- Get current phaseGET /api/v1/phase/permissions/<phase>- Get phase permissionsPhase restrictions:
gh issue comment/editgit push,gh pr creategh issue comment/edit,egg-contract add-decisiongit push,gh pr creategit push,egg-contract add-commit/mark-taskgh pr creategh pr create/edit,git pushThis prevents incidents like #202 by making it technically impossible to push code during the planning phase.
Issue: #133
Test plan:
Authored-by: egg