Skip to content

Authentication and Web Security

Elman Mohammadi edited this page Oct 6, 2026 · 1 revision

Authentication and Web Security

The application has a dedicated security layer around the V14 package engine. Cryptographic package security does not replace web authentication and request-level protections.

Sessions

The web layer configures PHP sessions with:

  • strict session mode
  • cookie-only sessions
  • HttpOnly cookies
  • SameSite=Strict
  • a dedicated session cookie name: SECUREPKGSESSID

On login, the session ID is regenerated and a new owner token is generated. CSRF state is rotated as part of login/logout transitions.

HTTPS

Production deployments should set:

SPK_REQUIRE_HTTPS=1

When enabled, the session layer rejects non-HTTPS requests.

The application also emits HSTS when the incoming connection is HTTPS.

CSRF protection

State-changing API actions require a CSRF token stored in the PHP session and supplied through:

X-CSRF-Token: <token>

The token is compared with constant-time equality using hash_equals().

The CSRF check also invokes the same-origin validation routine.

Same-origin protection

When an Origin header is present, the application validates it against either:

  • SPK_PUBLIC_ORIGIN, when explicitly configured; or
  • the current request scheme and validated HTTP host when no explicit origin is configured.

This creates a second browser request-boundary check alongside the CSRF token.

Security response headers

The web security layer emits headers including:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Origin-Agent-Cluster: ?1
X-Permitted-Cross-Domain-Policies: none
Cache-Control: no-store, no-cache, must-revalidate, max-age=0

HTML responses additionally use a restrictive Content Security Policy with same-origin scripts, styles, connections, images, fonts, and workers.

Account security

Usernames are normalized to lowercase and must satisfy the application's character and length policy.

The stored username identifier is an HMAC-SHA-256 digest using the server name pepper rather than a plaintext username in the account store.

Passwords are stored using PHP Argon2id password hashes.

When a username is not found, the verifier performs an Argon2id verification against a dummy hash to reduce timing differences.

Rate limiting

The file-backed RateLimiter uses an atomic fixed-window counter protected by file locking.

The default policy is:

12 failures / 900 seconds

The API applies this protection to sensitive operations such as:

  • login
  • encrypted archive password attempts
  • package opening/decryption

A successful operation clears the corresponding rate-limit record.

Authorization and ownership

AuthorizationService centralizes authentication and package-owner checks.

Package catalog records store a hash of the owner identifier. Package management operations compare that hash before proceeding.

Upload jobs are also owner-bound.

This prevents an authenticated user from simply guessing a package or job identifier and gaining access to another user's object.

Download tokens

Package downloads use randomly generated short-lived access tokens.

The token store records:

  • package ID
  • owner hash
  • creation time
  • expiration time
  • whether the token is one-time

Token storage is separate from package data. Revoking a package also removes its outstanding download tokens.

Restore tokens

Successful package restore creates a short-lived restore token mapped to the temporary restore directory.

The token is deleted when consumed or expired.

The implementation verifies that the restored directory is a real directory and rejects symbolic-link restore directories.

Replay protection

ReplayGuard stores a hash of a short-lived operation identifier and refuses to consume an identifier again until its record expires.

The storage operation uses file locking so parallel requests do not race between a check and a consume.

Audit logging

Security events are written as JSONL records using a SHA-256 hash chain.

Each event contains the previous record's hash, and a separate head file records the current chain head.

The logger also filters sensitive context keys such as:

  • password
  • pattern
  • secret
  • token
  • csrf
  • authorization
  • cookie

The goal is to make operational events auditable without turning the audit trail into a secret dump.

Canonical JSON

Security-sensitive structures use a deterministic JSON encoder that sorts associative keys while preserving list ordering.

This is used by header bindings, Merkle-related records, and release manifests so equivalent logical structures serialize predictably.

Trust boundary reminder

Authentication answers "who is allowed to invoke the application." Package decryption answers "which credentials can unlock this package." They are separate boundaries.

An authenticated account is not automatically entitled to plaintext from every package. Package catalog ownership, package availability, and V14 cryptographic credentials still matter.

Clone this wiki locally