feat: harden and modernize the file manager (v2.0.0)

Security hardening:
- bcrypt password auth via password_hash()/password_verify(), no default creds
- Session CSRF token verified with hash_equals()
- Secure session cookies: HttpOnly, SameSite=Strict, Secure over HTTPS
- Nonce-based Content-Security-Policy, frame/clickjack + nosniff headers
- realpath() root containment + symlink exclusion to block traversal
- Server-side upload size + MIME(finfo) + extension validation
- Configurable IP allow/deny with CIDR, optional HTTPS enforcement
- Fixed 500ms login delay to slow brute-force guessing

Features (single PHP file, no CDN):
- New Bootstrap 5 responsive UI with light/dark theme toggle
- Upload (drag+drop), download-only links, delete, rename, copy/move, mkdir
- Folder ZIP download, in-browser text editor (<=2MiB)
- Multi-user, per-user read-only + per-user root directories
- All frontend assets vendored locally under assets/ (349K)

Docs: full config reference, password hash guide, Tor hidden-service guide.
This commit is contained in:
2026-08-19 23:55:07 +08:00
parent 204033157b
commit e457e14b30
7 changed files with 1345 additions and 480 deletions
+440 -24
View File
@@ -1,35 +1,451 @@
simple-file-manager
===================
# Simple File Manager
A Simple PHP file manager. The code is a single php file.
A single-file PHP file manager — hardened, self-hosted, and CDN-free.
Just copy `index.php` to a folder on your webserver.
Forked from [jcampbell1/simple-file-manager](https://github.com/jcampbell1/simple-file-manager) with security hardening and features inspired by [prasathmani/tinyfilemanager](https://github.com/prasathmani/tinyfilemanager) v2.6.
## Why it is good
Maintained by **Hoelee Enterprise** — [hoelee.com](https://hoelee.com) · WhatsApp +6012-797 2969
- Single file, there are no images, or css folders.
- Ajax based so it is fast, but doesn't break the back button
- Allows drag and drop file uploads if the folder is writable by the webserver (`chmod 777 your/folder`)
- Suits my aesthetics. More like Dropbox, and less like Windows Explorer
- Works with Unicode file names
- The interface is usable from an iPad
- XSRF protection, and an optional password.
---
## Do not allow uploads on the public web
## Why it's good
If you allow uploads on the public web, it is only a matter of time before your server is hosting and serving very illegal content. Any of the following options will prevent this:
- Don't make the folder writable by the webserver `chmod 775`
- Set `$allow_upload = false`
- Use a password `$PASSWORD = 'some password'`
- Use a `.htaccess` file with Apache, or `auth_basic` for nginx
- Only use this on a private network
- **Single PHP file** (`index.php`) — all frontend JS/CSS stored locally in `assets/`, no CDN requests
- **No external dependencies** at runtime — works on air-gapped networks and Tor hidden services
- **Hardened security** — bcrypt auth, CSRF tokens, CSP headers, symlink protection, server-side upload validation
- **Modern UI** — Bootstrap 5, responsive, light/dark theme toggle
- **Full file operations** — upload, download, delete, rename, copy/move, mkdir, ZIP archive, in-browser file editor
- **Multi-user support** — per-user passwords, read-only accounts, per-user directory isolation
- **Tor-friendly** — no DNS lookups, no external fonts, no analytics, no CDN
HT: [@beardog108](https://github.com/beardog108)
## Requirements
## Forks
- PHP **7.4+** (8.x recommended)
- PHP extensions: `fileinfo` (required), `zip` (optional, for ZIP downloads)
- Web server: Apache, Nginx, OpenLiteSpeed, or PHP built-in server
- **Edit feature**. An extension of the initial project which lets you edit files and save them from the main php file. Works asynchronously with ajax requests. Link: [@diego95root](https://github.com/diego95root/File-manager-php)
## Quick Start
## Screenshot
### 1. Download
![Screenshot](https://raw.github.com/jcampbell1/simple-file-manager/master/screenshot.png "Screenshot")
```bash
git clone https://github.com/hoelee/simple-file-manager.git
cd simple-file-manager
```
Or just copy `index.php` + the `assets/` folder to your webserver.
### 2. Generate a password hash
Before the app will run, you need at least one password hash. Generate one with PHP:
```bash
php -r "echo password_hash('your-strong-password', PASSWORD_DEFAULT), PHP_EOL;"
```
Output looks like:
```
$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
```
### 3. Configure
Open `index.php` and edit the **CONFIGURATION** section at the top:
```php
$app_title = 'My File Manager';
// Directory users can manage. Use __DIR__ for the project folder,
// or an absolute path like '/srv/uploads'.
$root_path = __DIR__;
$use_auth = true;
$auth_users = [
'admin' => '$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy',
];
```
### 4. Serve
**PHP built-in server** (quick test):
```bash
php -S 0.0.0.0:8080 -t /path/to/simple-file-manager
```
Open `http://localhost:8080` and log in.
**Nginx:**
```nginx
server {
listen 80;
server_name files.example.com;
root /var/www/simple-file-manager;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Prevent direct execution of any PHP file except index.php
location ~ /assets/.*\.php$ {
deny all;
}
}
```
**Apache `.htaccess** (place in the project root):
```apache
Options -Indexes
AllowOverride None
<FilesMatch "^(?!index\.php$).+\.php$">
Require all denied
</FilesMatch>
```
**Docker:**
```bash
docker run -d \
--name file-manager \
-p 8080:80 \
-v /path/to/files:/var/www/html/data \
-v /path/to/simple-file-manager:/var/www/html/app \
php:8.2-apache
# Then visit http://localhost:8080/app/
```
---
## Configuration Reference
All settings are in the **CONFIGURATION** block at the top of `index.php`.
### Authentication
| Setting | Default | Description |
|---------|---------|-------------|
| `$use_auth` | `true` | Enable login. App refuses to run without a valid password hash. |
| `$auth_users` | `[]` | Map of `'username' => 'bcrypt-hash'` |
| `$readonly_users` | `[]` | Users who can browse/download but not modify |
| `$user_directories` | `[]` | Per-user root directory, e.g. `['client-a' => '/srv/client-a']` |
| `$block_placeholder_credentials` | `true` | Refuses to run if a placeholder hash is detected |
### Permissions
| Setting | Default | Description |
|---------|---------|-------------|
| `$allow_upload` | `true` | File upload |
| `$allow_delete` | `true` | Delete files/folders |
| `$allow_create_folder` | `true` | Create new folders |
| `$allow_rename` | `true` | Rename files/folders |
| `$allow_copy_move` | `true` | Copy or move between folders |
| `$allow_edit` | `true` | In-browser text file editor (≤2 MiB) |
| `$allow_archive_download` | `true` | Download folder as ZIP |
| `$allow_direct_link` | `false` | Direct file links vs. forced download |
### Upload Policy
| Setting | Default | Description |
|---------|---------|-------------|
| `$max_upload_size_bytes` | `104857600` (100 MiB) | Per-file upload limit (also configure PHP/nginx) |
| `$allowed_upload_extensions` | `[]` | Allowlist, e.g. `['jpg','png','pdf']`; empty = all non-blocked |
| `$blocked_upload_extensions` | `[php, phtml, phar, ...]` | Blocklist of dangerous extensions |
| `$allowed_upload_mime_types` | `[]` | MIME allowlist via `finfo`; empty = no check |
### Access Control
| Setting | Default | Description |
|---------|---------|-------------|
| `$ip_access_mode` | `'off'` | `'off'`, `'allow'` (allowlist), or `'deny'` (denylist) |
| `$ip_rules` | `[]` | IPs or CIDRs, e.g. `['127.0.0.1', '192.168.1.0/24']` |
| `$require_https` | `false` | Reject non-HTTPS requests |
### UI
| Setting | Default | Description |
|---------|---------|-------------|
| `$default_theme` | `'light'` | `'light'` or `'dark'` (user can toggle) |
| `$default_timezone` | `'Asia/Kuala_Lumpur'` | PHP timezone |
| `$show_hidden_files` | `false` | Show dotfiles in listing |
| `$debug` | `false` | Show PHP errors (keep `false` in production) |
---
## User Password Hash Guide
This app uses **bcrypt** via PHP's `password_hash()`. Never store plaintext passwords.
### Generate a hash
**Command line:**
```bash
php -r "echo password_hash('MySecretPass123!', PASSWORD_DEFAULT), PHP_EOL;"
```
**Interactive (prompts for password, hidden input):**
```bash
php -r "echo password_hash(readline('Password: '), PASSWORD_DEFAULT), PHP_EOL;"
```
**PHP script** (`generate-hash.php`):
```php
<?php
echo "Enter password: ";
system('stty -echo');
$password = trim(fgets(STDIN));
system('stty echo');
echo "\n" . password_hash($password, PASSWORD_DEFAULT) . "\n";
```
### Add a user
```php
$auth_users = [
'admin' => '$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy',
'viewer' => '$2y$10$X5zP2k3vQ8mF1bN7cR4dT6hJ9lE0wA3sB5nC8oY2uZ1xD4eG7hI3a',
];
$readonly_users = ['viewer']; // viewer can browse but not modify
```
### Multiple users with isolated directories
```php
$auth_users = [
'admin' => '$2y$10$...hash1...',
'client-a' => '$2y$10$...hash2...',
'client-b' => '$2y$10$...hash3...',
];
$user_directories = [
'client-a' => '/srv/uploads/client-a',
'client-b' => '/srv/uploads/client-b',
];
$readonly_users = ['client-b']; // client-b can only download
```
### Tips
- Use a unique password per user
- Bcrypt cost factor is 10 by default — fine for most servers
- To rotate a password, just generate a new hash and replace the old one
- The app auto-detects placeholder text (`REPLACE_WITH`) and refuses to start
---
## Hosting on Tor Hidden Service
This file manager is ideal for Tor because it makes **zero external requests** — no CDN, no Google Fonts, no analytics, no DNS lookups. All assets are served from the local `assets/` folder.
### Prerequisites
- A Tor relay or daemon installed on your server
- A web server (Nginx recommended) running on localhost
- PHP-FPM
### Step 1: Install Tor
**Ubuntu/Debian:**
```bash
sudo apt update && sudo apt install tor
```
**Synology DSM (Docker):**
```yaml
# docker-compose.yml
services:
tor:
image: dperson/torproxy
container_name: tor
restart: unless-stopped
ports:
- "127.0.0.1:9050:9050" # SOCKS proxy
volumes:
- ./tor-data:/var/lib/tor
```
### Step 2: Configure the hidden service
Edit `/etc/tor/torrc` (Linux) or your Tor config:
```ini
HiddenServiceDir /var/lib/tor/file-manager/
HiddenServicePort 80 127.0.0.1:8765
```
Restart Tor:
```bash
sudo systemctl restart tor
```
Get your `.onion` address:
```bash
sudo cat /var/lib/tor/file-manager/hostname
# Output: abcdefghijklmnop234567.onion
```
### Step 3: Configure Nginx for localhost only
```nginx
server {
listen 127.0.0.1:8765;
server_name _;
root /var/www/simple-file-manager;
index index.php;
# No HTTPS needed — Tor provides transport encryption
# But enforce that requests come through Tor (localhost only)
allow 127.0.0.1;
deny all;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Block direct PHP execution in assets/
location ~ /assets/.*\.php$ {
deny all;
}
# Static asset caching
location ~* \.(js|css|png|jpg|gif|svg|ico)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
```
### Step 4: Configure for Tor
In `index.php`:
```php
$app_title = 'Secure Files';
// Tor provides end-to-end encryption, so HTTPS is not required
$require_https = false;
// Bind to the managed directory (NOT the project folder)
$root_path = '/srv/tor-files';
// Strong auth — mandatory for .onion services
$use_auth = true;
$auth_users = [
'admin' => '$2y$10$your-bcrypt-hash-here',
];
// Lock down uploads on a public .onion
$allow_upload = false; // or true if you trust your users
$allow_delete = true;
$allow_direct_link = false; // force downloads only
// Optional: restrict to known clients (Tor exit uses localhost)
$ip_access_mode = 'allow';
$ip_rules = ['127.0.0.1'];
```
### Step 5: Access
```bash
# Install Tor Browser, then visit:
http://abcdefghijklmnop234567.onion
```
### Tor Hardening Checklist
- [x] No CDN/external requests (assets are local)
- [x] No DNS lookups (`.onion` resolves through Tor)
- [x] No analytics or third-party scripts
- [x] Session cookies are `SameSite=Strict`, `HttpOnly`
- [x] CSP prevents loading external resources
- [x] Nginx listens on `127.0.0.1` only (not `0.0.0.0`)
- [x] Strong bcrypt password configured
- [x] `$allow_upload` disabled or restricted
- [x] `$allow_direct_link = false` (no hotlinking from clearnet)
- [ ] Consider disabling `$allow_edit` if not needed
- [ ] Set `$show_hidden_files = false`
- [ ] Regularly check Tor logs for relay health
---
## Security Notes
### Do not allow uploads on the public web
If you allow uploads on the public web, it is only a matter of time before your server is hosting and serving illegal content. Prevent this with:
- Set `$allow_upload = false`
- Use a strong password (`$use_auth = true`)
- Restrict by IP (`$ip_access_mode = 'allow'`)
- Use `.htaccess` (Apache) or `auth_basic` (Nginx)
- Only expose on a private network or Tor hidden service
### Security features in this fork
| Feature | Status |
|---------|--------|
| bcrypt password hashing | ✅ `password_hash()` / `password_verify()` |
| CSRF protection | ✅ Session token + `hash_equals()` |
| Session cookie hardening | ✅ `HttpOnly`, `SameSite=Strict`, `Secure` |
| Content Security Policy | ✅ Nonce-based CSP header |
| Path traversal protection | ✅ `realpath()` + root containment check |
| Symlink protection | ✅ Symlinks hidden & excluded from all operations |
| Server-side upload size limit | ✅ Enforced before `move_uploaded_file()` |
| Upload extension/MIME validation | ✅ Blocklist + optional allowlists |
| IP allow/deny with CIDR | ✅ Configurable |
| HTTPS enforcement | ✅ Optional via `$require_https` |
| X-Frame-Options | ✅ `DENY` (clickjacking protection) |
| X-Content-Type-Options | ✅ `nosniff` |
| Referrer-Policy | ✅ `same-origin` |
| Brute-force delay | ✅ 500ms fixed delay on login attempt |
| No default credentials | ✅ Refuses to run without a real hash |
---
## Credits
- Original: [jcampbell1/simple-file-manager](https://github.com/jcampbell1/simple-file-manager) (MIT)
- Enhancements inspired by: [prasathmani/tinyfilemanager](https://github.com/prasathmani/tinyfilemanager) v2.6
- Frontend: [Bootstrap 5.3.3](https://getbootstrap.com/), [jQuery 3.7.1](https://jquery.com/), [Font Awesome 4.7](https://fontawesome.com/v4/), [Ace Editor](https://ace.c9.io/), [Highlight.js](https://highlightjs.org/)
---
## License
MIT — see [LICENSE](LICENSE)
---
## About
**Hoelee Enterprise** provides website design for SME, program & web app development, web hosting, and marketing.
- 🌐 [hoelee.com](https://hoelee.com) — Portfolio
- 📱 WhatsApp: +6012-797 2969
- ✉️ Email: me@hoelee.com