Skip to content

Commit 372f20e

Browse files
committed
feat: Enhanced security with whitelist-based path validation
- Add comprehensive path traversal protection - Block absolute path access outside project directories - Prevent symbolic link escapes - Add detailed security violation logging - Update documentation with security features - Release v1.0.5 with enhanced security
1 parent a48f20b commit 372f20e

File tree

3 files changed

+92
-10
lines changed

3 files changed

+92
-10
lines changed

README.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@ A powerful command-line interface for opening local files via custom URL scheme
1010
- 🔗 **Custom URL Protocol**: Register `fileopener://` scheme with your operating system
1111
- 📁 **Project Aliases**: Map project names to local directory paths
1212
- 🖥️ **Cross-Platform**: Works on macOS, Windows, and Linux
13-
- 🔒 **Security**: Built-in path traversal protection
13+
- 🔒 **Enhanced Security**: Whitelist-based path validation with protection against:
14+
- Path traversal attacks (`../`, `~/`)
15+
- Absolute path access outside project directories
16+
- Symbolic link escapes
17+
- Directory traversal bypass attempts
1418
- 🔄 **Dual URL Formats**: Supports both modern and legacy URL formats
1519
- ⚙️ **Configuration Management**: Easy project setup and management
1620
- 🛡️ **Error Handling**: Comprehensive error messages and validation
1721
- 🎯 **Config URL Support**: Access configuration via `fileopener://config`
1822
- 🔧 **ES Module Support**: Built with modern JavaScript (ES modules)
23+
- 🧹 **Memory Leak Prevention**: Automatic process cleanup after file operations
1924
- 🌐 **Web Integration**: Compatible with [fileopener-redirect-worker](https://github.com/mineclover/fileopener-redirect-worker) for HTTP-to-protocol redirection
2025

2126
## 🚀 Quick Start
@@ -349,13 +354,34 @@ fileopener://myproject/my%20file%20with%20spaces.txt
349354

350355
## 🔒 Security Features
351356

352-
### Path Traversal Protection
353-
The tool prevents access to files outside the configured project directories:
357+
### Enhanced Whitelist-Based Security
358+
The tool implements comprehensive security measures to prevent unauthorized file access:
354359

360+
#### Path Traversal Protection
355361
```bash
356362
# ❌ These will be blocked:
357363
fopen open "fileopener://myproject/../../../etc/passwd"
358-
fopen open "fileopener://myproject/../../sensitive-file.txt"
364+
fopen open "fileopener://myproject?path=../sensitive-file.txt"
365+
fopen open "fileopener://myproject?path=~/Documents/private.txt"
366+
```
367+
368+
#### Absolute Path Protection
369+
```bash
370+
# ❌ These will be blocked:
371+
fopen open "fileopener://myproject?path=/etc/passwd"
372+
fopen open "fileopener://myproject?path=/Users/otheruser/private.txt"
373+
```
374+
375+
#### Symbolic Link Protection
376+
The tool resolves symbolic links and ensures they don't escape the project directory boundaries.
377+
378+
#### Security Violation Messages
379+
When security violations are detected, the tool provides clear error messages:
380+
```
381+
Security violation: Path traversal attempt detected
382+
Access denied: Security policy violation
383+
Attempted access to: ../etc/passwd
384+
Allowed project path: /path/to/project
359385
```
360386

361387
### File Validation

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@context-action/fopen-cli",
3-
"version": "1.0.0",
3+
"version": "1.0.5",
44
"packageManager": "pnpm@10.14.0",
55
"type": "module",
66
"license": "MIT",

src/bin-simple.js

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,54 @@ function openFile(filePath) {
180180
}, 5000) // 5 second timeout
181181
}
182182

183+
// Security validation functions
184+
function validateFilePath(filePath, projectPath) {
185+
// Check for path traversal attempts
186+
if (filePath.includes('..') || filePath.includes('~')) {
187+
console.log("Security violation: Path traversal attempt detected")
188+
return false
189+
}
190+
191+
// Check for absolute paths (should be relative to project)
192+
if (path.isAbsolute(filePath)) {
193+
console.log("Security violation: Absolute path not allowed")
194+
return false
195+
}
196+
197+
// Normalize the path to prevent various bypass attempts
198+
const normalizedPath = path.normalize(filePath)
199+
200+
// Check for any remaining traversal attempts after normalization
201+
if (normalizedPath.includes('..')) {
202+
console.log("Security violation: Path traversal detected after normalization")
203+
return false
204+
}
205+
206+
// Resolve full file path
207+
const fullPath = path.resolve(path.join(projectPath, normalizedPath))
208+
const normalizedProjectPath = path.resolve(projectPath)
209+
210+
// Ensure the resolved path is within the project directory
211+
if (!fullPath.startsWith(normalizedProjectPath)) {
212+
console.log("Security violation: Path outside project directory")
213+
return false
214+
}
215+
216+
// Check for symbolic links that might escape the project directory
217+
try {
218+
const realPath = fs.realpathSync(fullPath)
219+
if (!realPath.startsWith(normalizedProjectPath)) {
220+
console.log("Security violation: Symbolic link escapes project directory")
221+
return false
222+
}
223+
} catch (error) {
224+
// If realpathSync fails, the file might not exist yet, but we'll check later
225+
// This is not a security violation by itself
226+
}
227+
228+
return true
229+
}
230+
183231
// Parse URL and open file
184232
function handleUrl(url) {
185233
console.log(`Processing URL: ${url}`)
@@ -223,7 +271,7 @@ function handleUrl(url) {
223271

224272
console.log(`Project: ${project}, File: ${filePath}`)
225273

226-
// Get project path from config
274+
// Get project path from config (whitelist check)
227275
const config = getConfig()
228276
const projectPath = config.projects[project]
229277

@@ -236,13 +284,21 @@ function handleUrl(url) {
236284
return
237285
}
238286

239-
// Resolve full file path
240-
const fullPath = path.resolve(path.join(projectPath, filePath))
287+
// Security validation: whitelist-based path checking
288+
if (!validateFilePath(filePath, projectPath)) {
289+
console.log("Access denied: Security policy violation")
290+
console.log(`Attempted access to: ${filePath}`)
291+
console.log(`Allowed project path: ${projectPath}`)
292+
return
293+
}
294+
295+
// Resolve full file path (after validation)
296+
const fullPath = path.resolve(path.join(projectPath, path.normalize(filePath)))
241297

242-
// Security check: ensure the resolved path is within the project directory
298+
// Final security check: ensure the resolved path is within the project directory
243299
const normalizedProjectPath = path.resolve(projectPath)
244300
if (!fullPath.startsWith(normalizedProjectPath)) {
245-
console.log("Path traversal detected - access denied for security reasons")
301+
console.log("Security violation: Final path validation failed")
246302
return
247303
}
248304

0 commit comments

Comments
 (0)