Freenet requires different security considerations than other projects.
Any security issue that can correlate your activity with easily observable behavior of your node is critical.
This specifically means:
- any way to crash Freenet when accessing some known content is serious.
- if you can get Freenet or the browser opening a site from Freenet to make a request to some clearnet server depending on the content being accessed, this is serious.
More so if the security issue affects the friend-to-friend mode.
There are known unfixable attacks against opennet (Sybil attacks cannot be prevented completely, only their impact reduced).
There are no known unfixable identification-attacks against friend-to-friend mode, except if your friends' nodes attack you.
Attacks we know about are detailed on the opennet attacks and the major attacks page:
Best practices, hardening tips, and similar are not security issues. Please report them to our regular bugtracker: https://bugs.freenetproject.org
If you are unsure whether your issue is a security issue, please come to our IRC channel (#freenet at irc.libera.chat) and talk to an op: https://web.libera.chat/?nick=FollowingTheRabbit|?#freenet
Please report security issues to security@freenetproject.org encrypting to all PGP/gnupg keys from our Keyring.
Please do not file public reports of security problems that could be used to connect the pseudonyms of users with the nodes they run. If you find those, please send them to the email address above so they can be resolved and the fix released before the vulnerability gets someone in danger.
We will acknowledge a report within one week. If you do not get a reply within one week, it most likely got lost: Please send it again!
Signed AppHost bundles and signed app catalogs use developer-supplied Ed25519 key material during local build, verification, install, and catalog-refresh flows.
- Do not commit production private keys, keystores, or exported PKCS#8 private key material to this repository.
- Keep local development keys outside the repo and pass them through Gradle properties or environment variables, or point at local files outside the checkout.
- If you generate a development-only key pair for testing signed bundles or catalogs, label it clearly as non-production and rotate or remove it when no longer needed.
- The unsigned-bundle bypass is only for explicit local development. It does not make remote catalogs or remote artifacts trusted.
Catalog installs and updates verify the signed catalog, the advertised artifact size and SHA-256,
and the extracted bundle signature before AppHost installs the app. The catalog signature
authenticates catalog bytes and publisher metadata only. Legacy catalog review.status and
review.note fields are publisher-advisory metadata and are not a cryptographic review trust
boundary.
Independent app review receipts use a separate trusted reviewer-key registry. A trusted receipt signature binds the reviewer key id, app id, app version, artifact digest, artifact size, review policy id/version, reviewer status, timestamps, and optional evidence metadata to canonical receipt payload bytes. Do not reuse app or catalog signing trust implicitly for review receipts, and do not commit reviewer private keys. Platform API and Web Shell review-trust responses may expose reviewer key ids, display names, policy ids, timestamps, evidence digests, and evidence URIs, but must not expose reviewer public key bytes, reviewer private key material, local receipt or evidence paths, catalog scratch paths, staging paths, browser session tokens, or AppHost process tokens.
Catalog fetches support local files, https:, and loopback-only http: sources. Catalog ZIP
extraction drops macOS __MACOSX/** and AppleDouble ._* metadata entries before verification;
executable app payload still has to match the signed bundle digest.
App-owned static UI routes serve files from the immutable installed bundle. Static apps prefer a
distinct loopback-only browser origin per app, with /apps/{appId}/ retained as a compatibility
and diagnostics fallback. They do not serve app data, cache, run directories, catalog scratch
directories, or caller staging paths. External URL entries are rejected, app UI listeners bind only
to loopback addresses, and route responses use conservative CSP, nosniff, no-referrer, and
non-public no-cache headers.
AppHost process launches are local child processes unless a sandbox provider is selected. AppHost
starts each app with the installed bundle root as its working directory, a minimal environment,
per-app data/cache/run directories, and a per-launch CRYPTAD_APP_TOKEN for app-originated
Platform API authentication. App manifests can request sandbox.mode=none,
restricted-process, or wasm-preview, and Platform API/Web Shell surfaces report the requested
mode and actual support level. On Linux hosts with bubblewrap available, restricted-process can
run through the bubblewrap provider and report supportLevel=enforced. That provider enforces
filesystem containment for the installed bundle and AppHost-managed mutable directories, but it
does not enforce CPU, memory, or network limits. When bubblewrap is unavailable and the app does
not require an enforced sandbox, the existing restricted-process provider remains best-effort
launch hygiene, not a container, WASM runtime, seccomp profile, chroot, jail, Windows Job Object
policy, network isolation, or browser UI origin isolation. Browser UI origin isolation is provided
by the app-owned UI loopback listener layer and does not make the child process sandboxed.
AppHost enforces manifest data/cache quotas only when quota.data.bytes or quota.cache.bytes is
positive. Missing quota fields and explicit 0 values mean unlimited or no explicit app quota for
backward compatibility with existing first-party app manifests. Enforcement is scoped to
AppHost-managed app data and cache directories, uses path-free status warnings for incomplete scans,
and blocks launch or automatic restart for positive quotas when an enforced area cannot be measured
completely. AppHost also bounds each managed process.log file on a best-effort basis at lifecycle
and status checkpoints, while retaining a small redaction overlap before the visible tail so log
bounding does not split tokens or known AppHost paths away from the context needed to redact them.
These quotas do not provide CPU, memory, network, container, or full filesystem isolation and do not
replace the sandbox provider model.
App-originated Platform API calls authenticate with the launch token in X-Crypta-App-Token.
Authorization: Bearer is accepted only when the Bearer value matches a live app token; unrelated
Bearer credentials continue through the host/operator path so reverse proxies and shared clients do
not accidentally convert local management requests into failed app-token attempts. Valid app
principals are denied by default unless the route is covered by manifest-declared capabilities such
as queue.read, queue.write, or content.insert. Invalid or stale X-Crypta-App-Token values
fail authentication, and missing capabilities fail authorization without echoing the token.
App-owned static browser UI uses a separate browser app session. On isolated app origins, the
dynamic bootstrap at /.well-known/cryptad-bootstrap.json returns route metadata, the absolute
admin Platform API root, UI origin metadata, an opaque browserSessionToken, and an expiry
timestamp. The compatibility /apps/{appId}/.well-known/cryptad-bootstrap.json route remains for
explicit same-origin fallback. Static UI sends that token as X-Crypta-App-Session; the Platform
API treats the request as an app browser principal and applies the same manifest capability matrix.
Browser sessions are distinct from CRYPTAD_APP_TOKEN, are bound to one installed static app and
the expected browser origin, and must not be persisted by app JavaScript. Invalid browser sessions
fail with 401 invalid_app_browser_session; mismatched isolated origins fail with
403 origin_mismatch.
Isolated bootstrap token issuance also requires an admin/Web Shell launch proof. Web Shell first
checks that the current browser can read a token-free origin probe from the app loopback listener.
Only then does it send an explicit same-origin compatibility launch request, which redirects to the
isolated origin with a short-lived nonce in the URL fragment. If the probe is not reachable, such as
from a remote browser or a single-port tunnel, the compatibility route remains a same-origin
fallback. The SDK echoes the launch proof as X-Crypta-App-Bootstrap-Nonce before the loopback
server returns a browser-session token. Public app-origin URLs and app summaries do not contain this
proof and are not enough to mint browser sessions.
Platform API CORS for app browsers is restricted to active registered app UI origins. It never uses
wildcard Access-Control-Allow-Origin, never allows cookies or local-admin credentials, and does
not allow X-Crypta-App-Token through app-browser preflight. Requests from registered app origins
without X-Crypta-App-Session fail as app-browser requests and cannot silently fall back to
host/operator authentication.
Runtime status and process-log Platform API responses must remain token-free and path-free. Log
tail responses redact the current launch token and obvious CRYPTAD_APP_TOKEN=... text before
returning app output to the Web Shell. Process-log truncation keeps enough overlap before the
bounded tail for that redaction step. This is defense in depth for operator visibility; it is not a
general secret scanner for arbitrary app output.
App-originated allowed and denied Platform API decisions are recorded in a bounded process-local audit log. Audit events keep route family, action, required capabilities, decision, status, and a short reason code. They also include the token-free authentication source so operators can distinguish process-token requests from browser-session requests. They must not include raw launch tokens, raw browser session tokens, query strings, request bodies, form passwords, or filesystem paths.
The app secret and identity vault is a local at-rest protection boundary, not a remote KMS, hardware-backed enclave, OS keychain, or replacement for process sandboxing. Vault records are encrypted with a host-local wrapping key file protected by local filesystem permissions; that protects against casual offline disclosure but not malware, a compromised daemon, a debugger, or a same-user process. App secret values, identity private keys, signing payloads, private insert URIs, vault storage paths, browser session tokens, app process tokens, and form passwords must not appear in public JSON, Web Shell summaries, app audit events, release-certification reports, diagnostics, or logs. App browser principals are limited to identity metadata and grant-request workflows by default; raw secret read/write and identity-use operations are app-process routes unless a future contract explicitly broadens that surface with tests and docs.
Static UI code should run on its isolated per-app loopback origin when available. Browser app
sessions improve server-side attribution and capability enforcement for SDK/API calls, and the
isolated origin separates app JavaScript from the Web Shell/admin origin. The compatibility
same-origin /apps/{appId}/ path is for fallback and diagnostics, not the preferred third-party
app UI boundary.
Treat a bypass that serves host files, follows symlink escapes, executes JavaScript while the admin UI has JavaScript disabled, exposes AppHost launch tokens or browser session tokens to the wrong surface, binds app UI listeners to wildcard or LAN-visible interfaces, shares one browser origin across unrelated static apps, authenticates app browser requests as host/operator requests, bypasses app capability checks, or allows bundled UI to exfiltrate operator-entered data off-node as security-relevant.
For the detailed runtime boundary, exposed environment variables, restart policy semantics, permission matrix, audit model, and remaining limitations, see apphost-runtime-hardening.md and app-permissions-and-audit.md.