Fixes the nine findings of security audit run 1 on 2.4.1 (T1-T11).
F1: Uploads that are not complete now count only the bytes the server
received, and a chunk that goes above the storage quota gets 422. An
anonymous uploader can no longer fill the quota with declared sizes.
F2: The share password and the admin login have a second limit without
the address (20 attempts per minute per share and per e-mail), because
a forged X-Forwarded-For changes the address.
F3: The system password gate and the admin check now also run on
Livewire updates, so an old uploader snapshot cannot register files.
F4: The session keeps a fingerprint of the system password's hash.
When the admin changes the system password, earlier verifications end.
F5: The page after the upload opens only in the session that created
the share, and only while recipients can still reach the share.
F6: The share password limit counts an attempt before the password is
checked, so parallel attempts cannot pass it.
F7: The system password has the same two limits (5 per address, 20 in
total per minute).
F8: A password change or reset signs the user's other sessions out.
F9: The only admin cannot delete the account, which would open /setup
to every visitor.
The setup wizard also holds the admin password to the password
defaults, and Livewire applies EnsureAdmin to updates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShareDownload::$authenticated was a public property without #[Locked],
so a visitor with a share's link but not its password could set it from
the browser and see the file list: names, folder paths, sizes and
whether the share holds a text. The contents stayed protected; the flag
is now locked, and a test tries to set it.
Sessions hold the keys of the password shares a recipient unlocked, and
were stored unencrypted in the sessions table wherever SESSION_ENCRYPT
was not set, which includes every Docker install. They are now
encrypted unless SESSION_ENCRYPT=false. Share keys never live in the
session, so every share stays readable; people sign in again once.
The container generated an APP_KEY when none was set, printed it to the
logs and generated another on the next start, which left the shares
without a password unreadable. It now refuses to start without one and
says how to create it; docker-compose.example.yml requires it for both
services. The command it names, and the README's, now pass
--entrypoint php: the image's entrypoint ignores its arguments, so
"docker run <image> php artisan key:generate --show" never ran it.
Every proxy is trusted, so the password-reset mail built its link from
a host a forged X-Forwarded-Host could choose. It is now built from
APP_URL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Config: auth, services, logging, queue and database only repeated the
framework's own files and are gone; the others keep only the keys that
differ (app version, cache serializable_classes, session cookie name,
Markdown mail theme, the shares disk, three Octane values, Livewire's
pagination theme and payload guards).
- Email verification is removed: User never implemented MustVerifyEmail,
so it was never enforced, and SealShare has a single admin and no
registration. CreateNewUser goes with it.
- FileEncryptionService::encryptFile() and generateSalt() were only used
by tests; tests build files with encryptTestFile() in tests/Pest.php.
- The expiration options are defined once, as Share::EXPIRATIONS. "30 Days"
now lasts 30 days instead of a calendar month, and Admin settings only
save a default expiration that is one of the options.
- One-caller helpers are inlined, the uploader reads chunk responses with
XHR's responseType, and starter-kit leftovers are removed.
- Docker: PHP reads the PHP_* limits from the environment itself
(${VAR:-default} in uploads.ini); both entrypoints stop writing the ini.
docker-compose.yml shares the app and scheduler variables through one
anchor. The dev image installs gd for the screenshot publisher and fake
test images.
- Development runs in Docker only: the composer dev script, concurrently,
laravel/pail, laravel/sail, autoprefixer and the shell-quote override
are gone.
- phpunit.xml forces the test environment with <server> entries, so tests
run in the dev container no longer use its real database.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces maryUI and daisyUI with nonameweb/livewire-material: the Vibrant
indigo scheme, a system/light/dark theme under sealshare-theme, one top
app bar with the account menu, the upload drop zone and link-ready
moments, M3 fields, dialogs instead of wire:confirm, snackbars instead of
flashed messages, a sortable admin table, and the starter-kit cleanup.
Docker builds assets after Composer; CI drops the Flux step.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy