Security hardening, delete-after-reading fix, WEIN & CO rebranding, Kubernetes deployment#1
Draft
predator2003 wants to merge 8 commits into
Draft
Security hardening, delete-after-reading fix, WEIN & CO rebranding, Kubernetes deployment#1predator2003 wants to merge 8 commits into
predator2003 wants to merge 8 commits into
Conversation
Security-driven refresh of the frozen v1.16.0 lockfile: - hono 4.7.5 -> 4.12.30 (JWT algorithm confusion GHSA-f67f-6cw9-8mq4, serveStatic path handling GHSA-q5qw-h33p-qvwr, JWT NumericDate validation GHSA-hm8q-7f3q-5f36, and further advisories) - @hono/node-server 1.13.8 -> 1.19.14 (serve-static authorization bypass GHSA-wc8c-qw6v-h7f6 and middleware bypass via repeated slashes) - lodash-es 4.17.21 -> 4.18.1 (_.template code injection) - unstorage 1.15.0 -> 1.17.5 (pulls patched h3 1.15.11 and defu 6.1.7) - vite 5.4.14 -> 5.4.21 (dev-server fs.deny bypasses, build-time only) Also adds @fontsource/mulish (self-hosted font, used by the branding commit) and enables skipLibCheck for @enclosed/lib to keep typecheck green with vite 5.4.21 type declarations (same setting as crypto). All package tests, typechecks and builds pass with the new versions. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01McgPwoZTeJF5Tgimar97t5
- Add a strict Content-Security-Policy (script-src 'self', no inline scripts), Permissions-Policy and a streaming request body limit sized to the configured max payload (memory-exhaustion protection), based on upstream PR CorentinTh#477. - Inject the runtime public config as a non-executable JSON script block instead of an inline executable script so the CSP can stay strict; the client reads the JSON block and falls back to the legacy global. - Refuse to start when authentication is required but AUTHENTICATION_JWT_SECRET is still the default value, and warn when users are configured with the default secret (upstream issue CorentinTh#445). - Generate note ids with non-monotonic ULIDs so ids created in the same millisecond keep their full 80 bits of randomness instead of being a predictable increment (upstream PR CorentinTh#478). Verified live: CSP/Permissions-Policy headers present, config block parsed by the client, server exits with code 1 when auth is enabled with the default secret. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01McgPwoZTeJF5Tgimar97t5
Previously the server deleted a delete-after-reading note on ANY fetch. Since the password is verified client-side, a wrong password attempt or a link-preview bot prefetching the URL permanently destroyed the note before the intended reader ever saw it (upstream issue CorentinTh#307). Now the note is only deleted once the reader confirms a successful decryption via the new POST /api/notes/:noteId/read-confirmation endpoint. The endpoint shares the note access guard with the GET route, only ever deletes notes flagged deleteAfterReading, and requires the capability note id. Web client and CLI confirm automatically after a successful decrypt (failures are ignored: content is already delivered and the TTL still applies). Trade-off: a reader that fetches but never confirms leaves the note until its TTL expires; previously it was deleted on fetch. This is the intended direction - availability for the legitimate reader wins over eager deletion, and the TTL remains the backstop. Covered by new unit and e2e tests; verified live against the built server (note survives repeated fetches, 404 after confirmation). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01McgPwoZTeJF5Tgimar97t5
The hidden file input kept its value after a file was removed from the list, so selecting the identical file again fired no change event and the upload silently did nothing until a page refresh (upstream issue CorentinTh#446). Clear the input value after reading the selection. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01McgPwoZTeJF5Tgimar97t5
Rebrand the client to the WEIN & CO corporate identity (weinco.at): - Color palette measured from the weinco.at theme: brand red #BC0936 as primary (hover #9F082F), warm greys #F5F1F1/#EEE9E9/#D9D4D0 as surfaces and borders, charcoal #3C3C3B text, champagne gold #CFBB79 available as --gold; matching dark mode variants. - Header becomes the brand-red bar with the WEIN & CO square logo, mirroring the weinco.at main navigation; footer shows the company imprint plus 'Powered by Enclosed (Apache 2.0)' attribution. - Self-hosted Mulish font (closest free match to the commercial Altis corporate font) replaces the Inter webfont from the bunny CDN - no external font requests, consistent with the strict CSP. - German index.html metadata (noindex for the internal instance), WEIN & CO favicons/webmanifest, app name replaced in all locales. - New-note navigation is now a full document load so an authenticating reverse proxy in front of the instance can challenge the user (upstream issue CorentinTh#461). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01McgPwoZTeJF5Tgimar97t5
- deploy/kubernetes/: namespace, configmap, example secret, PVC, hardened single-replica Deployment (runAsNonRoot 1000, fsGroup, readOnlyRootFilesystem, drop ALL, /api/ping probes), service, TLS ingress with proxy-body-size sized for the 50 MiB payload cap, and a kustomization for one-command deploys. - docs: new Self-hosting -> Kubernetes page covering the architecture constraints (single-writer fs storage, mandatory HTTPS for WebCrypto, non-root volume ownership) plus an agent prompt for automated deployment; registered in the sidebar. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01McgPwoZTeJF5Tgimar97t5
Correctness and security: - Body limit is now resolved per request from the config middleware, so deployments that only configure through the environment (Cloudflare entry passes no config object) honor NOTES_MAX_ENCRYPTED_PAYLOAD_LENGTH instead of a construction-time 50 MiB constant; the 413 response now goes through the shared error factory. Verified live with a 1 KB limit. - The note access guard no longer acts as a note-existence oracle on auth-required instances: unauthenticated requests get the same 401 for missing and for existing private notes (the previous 'if (!note)' branch was dead code because getNoteById throws 404). - Delete-after-reading backstop: fetching a delete-after-reading note that has no expiration assigns a 24h fallback TTL, so a reader that never confirms (old client, curl) cannot keep the note alive forever. - CSP connect-src now includes the origin of an absolute PUBLIC_BASE_API_URL so a cross-origin API setup is not blocked. - The web client sends the read confirmation with fetch keepalive so closing the tab right after decryption does not abort the deletion; the CLI prints the note before confirming instead of after. Cleanups from review: - DEFAULT_JWT_SECRET exported from the config module instead of a duplicated literal; startup guards restructured without overlap. - config.provider uses safelySync and drops the unreachable legacy window.__CONFIG__ fallback. - Removed dead i18n keys (footer.crafted-by/source-code/github, navbar.github/github-repository/settings.support) from all locales, fixed the Hungarian article before the new brand name, removed the now-dead form-reset call before the full-page new-note navigation, deduplicated header trigger classes and dark-theme CSS variables, aligned bundled Mulish weights with actual usage (300 added, unused 800 dropped), updated robots.txt/humans.txt leftovers. - Missing awaits added to rejects assertions in notes usecases tests; new tests cover the fallback-TTL behavior (69 server tests total). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01McgPwoZTeJF5Tgimar97t5
- de.json now covers all 98 keys of the English reference (was 78): QR-code sharing, password visibility toggles, no-expiration switch, loading/attachment strings, plus 'Design' and 'Anmelden' instead of the leftover English 'Theme'/'Login'. - Header bar is taller (py-4) with a larger logo (h-13, rounded-md, subtle shadow) and a larger app title so the brand mark carries the header, as on weinco.at. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01McgPwoZTeJF5Tgimar97t5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This PR brings the fork from a clean upstream v1.16.0 to a hardened, rebranded, Kubernetes-ready instance. Upstream has been dormant since v1.16.0 (July 2025), so the security-relevant open upstream PRs were adapted here directly. There are no published CVEs against enclosed itself; the work below closes dependency advisories and hardening gaps.
1. Security
Dependency refresh (lockfile was frozen ~15 months): hono 4.7.5 → 4.12.30, @hono/node-server 1.13.8 → 1.19.14, lodash-es → 4.18.1, unstorage → 1.17.5 (pulls patched h3/defu), vite → 5.4.21.
pnpm audit --proddrops from 75 findings to 22, and all remaining ones are client build-chain only — the server runtime is clear.Hardening (adapted from unmerged upstream PRs CorentinTh#477/CorentinTh#478 and issue CorentinTh#445):
script-src 'self', no inline scripts) + Permissions-Policy. The runtime config injection was converted from an inline executable script to a non-executable JSON block so the CSP can stay strict;connect-srcincludes the origin of an absolutePUBLIC_BASE_API_URL.NOTES_MAX_ENCRYPTED_PAYLOAD_LENGTH(memory-exhaustion protection, honored on env-only configs like the Cloudflare entry too).AUTHENTICATION_JWT_SECRETis stillchange-me(verified: exit code 1).2. Delete-after-reading data-loss fix (upstream issue CorentinTh#307)
Previously any fetch deleted the note — a wrong password attempt or a link-preview bot destroyed it before the recipient saw it. Now deletion is two-phase: the note is deleted only after the client confirms successful decryption via
POST /api/notes/:noteId/read-confirmation(web client sends it with fetch keepalive; CLI confirms after printing). Backstop for non-confirming readers: a fetched delete-after-reading note without expiration gets a 24h fallback TTL. Trade-off documented in the commit: one-time-read is now cooperative rather than fetch-enforced, in exchange for eliminating accidental destruction.Also: file re-upload after removal works again (CorentinTh#446), and "New note" performs a full document load so SSO reverse proxies can challenge (CorentinTh#461).
3. WEIN & CO rebranding
Palette measured from weinco.at theme files: brand red
#BC0936primary, warm greys, charcoal text, dark-mode variants; red header bar with the square logo; WEIN & CO favicons/webmanifest; German metadata (noindex); app name replaced across all 17 locales; self-hosted Mulish font (no font CDN, CSP-compatible); footer with company imprint and "Powered by Enclosed (Apache 2.0)" attribution.4. Kubernetes
deploy/kubernetes/reference manifests (hardened non-root single-replica Deployment with/api/pingprobes, PVC, TLS ingress sized for 50 MiB payloads, kustomization) plus a new docs page Self-hosting → Kubernetes including an agent prompt for automated deployment.Verification
Notes for reviewers
🤖 Generated with Claude Code
https://claude.ai/code/session_01McgPwoZTeJF5Tgimar97t5
Generated by Claude Code