Private
Public Access
5-part fix to prevent test data loss outside ./tests/: 1. FR2 (root-cause): remove SLOP_CONFIG env var fallback from src/paths.py 2. --config CLI flag at entry point (sloppy.py for prod, conftest.py for tests) 3. FR1: sys.addaudithook runtime guard blocks writes outside ./tests/ 4. FR3: pytest --basetemp + isolate_workspace migration under ./tests/ 5. FR4: static audit (scripts/audit_test_sandbox_violations.py) + --strict CI gate Opt-in: FR5 Windows restricted-token wrapper (scripts/run_tests_sandboxed.ps1). 13 regression tests in tests/test_test_sandbox.py. Baseline: 1288 passed + 4 xdist-skipped (per result_migration_small_files_20260617). User directive: NO ENV VARS for config path. Use --config CLI flag. Test workspace file naming: config_overrides.toml (per user direction). Hard fail on any sandbox violation. Tests should never need AppData temp. Co-Authored-By: Claude <noreply@anthropic.com>
170 lines
9.9 KiB
JSON
170 lines
9.9 KiB
JSON
{
|
|
"track_id": "test_sandbox_hardening_20260619",
|
|
"name": "Test Sandbox Hardening",
|
|
"created": "2026-06-19",
|
|
"status": "spec_written",
|
|
"blocked_by": [],
|
|
"blocks": [],
|
|
"priority": "A",
|
|
"rationale": "User has lost important sample data multiple times over the past month because tests have written to top-level TOML files (manual_slop.toml, manual_slop_history.toml, personas.toml, presets.toml, tool_presets.toml, credentials.toml) at the project root. This track adds a 4-layer enforcement stack to make such writes impossible at the Python layer (default) and at the OS layer (opt-in).",
|
|
"scope": {
|
|
"new_files": [
|
|
"scripts/audit_test_sandbox_violations.py",
|
|
"scripts/run_tests_sandboxed.ps1",
|
|
"tests/test_test_sandbox.py",
|
|
"conductor/code_styleguides/test_sandbox.md"
|
|
],
|
|
"modified_files": [
|
|
"src/paths.py",
|
|
"src/models.py",
|
|
"sloppy.py",
|
|
"tests/conftest.py",
|
|
"pyproject.toml",
|
|
"conductor/tech-stack.md",
|
|
"conductor/code_styleguides/workspace_paths.md",
|
|
"docs/guide_testing.md",
|
|
".gitignore"
|
|
],
|
|
"deleted_files": []
|
|
},
|
|
"estimated_effort": {
|
|
"method": "scope (per workflow.md Tier 1 Track Initialization Rules). NO day estimates.",
|
|
"phase_1": "1 task: baseline pass-rate capture + verification that isolate_workspace + check_test_toml_paths work as documented",
|
|
"phase_2": "1 audit script + 4 regression tests + 1 commit",
|
|
"phase_3": "1 conftest fixture (Layer 1 audit hook) + 4 guard-specific regression tests + 1 commit",
|
|
"phase_4": "isolate_workspace migration + pyproject.toml addopts + tech-stack.md note + 1 commit",
|
|
"phase_5": "1 PowerShell wrapper (Layer 3) + 1 smoke test + 1 commit",
|
|
"phase_6": "2 doc files updated or 1 new styleguide + 1 commit",
|
|
"phase_7": "11-tier verification run + 1 commit (report)",
|
|
"phase_8": "1 end-of-track report + 1 commit",
|
|
"summary": "8 phases, ~8 commits, ~10-12 source files touched across scripts/, tests/, pyproject.toml, docs/, conductor/"
|
|
},
|
|
"verification_criteria": [
|
|
"tests/test_test_sandbox.py exists and all 13 tests pass",
|
|
"scripts/audit_test_sandbox_violations.py runs in both default and --strict modes",
|
|
"pyproject.toml contains addopts = '--basetemp=tests/artifacts/_pytest_tmp' under [tool.pytest.ini_options]",
|
|
"tests/conftest.py isolate_workspace no longer calls tmp_path_factory.mktemp (per workspace_paths.md); all env-var redirects point to paths inside ./tests/artifacts/",
|
|
"src/paths.py:get_config_path() does NOT call os.environ.get('SLOP_CONFIG', ...); uses set_config_override() instead",
|
|
"src/paths.py:set_config_override(path) exists and is callable from sloppy.py and conftest.py",
|
|
"sloppy.py accepts --config argparse argument and calls paths.set_config_override() before importing src/",
|
|
"tests/conftest.py parses sys.argv for --config at module body (BEFORE any src/ import); auto-defaults to tests/artifacts/_isolation_workspace_<RUN_ID>/config_overrides.toml",
|
|
"tests/artifacts/_isolation_workspace_<RUN_ID>/config_overrides.toml is auto-generated on every pytest run",
|
|
"conductor/code_styleguides/test_sandbox.md exists and documents the --config CLI flag + config_overrides.toml convention",
|
|
"scripts/run_tests_sandboxed.ps1 exists, parses cleanly, and on Windows can be invoked (-WhatIf mode for dry-run)",
|
|
"conductor/tech-stack.md has a dated note explaining the --basetemp choice",
|
|
"conductor/code_styleguides/workspace_paths.md or new test_sandbox.md documents the 3-layer model",
|
|
"Full test suite (11 tiers) runs to completion with no regression vs. pre-track baseline (1288 passed + 4 xdist-skipped per result_migration_small_files_20260617)",
|
|
"No new @pytest.mark.skip markers added (per conductor/workflow.md Skip-Marker Policy + user directive)",
|
|
"End-of-track report at docs/reports/TRACK_COMPLETION_test_sandbox_hardening_20260619.md"
|
|
],
|
|
"risk_register": [
|
|
{
|
|
"id": "R1",
|
|
"title": "Layer 1 audit hook breaks a test that legitimately writes outside ./tests/",
|
|
"likelihood": "medium",
|
|
"scope_impact": "the implementation may be larger than the spec suggests if many tests need to be migrated to tmp_path",
|
|
"mitigation": "allowlist includes pytest --basetemp; RuntimeError includes test name so offending test is obvious; add new paths to allowlist only via explicit allowlist update"
|
|
},
|
|
{
|
|
"id": "R2",
|
|
"title": "Layer 1 audit hook slows down the test suite",
|
|
"likelihood": "low",
|
|
"scope_impact": "minimal",
|
|
"mitigation": "sys.addaudithook is a thin C-level callback; overhead measured in <2% per Python docs"
|
|
},
|
|
{
|
|
"id": "R3",
|
|
"title": "Layer 4 audit flags a currently-passing test as a false positive",
|
|
"likelihood": "medium",
|
|
"scope_impact": "the implementation may be larger than the spec suggests if many tests need cleanup",
|
|
"mitigation": "audit is INFORMATIONAL by default; --strict is opt-in for CI; fix offending test rather than suppress audit"
|
|
},
|
|
{
|
|
"id": "R4",
|
|
"title": "Layer 3 PowerShell wrapper breaks on a Windows version without the required privileges",
|
|
"likelihood": "low",
|
|
"scope_impact": "minimal",
|
|
"mitigation": "wrapper is opt-in; default invocation stays uv run pytest; wrapper docs explain privilege requirements"
|
|
},
|
|
{
|
|
"id": "R5",
|
|
"title": "Existing tests that don't go through isolate_workspace still read real config files",
|
|
"likelihood": "high",
|
|
"scope_impact": "known gap, out of scope",
|
|
"mitigation": "Reads are out of scope per the Out of Scope section; Layer 1 still blocks writes which is the user's primary concern"
|
|
},
|
|
{
|
|
"id": "R7",
|
|
"title": "Removing SLOP_CONFIG env var fallback breaks code paths that relied on it",
|
|
"likelihood": "medium",
|
|
"scope_impact": "the implementation may be larger than the spec suggests if many call sites need updating",
|
|
"mitigation": "conftest.py auto-defaults to config_overrides.toml inside the test workspace; sloppy.py auto-defaults to root_dir/config.toml; the change should be transparent for any code that goes through get_config_path()"
|
|
},
|
|
{
|
|
"id": "R8",
|
|
"title": "conftest.py sys.argv parse at module body races with pytest's own argparse",
|
|
"likelihood": "low",
|
|
"scope_impact": "minimal",
|
|
"mitigation": "pytest_addoption registers --config so pytest doesn't warn about unknown flag; sys.argv parse at module body is a known-safe pattern (per conductor/tracks/test_infrastructure_hardening_20260609 conftest patterns)"
|
|
},
|
|
{
|
|
"id": "R6",
|
|
"title": "pytest_configure setting _tmp_path_factory._basetemp uses a private API that changes between versions",
|
|
"likelihood": "medium",
|
|
"scope_impact": "minimal",
|
|
"mitigation": "the --basetemp addopts is the primary mechanism; the _basetemp assignment is defensive only; if it breaks, addopts still works"
|
|
}
|
|
],
|
|
"architecture_reference": {
|
|
"primary_styleguide": "conductor/code_styleguides/workspace_paths.md",
|
|
"secondary_styleguides": [
|
|
"conductor/code_styleguides/feature_flags.md",
|
|
"conductor/code_styleguides/data_oriented_design.md"
|
|
],
|
|
"related_tracks": [
|
|
"conductor/archive/workspace_path_finalize_20260609/",
|
|
"conductor/tracks/tier2_autonomous_sandbox_20260616/",
|
|
"Test Consolidation & TOML Sandboxing (per conductor/tracks.md:395)"
|
|
],
|
|
"pattern_references": [
|
|
"scripts/audit_no_temp_writes.py (audit script pattern)",
|
|
"scripts/tier2/run_tier2_sandboxed.ps1 (PowerShell wrapper pattern)",
|
|
"scripts/check_test_toml_paths.py (existing static audit)"
|
|
]
|
|
},
|
|
"deferred_to_followup_tracks": [
|
|
{
|
|
"title": "Eliminate the remaining SLOP_* env vars (presets, credentials, etc.)",
|
|
"description": "This track only eliminates SLOP_CONFIG. The other 7 SLOP_* env vars (SLOP_GLOBAL_PRESETS, SLOP_GLOBAL_TOOL_PRESETS, SLOP_GLOBAL_PERSONAS, SLOP_GLOBAL_WORKSPACE_PROFILES, SLOP_CREDENTIALS, SLOP_MCP_ENV, SLOP_LOGS_DIR, SLOP_SCRIPTS_DIR) remain env-var-driven. Per user directive, this is the 'mess' to address in follow-up tracks. Same pattern: paths.set_<thing>_override() module-level + CLI flag at entry point.",
|
|
"track_status": "not yet specced"
|
|
},
|
|
{
|
|
"title": "Read-side isolation (block reads of real config from tests)",
|
|
"description": "Layer 1 only blocks writes; reads of real credentials.toml / config.toml still happen for tests that don't go through isolate_workspace. Future track could block reads via a stricter allowlist.",
|
|
"track_status": "not yet specced"
|
|
},
|
|
{
|
|
"title": "macOS/Linux OS-level sandbox wrapper",
|
|
"description": "Layer 3 is Windows-only (restricted token + Job Object). A run_tests_sandboxed.sh using bwrap/unshare would extend to macOS/Linux.",
|
|
"track_status": "not yet specced"
|
|
},
|
|
{
|
|
"title": "Per-fixture sandbox strictness tuning",
|
|
"description": "The blanket autouse fixture is the v1. A future track could add @pytest.fixture(sandbox_strict=True) for tests that need full OS isolation vs. the default Python guard.",
|
|
"track_status": "not yet specced"
|
|
}
|
|
],
|
|
"regressions_and_pre_existing_failures": [],
|
|
"pre_existing_failures_remaining": [],
|
|
"user_directives": [
|
|
"Hard sandbox for tests, similar to Tier 2 - completely banned from accessing files outside ./tests/",
|
|
"No new @pytest.mark.skip markers",
|
|
"User has lost important sample data multiple times - this is the primary motivation",
|
|
"NO ENV VARS for config path. Use --config CLI flag at the entry point (sloppy.py for production, conftest.py for tests)",
|
|
"Test workspace file naming: config_overrides.toml (per user direction)",
|
|
"Out of scope: converting the other SLOP_* env vars (presets, credentials, etc.) to CLI flags. User considers them a separate mess to address in follow-up tracks.",
|
|
"Hard fail on any sandbox violation (no warnings, no soft fails)",
|
|
"Tests should never need AppData temp (tempfile.mkdtemp/mkstemp without dir= is a flag)"
|
|
]
|
|
}
|