nono-py 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. nono_py-0.1.0/.github/workflows/ci.yml +145 -0
  2. nono_py-0.1.0/.github/workflows/docs-dispatch.yml +19 -0
  3. nono_py-0.1.0/.github/workflows/publish.yml +170 -0
  4. nono_py-0.1.0/.github/workflows/release.yml +28 -0
  5. nono_py-0.1.0/.gitignore +38 -0
  6. nono_py-0.1.0/.python-version +1 -0
  7. nono_py-0.1.0/CLAUDE.md +67 -0
  8. nono_py-0.1.0/Cargo.lock +1026 -0
  9. nono_py-0.1.0/Cargo.toml +18 -0
  10. nono_py-0.1.0/DEVELOPMENT.md +320 -0
  11. nono_py-0.1.0/LICENSE +201 -0
  12. nono_py-0.1.0/Makefile +101 -0
  13. nono_py-0.1.0/PKG-INFO +239 -0
  14. nono_py-0.1.0/README.md +208 -0
  15. nono_py-0.1.0/assets/nono-py.png +0 -0
  16. nono_py-0.1.0/docs/assets/nono-py.png +0 -0
  17. nono_py-0.1.0/docs/docs.json +57 -0
  18. nono_py-0.1.0/docs/python/api/access-mode.mdx +112 -0
  19. nono_py-0.1.0/docs/python/api/capability-set.mdx +306 -0
  20. nono_py-0.1.0/docs/python/api/capability-source.mdx +96 -0
  21. nono_py-0.1.0/docs/python/api/fs-capability.mdx +166 -0
  22. nono_py-0.1.0/docs/python/api/functions.mdx +200 -0
  23. nono_py-0.1.0/docs/python/api/query-context.mdx +254 -0
  24. nono_py-0.1.0/docs/python/api/sandbox-state.mdx +270 -0
  25. nono_py-0.1.0/docs/python/api/support-info.mdx +106 -0
  26. nono_py-0.1.0/docs/python/examples.mdx +368 -0
  27. nono_py-0.1.0/docs/python/installation.mdx +151 -0
  28. nono_py-0.1.0/docs/python/overview.mdx +87 -0
  29. nono_py-0.1.0/docs/python/quickstart.mdx +196 -0
  30. nono_py-0.1.0/examples/01_basic_sandbox.py +69 -0
  31. nono_py-0.1.0/examples/02_query_permissions.py +97 -0
  32. nono_py-0.1.0/examples/03_sandbox_state.py +93 -0
  33. nono_py-0.1.0/examples/04_capability_inspection.py +121 -0
  34. nono_py-0.1.0/examples/05_subprocess_sandbox.py +133 -0
  35. nono_py-0.1.0/examples/06_command_filtering.py +99 -0
  36. nono_py-0.1.0/examples/07_error_handling.py +190 -0
  37. nono_py-0.1.0/examples/README.md +101 -0
  38. nono_py-0.1.0/nono/Cargo.toml +51 -0
  39. nono_py-0.1.0/nono/crates/nono/Cargo.toml +43 -0
  40. nono_py-0.1.0/nono/crates/nono/README.md +54 -0
  41. nono_py-0.1.0/nono/crates/nono/src/capability.rs +1055 -0
  42. nono_py-0.1.0/nono/crates/nono/src/diagnostic.rs +703 -0
  43. nono_py-0.1.0/nono/crates/nono/src/error.rs +146 -0
  44. nono_py-0.1.0/nono/crates/nono/src/keystore.rs +192 -0
  45. nono_py-0.1.0/nono/crates/nono/src/lib.rs +67 -0
  46. nono_py-0.1.0/nono/crates/nono/src/query.rs +233 -0
  47. nono_py-0.1.0/nono/crates/nono/src/sandbox/linux.rs +931 -0
  48. nono_py-0.1.0/nono/crates/nono/src/sandbox/macos.rs +845 -0
  49. nono_py-0.1.0/nono/crates/nono/src/sandbox/mod.rs +144 -0
  50. nono_py-0.1.0/nono/crates/nono/src/state.rs +152 -0
  51. nono_py-0.1.0/nono/crates/nono/src/supervisor/mod.rs +178 -0
  52. nono_py-0.1.0/nono/crates/nono/src/supervisor/never_grant.rs +300 -0
  53. nono_py-0.1.0/nono/crates/nono/src/supervisor/socket.rs +510 -0
  54. nono_py-0.1.0/nono/crates/nono/src/supervisor/types.rs +95 -0
  55. nono_py-0.1.0/nono/crates/nono/src/undo/exclusion.rs +284 -0
  56. nono_py-0.1.0/nono/crates/nono/src/undo/merkle.rs +235 -0
  57. nono_py-0.1.0/nono/crates/nono/src/undo/mod.rs +17 -0
  58. nono_py-0.1.0/nono/crates/nono/src/undo/object_store.rs +481 -0
  59. nono_py-0.1.0/nono/crates/nono/src/undo/snapshot.rs +1020 -0
  60. nono_py-0.1.0/nono/crates/nono/src/undo/types.rs +283 -0
  61. nono_py-0.1.0/pyproject.toml +77 -0
  62. nono_py-0.1.0/python/nono_py/__init__.py +55 -0
  63. nono_py-0.1.0/python/nono_py/_nono_py.pyi +274 -0
  64. nono_py-0.1.0/python/nono_py/py.typed +0 -0
  65. nono_py-0.1.0/src/lib.rs +685 -0
  66. nono_py-0.1.0/tests/conftest.py +17 -0
  67. nono_py-0.1.0/tests/test_access_mode.py +56 -0
  68. nono_py-0.1.0/tests/test_capability_set.py +194 -0
  69. nono_py-0.1.0/tests/test_query.py +137 -0
  70. nono_py-0.1.0/tests/test_sandbox_state.py +128 -0
  71. nono_py-0.1.0/tests/test_support.py +80 -0
  72. nono_py-0.1.0/uv.lock +440 -0
@@ -0,0 +1,145 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ env:
10
+ CARGO_TERM_COLOR: always
11
+ PYTHON_VERSION: "3.12"
12
+
13
+ jobs:
14
+ lint-rust:
15
+ name: Lint Rust
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
19
+
20
+ - name: Checkout nono library
21
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
22
+ with:
23
+ repository: always-further/nono
24
+ path: nono
25
+
26
+ - name: Install Rust toolchain
27
+ uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
28
+ with:
29
+ components: rustfmt, clippy
30
+
31
+ - name: Cache cargo
32
+ uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
33
+
34
+ - name: Check formatting
35
+ run: cargo fmt --check
36
+
37
+ - name: Run clippy
38
+ run: cargo clippy -- -D warnings
39
+
40
+ lint-python:
41
+ name: Lint Python
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
45
+
46
+ - name: Set up Python
47
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
48
+ with:
49
+ python-version: ${{ env.PYTHON_VERSION }}
50
+
51
+ - name: Install ruff
52
+ run: pip install ruff
53
+
54
+ - name: Check formatting
55
+ run: ruff format --check python/ tests/
56
+
57
+ - name: Run linter
58
+ run: ruff check python/ tests/
59
+
60
+ test:
61
+ name: Test (${{ matrix.os }})
62
+ runs-on: ${{ matrix.os }}
63
+ strategy:
64
+ fail-fast: false
65
+ matrix:
66
+ os: [ubuntu-latest, macos-latest]
67
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
68
+ steps:
69
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
70
+
71
+ - name: Checkout nono library
72
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
73
+ with:
74
+ repository: always-further/nono
75
+ path: nono
76
+
77
+ - name: Set up Python ${{ matrix.python-version }}
78
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
79
+ with:
80
+ python-version: ${{ matrix.python-version }}
81
+
82
+ - name: Install Rust toolchain
83
+ uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
84
+
85
+ - name: Cache cargo
86
+ uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
87
+
88
+ - name: Create virtual environment
89
+ run: python -m venv .venv
90
+
91
+ - name: Install maturin and pytest
92
+ run: |
93
+ source .venv/bin/activate
94
+ pip install maturin pytest
95
+
96
+ - name: Build and install
97
+ run: |
98
+ source .venv/bin/activate
99
+ maturin develop
100
+
101
+ - name: Run tests
102
+ run: |
103
+ source .venv/bin/activate
104
+ pytest tests/ -v
105
+
106
+ typecheck:
107
+ name: Type Check
108
+ runs-on: ubuntu-latest
109
+ steps:
110
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
111
+
112
+ - name: Checkout nono library
113
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
114
+ with:
115
+ repository: always-further/nono
116
+ path: nono
117
+
118
+ - name: Set up Python
119
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
120
+ with:
121
+ python-version: ${{ env.PYTHON_VERSION }}
122
+
123
+ - name: Install Rust toolchain
124
+ uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
125
+
126
+ - name: Cache cargo
127
+ uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
128
+
129
+ - name: Create virtual environment
130
+ run: python -m venv .venv
131
+
132
+ - name: Install dependencies
133
+ run: |
134
+ source .venv/bin/activate
135
+ pip install maturin mypy
136
+
137
+ - name: Build package
138
+ run: |
139
+ source .venv/bin/activate
140
+ maturin develop
141
+
142
+ - name: Run mypy
143
+ run: |
144
+ source .venv/bin/activate
145
+ mypy python/nono_py --ignore-missing-imports
@@ -0,0 +1,19 @@
1
+ name: Trigger Docs Rebuild
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - 'docs/**'
8
+
9
+ jobs:
10
+ dispatch:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Trigger docs repo rebuild
14
+ uses: peter-evans/repository-dispatch@v3
15
+ with:
16
+ token: ${{ secrets.DOCS_PAT }}
17
+ repository: always-further/nono-docs
18
+ event-type: docs-update
19
+ client-payload: '{"repo": "${{ github.repository }}", "sha": "${{ github.sha }}"}'
@@ -0,0 +1,170 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ publish_target:
9
+ description: 'Publish target'
10
+ required: true
11
+ default: 'testpypi'
12
+ type: choice
13
+ options:
14
+ - testpypi
15
+ - pypi
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ build-sdist:
22
+ name: Build source distribution
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
26
+
27
+ - name: Checkout nono library
28
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
29
+ with:
30
+ repository: always-further/nono
31
+ path: nono
32
+
33
+ - name: Fix nono path for CI
34
+ run: sed -i 's|path = "../nono/crates/nono"|path = "nono/crates/nono"|' Cargo.toml
35
+
36
+ - name: Build sdist
37
+ uses: PyO3/maturin-action@b1bd829e37fef14c63f19162034228a2f3dc1021 # v1.50.0
38
+ with:
39
+ command: sdist
40
+ args: --out dist
41
+
42
+ - name: Upload sdist
43
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
44
+ with:
45
+ name: dist-sdist
46
+ path: dist/*.tar.gz
47
+
48
+ build-wheels-linux:
49
+ name: Build wheels (Linux ${{ matrix.arch }})
50
+ runs-on: ${{ matrix.runner }}
51
+ strategy:
52
+ fail-fast: false
53
+ matrix:
54
+ include:
55
+ - runner: ubuntu-latest
56
+ arch: x86_64
57
+ target: x86_64
58
+ - runner: ubuntu-24.04-arm
59
+ arch: aarch64
60
+ target: aarch64
61
+ steps:
62
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
63
+
64
+ - name: Checkout nono library
65
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
66
+ with:
67
+ repository: always-further/nono
68
+ path: nono
69
+
70
+ - name: Fix nono path for CI
71
+ run: sed -i 's|path = "../nono/crates/nono"|path = "nono/crates/nono"|' Cargo.toml
72
+
73
+ - name: Build wheels
74
+ uses: PyO3/maturin-action@b1bd829e37fef14c63f19162034228a2f3dc1021 # v1.50.0
75
+ with:
76
+ target: ${{ matrix.target }}
77
+ args: --release --out dist -i python3.9 python3.10 python3.11 python3.12 python3.13
78
+ manylinux: auto
79
+ before-script-linux: |
80
+ if command -v dnf &> /dev/null; then
81
+ dnf install -y dbus-devel pkgconfig
82
+ elif command -v yum &> /dev/null; then
83
+ yum install -y dbus-devel pkgconfig
84
+ fi
85
+
86
+ - name: Upload wheels
87
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
88
+ with:
89
+ name: dist-linux-${{ matrix.arch }}
90
+ path: dist/*.whl
91
+
92
+ build-wheels-macos:
93
+ name: Build wheels (macOS ${{ matrix.target }})
94
+ runs-on: macos-latest
95
+ strategy:
96
+ fail-fast: false
97
+ matrix:
98
+ target: [x86_64-apple-darwin, aarch64-apple-darwin]
99
+ steps:
100
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
101
+
102
+ - name: Checkout nono library
103
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
104
+ with:
105
+ repository: always-further/nono
106
+ path: nono
107
+
108
+ - name: Set up Python
109
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
110
+ with:
111
+ python-version: '3.12'
112
+
113
+ - name: Fix nono path for CI
114
+ run: sed -i '' 's|path = "../nono/crates/nono"|path = "nono/crates/nono"|' Cargo.toml
115
+
116
+ - name: Build wheels
117
+ uses: PyO3/maturin-action@b1bd829e37fef14c63f19162034228a2f3dc1021 # v1.50.0
118
+ with:
119
+ target: ${{ matrix.target }}
120
+ args: --release --out dist -i python3.12
121
+
122
+ - name: Upload wheels
123
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
124
+ with:
125
+ name: dist-macos-${{ matrix.target }}
126
+ path: dist/*.whl
127
+
128
+ publish-testpypi:
129
+ name: Publish to TestPyPI
130
+ needs: [build-sdist, build-wheels-linux, build-wheels-macos]
131
+ runs-on: ubuntu-latest
132
+ if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish_target == 'testpypi'
133
+ environment:
134
+ name: testpypi
135
+ url: https://test.pypi.org/p/nono-py
136
+ permissions:
137
+ id-token: write
138
+ steps:
139
+ - name: Download artifacts
140
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
141
+ with:
142
+ pattern: dist-*
143
+ path: dist
144
+ merge-multiple: true
145
+
146
+ - name: Publish to TestPyPI
147
+ uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1
148
+ with:
149
+ repository-url: https://test.pypi.org/legacy/
150
+
151
+ publish-pypi:
152
+ name: Publish to PyPI
153
+ needs: [build-sdist, build-wheels-linux, build-wheels-macos]
154
+ runs-on: ubuntu-latest
155
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish_target == 'pypi')
156
+ environment:
157
+ name: pypi
158
+ url: https://pypi.org/p/nono-py
159
+ permissions:
160
+ id-token: write
161
+ steps:
162
+ - name: Download artifacts
163
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
164
+ with:
165
+ pattern: dist-*
166
+ path: dist
167
+ merge-multiple: true
168
+
169
+ - name: Publish to PyPI
170
+ uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1
@@ -0,0 +1,28 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ create-release:
13
+ name: Create GitHub Release
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
17
+
18
+ - name: Extract version from tag
19
+ id: version
20
+ run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
21
+
22
+ - name: Create Release
23
+ uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
24
+ with:
25
+ name: Release v${{ steps.version.outputs.VERSION }}
26
+ draft: false
27
+ prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
28
+ generate_release_notes: true
@@ -0,0 +1,38 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info/
8
+
9
+ # Virtual environments
10
+ .venv/
11
+
12
+ # Rust/Cargo artifacts
13
+ target/
14
+ Cargo.lock
15
+
16
+ # Maturin artifacts
17
+ *.so
18
+ *.dylib
19
+ *.pyd
20
+
21
+ # IDE
22
+ .idea/
23
+ .vscode/
24
+ *.swp
25
+ *.swo
26
+ *~
27
+
28
+ # Testing
29
+ .pytest_cache/
30
+ .coverage
31
+ htmlcov/
32
+ .tox/
33
+
34
+ # Type checking
35
+ .mypy_cache/
36
+
37
+ # Linting
38
+ .ruff_cache/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,67 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What This Is
6
+
7
+ nono-py provides Python bindings for the [nono](https://github.com/always-further/nono) Rust capability-based sandboxing library. It uses PyO3/maturin to expose Rust code to Python, supporting Landlock (Linux) and Seatbelt (macOS).
8
+
9
+ ## Build & Development Commands
10
+
11
+ ```bash
12
+ uv sync # Install all dependencies (creates venv automatically)
13
+ uv run maturin develop # Build native module (debug mode)
14
+ uv run maturin develop --release # Build native module (release mode)
15
+ ```
16
+
17
+ ## Testing
18
+
19
+ ```bash
20
+ uv run pytest tests/ -v # All tests
21
+ uv run pytest tests/test_capability_set.py -v # Single file
22
+ uv run pytest tests/test_query.py::TestQueryContextPathQueries -v # Single class
23
+ ```
24
+
25
+ ## Linting & Formatting
26
+
27
+ ```bash
28
+ cargo fmt # Format Rust
29
+ cargo clippy -- -D warnings # Lint Rust
30
+ uv run ruff format python/ tests/ # Format Python
31
+ uv run ruff check --fix python/ tests/ # Lint Python (autofix)
32
+ uv run mypy python/nono_py # Type check (strict mode)
33
+ ```
34
+
35
+ `make ci` runs the full suite: fmt-check, lint, test.
36
+
37
+ ## Architecture
38
+
39
+ ### Binding Layer
40
+
41
+ `src/lib.rs` is the single Rust source file. It wraps the `nono` crate's types using PyO3 `#[pyclass]`/`#[pymethods]` macros. Each Python class holds an inner Rust type (e.g., `CapabilitySet` wraps `RustCapabilitySet`). Rust `NonoError` variants map to Python exceptions: `FileNotFoundError`, `ValueError`, `OSError`, `RuntimeError`, `PermissionError`.
42
+
43
+ ### Python Package
44
+
45
+ `python/nono_py/__init__.py` re-exports everything from the native `_nono_py` module. The underscore-prefixed native module is an internal implementation detail.
46
+
47
+ `python/nono_py/_nono_py.pyi` contains type stubs that must stay in sync with the Rust API. This file is the source of truth for IDE autocompletion and mypy.
48
+
49
+ ### Key Classes
50
+
51
+ - **CapabilitySet** — mutable builder: `allow_path()`, `allow_file()`, `block_network()`, `allow_command()`, `block_command()`
52
+ - **QueryContext** — test permissions without applying: returns dicts with `status`/`reason` keys
53
+ - **SandboxState** — JSON-serializable snapshot of a CapabilitySet for cross-process transfer
54
+ - **AccessMode** — enum: `READ`, `WRITE`, `READ_WRITE` (frozen)
55
+ - **apply()** — module-level function, **irreversible** OS sandbox enforcement
56
+
57
+ ### Nono Dependency
58
+
59
+ `Cargo.toml` references the nono library via local path (`../nono/crates/nono`). Both repos must be sibling directories for local development. CI checks out nono as a sibling automatically.
60
+
61
+ ## Conventions
62
+
63
+ - Python: ruff, line-length 100, target py39, strict mypy
64
+ - Rust: edition 2021, rust-version 1.80, clippy with `-D warnings`
65
+ - Frozen PyO3 classes for immutable types (AccessMode, FsCapability, SupportInfo, CapabilitySource)
66
+ - Path validation happens at add-time in Rust (fail fast)
67
+ - Tests use `conftest.py` fixtures `temp_dir` and `temp_file` for filesystem isolation