Skip to content
 
 

Latest commit

 

History

68 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Simple File Manager

A single-file PHP file manager — hardened, self-hosted, and CDN-free.

Forked from jcampbell1/simple-file-manager with security hardening and features inspired by prasathmani/tinyfilemanager v2.6.

Maintained by Hoelee Enterprisehoelee.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

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:

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:

$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):

php -S 0.0.0.0:8080 -t /path/to/simple-file-manager

Open http://localhost:8080 and log in.

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):

Options -Indexes
AllowOverride None

<FilesMatch "^(?!index\.php$).+\.php$">
    Require all denied
</FilesMatch>

Docker:

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:

php -r "echo password_hash('MySecretPass123!', PASSWORD_DEFAULT), PHP_EOL;"

Interactive (prompts for password, hidden input):

php -r "echo password_hash(readline('Password: '), PASSWORD_DEFAULT), PHP_EOL;"

PHP script (generate-hash.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

$auth_users = [
    'admin'  => '$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy',
    'viewer' => '$2y$10$X5zP2k3vQ8mF1bN7cR4dT6hJ9lE0wA3sB5nC8oY2uZ1xD4eG7hI3a',
];
$readonly_users = ['viewer'];  // viewer can browse but not modify

Multiple users with isolated directories

$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:

sudo apt update && sudo apt install tor

Synology DSM (Docker):

# 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:

HiddenServiceDir /var/lib/tor/file-manager/
HiddenServicePort 80 127.0.0.1:8765

Restart Tor:

sudo systemctl restart tor

Get your .onion address:

sudo cat /var/lib/tor/file-manager/hostname
# Output: abcdefghijklmnop234567.onion

Step 3: Configure Nginx for localhost only

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:

$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

# Install Tor Browser, then visit:
http://abcdefghijklmnop234567.onion

Tor Hardening Checklist

  • No CDN/external requests (assets are local)
  • No DNS lookups (.onion resolves through Tor)
  • No analytics or third-party scripts
  • Session cookies are SameSite=Strict, HttpOnly
  • CSP prevents loading external resources
  • Nginx listens on 127.0.0.1 only (not 0.0.0.0)
  • Strong bcrypt password configured
  • $allow_upload disabled or restricted
  • $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


License

MIT — see LICENSE


About

Hoelee Enterprise provides website design for SME, program & web app development, web hosting, and marketing.

About

Single-file PHP file manager - hardened & CDN-free. bcrypt auth, CSRF/CSP security, multi-user, upload/delete/rename/copy/move, ZIP, in-browser editor, Tor-friendly.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages