ByteBite Docs
Infrastructure

Security

Security boundaries, implemented controls, known gaps, and planned hardening.

Security

ByteBite staging follows a defense-in-depth model: minimal public exposure, internal-only services, and strict tenant trust boundaries.

Implemented controls

Network exposure

ControlDetail
Public portsOnly 80 and 443 on 88.99.0.118 (verified 2026-09-08)
App ports closed3000, 3001, 3002, 5432, 8080 not reachable externally
Internal routingTraefik → Docker network only
PostgreSQLNo public port; internal Dokploy network only

TLS

ControlDetail
HTTPS everywhere (staging)HTTP → HTTPS redirect verified on all explicit hosts
Valid certificatesLet's Encrypt HTTP-01 (explicit) + DNS-01 (wildcard)
Separate ACME storesacme.json and acme-cloudflare.json isolated

Tenant trust boundary

ControlDetail
trustProxy: falseFastify does not trust X-Forwarded-* from clients
Tenant from Host only (external)Browser and same-origin /api use Traefik-delivered Host
X-ByteBite-Tenant-Host restrictedHonored only when request Host is internal (api, localhost, 127.0.0.1, ::1)
No public API portRaw api:3001 not host-published
Exact hostname lookupNo slug inference from DNS labels

Secrets

ControlDetail
No secrets in Git.env git-ignored; Dokploy env for staging
Cloudflare token scopedZone Read + DNS Edit on getbytebite.co only
Token file permissions600, root:root on host filesystem
ACME keys on hostNot in repository

Container security

ControlDetail
Non-root runtimeNode apps run as node user; nginx unprivileged for dashboard
Docker socketTraefik mounts read-only
No privileged modeNot used in deployment compose
No bind mounts (deploy)Application images are self-contained

DNS

ControlDetail
DNS-only CloudflareNot proxied — Traefik terminates TLS directly
Edge indirectionedge.getbytebite.co centralizes IP changes

PostgreSQL backup security

ControlDetail
Off-host storageBackups stored in Backblaze B2, separate from APP VM
Client-side encryptionRestic encrypts repository before upload
Credential isolationB2 and Restic credentials root-only under /etc/bytebite/backups/
Temporary dump cleanupLocal bytebite.dump deleted after backup script exits
Isolated restore testingRestore drill uses postgres:18 with --network none, no host ports
Live DB isolationRestore drill never targets the live staging database
Concurrent operation preventionShared flock on /run/lock/bytebite-postgres-backup.lock

A verified PostgreSQL restore does not mean full infrastructure disaster recovery is complete.

Staging indexing

ControlDetail
noindex, nofollowLanding, storefront, docs (when BYTEBITE_DEPLOY_ENV=staging), dashboard (always)

Staging access protection

HTTP BasicAuth at Traefik, not in the applications. Browsers get a login prompt; curl needs -u user:pass. Internal Docker traffic (storefront SSR → api:3001, container HEALTHCHECKs) does not pass through Traefik and is not gated.

ControlDetail
Middlewarebytebite-staging-auth (Docker provider)
Scopestaging.getbytebite.co and *.staging.getbytebite.co HTTPS routers in compose.deploy.yml
RealmByteBite Staging
CredentialHashed user:hash in Dokploy env STAGING_BASIC_AUTH_USERS — never Git
Header forwardingremoveHeader=true — Authorization is not sent to the apps
Out of scopedokploy.getbytebite.co (own login); HTTP entrypoint (redirect only); future production hosts

Generate the Dokploy value (SHA is Compose-safe because the hash has no $):

./infrastructure/scripts/staging-htpasswd.sh bytebite

Set STAGING_BASIC_AUTH_USERS in the Dokploy compose environment, then redeploy (labels are interpolated at compose parse time; a container restart is not enough). Changing the password uses the same flow. Compose refuses to start if the variable is unset (:? interpolation).

This is a shared team gate, not application authentication (ADR-019). Rotate if the password is pasted into tickets or screenshots.

Known gaps

GapSeverityStatus
Dashboard SPA fallback returns HTTP 200 for /.env, /.git/HEAD, etc.MediumOPEN
Unknown storefront tenant returns HTTP 200 instead of 404Low (SEO)OPEN — application issue
No WAF or rate limiting at edgeMediumOPEN
No centralized security monitoringMediumOPEN
Public internet scanner noiseInformationalACCEPTED — normal for exposed services
Dokploy Traefik custom wiring may not survive upgradesHighOPEN — ops risk

Dashboard dot-path issue

Requests such as /.env, /.git/HEAD, /.git/config to the dashboard return HTTP 200 with the SPA index.html (~566 bytes). This is not evidence of real file exposure but is undesirable HTTP semantics. Storefront and docs correctly return 404 for these paths.

Scanner traffic

Public internet scanners probe common paths (.env, .git, GraphQL, PHP, WordPress). This is normal for internet-exposed services and does not indicate compromise.

Planned hardening

ItemStatus
Webhook exceptions on staging BasicAuth (Stripe / Twilio, when added)PLANNED
Production WAF strategyPLANNED
Production custom-domain TLS verificationPLANNED
CSP and security headers auditPLANNED

On this page