This document defines the minimum security posture for iVPN. It is normative for implementation and release. These are requirements, not evidence that a deployment satisfies them. The fuller threat analysis is in THREAT_MODEL.md.
iVPN supports one operator publishing approved content to one X account through a dedicated France exit and one Nostr identity through rotating, allowlisted VPN countries. It is a source-IP privacy tool, not an anonymity system or an enforcement-evasion tool.
The implementation must not support account fleets, third-party accounts, platform limit circumvention, browser automation of X, session-cookie reuse, misleading location claims, or silent network fallback.
- Linux is the security baseline for the first release.
- The controller and posting worker run unprivileged.
- A minimal broker holds only the network privileges needed to create and destroy the per-post namespace and WireGuard interface.
- The broker accepts only validated
job_id,platform, and registeredendpoint_idvalues over a root-owned Unix socket. It accepts no command, path, route, arbitrary address, or shell fragment. - Each platform attempt gets a fresh namespace containing only loopback and one WireGuard interface.
- nftables policy is drop-by-default. No host or physical-interface route is available to the worker.
- The worker has a private resolver configuration that uses only a resolver through the tunnel.
- IPv6 is tunneled or disabled inside the namespace.
- X server-side OAuth token exchange, refresh, publication, and reconciliation use only the registered dedicated France endpoint. The user-agent authorization page follows the browser's route and is outside the posting worker.
- NIP-46 signing, Nostr relay requests, and Nostr country verification use one separately selected and pinned Nostr endpoint.
- X and Nostr execute in separate namespaces and have independent receipts and terminal states.
- Workers run as non-root UIDs with read-only filesystems, per-attempt tmpfs, and no capability beyond the minimum required by the fixed launcher. The broker destroys the namespace, interface, tmpfs, and credential handles after every outcome.
- Tunnel or verification failure stops the job. There is no direct-network fallback.
- The publishing path contains no browser, WebView, or WebRTC stack.
- The country pool is an explicit allowlist of provider, country, and exit tuples.
- Countries prohibited by the operator's applicable law or provider/platform terms must not enter the pool.
- Selection occurs at dispatch using an operating-system CSPRNG and unbiased sampling.
- Select uniformly by healthy country, excluding the immediately previous country, then select an exit within that country.
- Require at least three healthy approved countries. If no different healthy country exists, return a visible failure and leave the Nostr destination queued.
- Do not precompute or log future selections.
- GeoIP can identify disagreement, not prove physical server location. UI and logs must call the value an exit country, never the user's location.
- Pin the selected endpoint across signing, every relay attempt, and retry for the event.
- Pin production X traffic to one registered static France endpoint.
- Keep
rotating_countrydisabled in production policy. - Never change the X endpoint after a 403, 429, challenge, restriction, exhausted credit response, or ambiguous result.
- Enabling X rotation requires a test-account pilot plus written X clarification or a new explicit risk decision and ADR.
- All request builders omit X geotags and Nostr location tags. A future user-supplied location feature requires separate review and explicit consent.
- Prefer NIP-46. The main application must not receive or store the user's private key.
- Pin the expected signer public key and validate the NIP-46 connection secret.
- Request
sign_event:1only. Addsign_event:22242only when a configured relay requires NIP-42 authentication. - The signer must enforce permissions and reject unexpected event kinds.
- Never fall back from NIP-46 to a locally stored private key.
- If local-key support is ever proposed, it requires a new threat-model review and ADR before implementation.
- Use the official X API and OAuth 2.0 Authorization Code with PKCE.
- Validate
state, use an exact redirect URI, and use a cryptographically random verifier. - Request only
tweet.write,tweet.read, andusers.read; requestoffline.accessonly for approved unattended scheduling. - Never request or store the X password, session cookies, or a user's developer secrets.
- Refresh and revoke tokens through the dedicated X France endpoint.
- Bind credentials to the single configured X user ID and reject a mismatched account response.
- VPN private keys and provider credentials are root-readable only and unavailable to the worker.
- Prefer distinct keys per provider or exit when the provider supports them.
- Encrypt persistent OAuth and pairing material with an OS or TPM-backed key.
- Deliver short-lived credentials through a sealed memory handle or pipe, not command-line arguments, environment variables, or temporary files.
- Disable core dumps for secret-bearing processes. Do not enable swap of secret memory where the runtime can safely prevent it.
- Zeroization is best effort in managed runtimes and must not be presented as proof that memory cannot be recovered.
- Secrets, real account IDs, and production VPN configurations must never be committed to the repository or included in fixtures.
- Provide tested rotation and revocation procedures before production use.
- Authentication is not publishing consent.
- Before queueing, show the exact content, X and Nostr destinations, and whether any geotag will be attached.
- Bind approval to a hash of content, target set, media set, and geotag state. Any change invalidates approval.
- Use an explicit durable state machine:
DRAFT,APPROVED,ROUTING,PUBLISHING,COMPLETE,PARTIAL,UNKNOWN, orFAILED. - The X and Nostr payload hashes are bound to one approval, but each platform executes in its own namespace and resolves independently.
- One signed Nostr event ID and one pinned Nostr egress lease are reused for all configured relay submissions.
- Validate Nostr event serialization, event ID, public key, and signature before relay submission.
- Require the configured Nostr relay acknowledgement quorum.
- Record an X post as successful only after receiving or reconciling its public post ID.
- A timeout after an X request may have been accepted is
UNKNOWN, not an automatic retry. - Cross-platform posting cannot be atomic. Expose partial completion and never claim rollback.
Logging is allowlist-based. Adding a field requires a privacy review.
Allowed by default:
- Random job ID.
- Normalized job state and stable error code.
- Selected Nostr exit country after dispatch and the non-secret X endpoint identifier.
- Public X post ID and Nostr event ID after success.
- Relay acknowledgement counts without raw messages.
- Component version and bounded duration metrics.
Prohibited:
- Draft or published content, links, media, or approval payloads.
- X access tokens, refresh tokens, PKCE verifier, authorization code, passwords, cookies, or Authorization headers.
- Nostr private keys, NIP-46 connection secrets, encrypted signer payloads, or raw signed events.
- VPN private keys, provider credentials, account identifiers, configuration bodies, or future country choices.
- Exact exit IP, home IP, VPN endpoint IP, raw DNS answers, or packet payloads.
- Raw HTTP request or response bodies and raw relay notices. Normalize remote errors because they may echo content or secrets.
- Process environment or command lines.
Debug mode must not relax these rules. Production builds must disable verbose HTTP tracing and secret-bearing core dumps.
- Encrypt drafts and approved queued content at rest.
- Keep content only until both platforms reach a resolved state. Unknown or partial jobs retain the minimum encrypted payload needed for safe reconciliation.
- After resolution, remove local content and keep minimal encrypted operational metadata for seven days.
- After seven days, delete operational metadata and destroy expired encryption keys where key separation permits cryptographic erasure.
- Keep only aggregated endpoint-health metrics for 90 days. They must not contain job IDs, content, account identifiers, exact IPs, or per-post timestamps.
- Exclude tokens, VPN keys, drafts, and raw job databases from ordinary backups. Any encrypted recovery backup needs a documented owner, purpose, expiry, and restore test.
- Deletion on SSD, journaling filesystems, snapshots, and provider-managed storage cannot be guaranteed byte-for-byte. Documentation must say so.
See ADR 0003 for the decision and exceptions.
- Prefer standard-library and operating-system facilities before adding a dependency.
- Pin direct dependencies and commit the appropriate lock file.
- Review new dependencies for maintenance, license, published vulnerabilities, transitive footprint, network behavior, and safer alternatives.
- Generate an SBOM and run dependency, secret, static-analysis, and license checks for release artifacts.
- Build release artifacts from a clean checkout with reproducible commands and signed provenance where the toolchain supports it.
- Do not bypass checks with
--no-verify, swallowed exceptions, expected-failure markers, or hardcoded test-only routes. - Treat VPN configuration and Nostr relay lists as untrusted input and validate size, scheme, host, port, and identifier format.
- Bind the UI only to the management VPN interface, serve TLS, and reject access through egress or public interfaces.
- Require two registered WebAuthn passkeys plus an offline recovery code. Require an authenticated, CSRF-protected session for approvals, recovery, emergency stop, and account re-pairing.
- Rate-limit locally below current platform limits and provide an immediate stop control.
- Do not automatically change behavior to work around 401, 403, 429, policy errors, suspension, or geographic denial.
- Time synchronization must be healthy before Nostr signing, but clock failure must not trigger insecure fallback.
- Health checks must use the same network isolation as the operation they authorize and expire after 90 seconds. Three failures quarantine an endpoint; route, DNS, static-IP, country, or TLS mismatch quarantines it immediately.
- Provider and country health data expires quickly and is revalidated at dispatch.
On suspected token, signer, or VPN-key compromise:
- Stop the scheduler and destroy active worker namespaces.
- Revoke X authorization and NIP-46 client access.
- Rotate affected VPN keys and local encryption keys.
- Preserve sanitized state needed to identify affected public post and event IDs.
- Inspect for unauthorized public posts through authoritative platform interfaces.
- Correct the root cause, not only the exposed credential.
- Rerun all three adversarial rounds before restoring unattended operation.
Do not place real secrets in a public issue. Until a private reporting channel is published, use the repository host's private security-advisory mechanism if available and share only redacted reproduction details.
A release is blocked unless:
- The threat model and ADRs match implemented behavior.
- Unit, integration, network-namespace, IPv4, IPv6, DNS, and crash-recovery tests pass from a clean environment.
- Physical-interface capture proves that the separate X and Nostr workers, signer, resolver, and verifier traffic cannot bypass WireGuard.
- Canary-secret scans find no disclosure in logs, state, process metadata, crash artifacts, or backups.
- Ambiguous X outcomes and mixed Nostr acknowledgements preserve truthful states without blind duplicates.
- The second-account, geotag, non-API X, X-rotation, and direct-route abuse tests are rejected.
- Three clean adversarial rounds pass with no unresolved medium-or-higher finding.
Evidence must name the command, environment, artifact, and result. A successful build alone is not evidence that privacy controls work.