Commit Graph
6 Commits
Author SHA1 Message Date
Andreas Reinhold / reiniandClaude Opus 5 40e35bab0e Encrypt uploads in the browser and send them in chunks
A 6 GB upload kept a customer waiting long after its progress bar
reached 100%. The server wrote every upload three times: PHP's
temporary file, Livewire's copy of it ("Processing files...") and the
encrypted file ("Create Share Link"), each a full rewrite of a slow
disk. The unencrypted copy also stayed behind in livewire-tmp.

Now the uploader's browser encrypts each file in 16 MB chunks with
WebCrypto and PUTs them one at a time; the server checks each chunk in
memory and writes it once, already encrypted. Creating the share only
wraps its key and saves the options. A 200 MB upload through the
Docker image took 2.8 s, and its download matched byte for byte.

- SEALCHK2: a 19-byte header (chunk size, 7-byte nonce prefix), then
  ciphertext and tag per chunk. Each nonce holds the chunk index and a
  last-chunk flag (the STREAM construction), so cut or reordered files
  fail to decrypt. SEALCHK1 and the single-block format still read.
- Envelope encryption: one random key per share. With a password it is
  wrapped with Argon2id (sodium, libsodium's interactive limits) in
  shares.wrapped_key, which names its parameters. Password shares from
  before keep their PBKDF2-derived key.
- The upload page registers each selection with FileUploader into a
  pending share of its own, lists the files with their progress, retries
  a failed chunk after 1-16 s, then offers Retry; Remove and Cancel
  abort. UploadChunkController only accepts chunks from the session that
  started the share: a repeat is acknowledged, a skip gets 409 with the
  count stored. Chunks go out as Blobs, which Chromium sends about eight
  times faster than ArrayBuffers.
- Uploads need a secure context: over plain HTTP the page says HTTPS is
  needed and takes no files. The Docker image gains AUTO_HTTPS, which
  serves Let's Encrypt on 443 for SERVER_NAME and redirects 80; without
  it the container stays on HTTP 80 behind a proxy. docker/Caddyfile was
  never loaded and is gone; docker/healthcheck.sh covers both modes.
- "Download all" streams the ZIP with maennchen/zipstream-php (STORE,
  ZIP64) instead of decrypting whole files into memory and writing the
  archive unencrypted to /tmp.
- Pending shares count towards the quota, stay out of the admin
  dashboard and 404 everywhere else. shares:cleanup deletes uploads idle
  for 4 hours and Livewire temporary files older than that.
- PHP's upload limits no longer cap the admin's max file size and
  default to 64M; LIVEWIRE_MAX_UPLOAD_TIME is gone and
  UPLOAD_CHUNK_SIZE_MB is new.
- Tests cover the format, key wrapping, registration limits, the chunk
  endpoint's answers, completing a share, the streamed ZIP, cleanup,
  and in Chromium a real chunked upload and the HTTPS warning; the
  selected-files overflow test runs again. README, website, CHANGELOG
  and .ai/rules follow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 20:49:17 +02:00
Andreas Reinhold / reiniandClaude Opus 5 504971ad7f Generate share passwords and offer them again beside the new link
Uploaders no longer have to make up a share password. With "Password
protect" on, the upload page has Generate and Copy under the field, and
the page the upload leads to offers the password once more beside the
link: masked, with the same copy button at the end of the field as the
link's. The password also derives the share's encryption key and only
its hash is stored, so a lost one means files nobody can open.

- PasswordGeneratorService draws from Random\Randomizer's secure engine.
  Characters are drawn uniformly and redrawn until every chosen set
  appears; passphrases come from EFF's large word list (CC BY 3.0 US,
  credited in the README), without its four hyphenated words.
- Admin settings gain a "Share Passwords" card: mode (off, on request,
  prefilled as protection is switched on), kind (characters: length
  12–64, the sets, look-alikes left out; passphrase: 4–10 words and a
  separator), and an example with its estimated entropy that follows the
  form before saving. Fields the chosen mode or kind hides are excluded
  from validation and keep their saved value. The default is on
  request, 20 letters and numbers without look-alikes.
- FileUploader flashes the password encrypted with the share's token;
  ShareCreated shows it only when the token matches, so a reload or any
  other visitor sees nothing. Crypt covers installs without
  SESSION_ENCRYPT, which the Docker setup does not set.
- The symbol set leaves out what chat apps turn into formatting and
  what breaks inside quotes, so a pasted password arrives unchanged.
- app.css imports group.css for <x-group>; .ai/rules/views.md records
  that <x-group> drops data-test and other attributes.
- Tests cover the generator, the admin card's saving, validation and
  example, prefill and generate on the upload page, the flash, and in
  Chromium Generate and Copy on the upload page and the masked copy on
  the share page. The admin settings page now has six headed sections.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 11:00:11 +02:00
Andreas Reinhold / reiniandClaude Opus 5 c95d0c43c2 Load the website's Plausible site script
linter / quality (push) Successful in 1m7s
tests / ci (8.5) (push) Successful in 2m16s
docker / build-and-push (push) Successful in 7m5s
docker / test (8.5) (push) Successful in 2m25s
docker / release (push) Successful in 5s
Plausible gives each new site its own script and an init call instead
of the generic script with data-domain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 16:21:51 +02:00
Andreas Reinhold / reiniandClaude Opus 5 21bea9646d Let the admin choose one of eight colour profiles
linter / quality (push) Successful in 1m3s
tests / ci (8.5) (push) Successful in 2m8s
docker / test (8.5) (push) Successful in 2m15s
docker / build-and-push (push) Failing after 7m22s
docker / release (push) Skipped
Indigo (the default), Blue, Teal, Green, Amber, Rose and Violet in the
Vibrant style and Graphite in the Neutral style are generated from
config into the stylesheet. Admin settings opens with a colour profile
card: a swatch previews the profile on the page, and Save Settings
stores it as color_profile, which AppServiceProvider hands to the
package's resolver, so every page, mail and error page wears it. An
unknown profile is refused, and a saved one that disappears falls back
to indigo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 15:06:47 +02:00
Andreas Reinhold / reiniandClaude Opus 5 d860a16551 Move SealShare from GitHub to Gitea only
docker / test (8.5) (push) Successful in 2m13s
linter / quality (push) Successful in 1m8s
tests / ci (8.5) (push) Successful in 2m7s
docker / build-and-push (push) Failing after 9m54s
docker / release (push) Has been skipped
The workflows live in .gitea/workflows. The Docker workflow logs in to
the Gitea container registry with REGISTRY_TOKEN (Gitea's job token cannot
publish packages yet), publishes gitea.nonameweb.ch/nonameweb/sealshare
with a registry build cache, and makes the release on Gitea with the
version's section of the changelog as its notes.

The README, the website's quick start, both compose files and the image
label point to Gitea; the changelog announces the new image name and
links Gitea. GiteaOnlyTest keeps GitHub and ghcr.io out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 13:59:07 +02:00
Andreas Reinhold / reiniandClaude Opus 5 606cc766f2 Add the SealShare website for sealshare.nonameweb.ch
docker / test (8.5) (push) Successful in 2m5s
docker / build-and-push (push) Failing after 9m38s
docker / release (push) Has been skipped
linter / quality (push) Successful in 1m8s
tests / ci (8.5) (push) Successful in 2m4s
website/ is hand-written HTML and CSS, uploaded as it is, like the
MailifySMS site: SealShare as software a company installs for its own
upload platform, how it works, features, a desktop/phone and light/dark
gallery of the generated screenshots, a dated and sourced comparison with
hosted transfer services and self-hosted tools, the Docker quick start,
FAQ and a privacy page for Plausible. Colours come from the app's scheme,
Google Sans Flex is served locally, nothing else loads from other hosts.

The README shows three screenshots and no longer calls the encryption
end-to-end. WebsiteTest guards missing files, other hosts, the screenshot
set and the encryption wording.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 12:38:49 +02:00