# Simple File Manager A single-file PHP file manager — hardened, self-hosted, and CDN-free. 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. Maintained by **Hoelee Enterprise** — [hoelee.com](https://hoelee.com) · WhatsApp +6012-797 2969 --- ## Why it's good - **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 ## Requirements - 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 ## Quick Start ### 1. Download ```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 Require all denied ``` **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 '$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