Provider security
Saved Connections hold API keys and URLs that point at other servers. This page lists the controls around them, the rules for provider URLs and the server settings to check before you expose EdgeQuake. The full threat model is in Security.
Controls at a glance
Section titled “Controls at a glance”| Control | What it does | Setting |
|---|---|---|
| Encrypted keys | Connection keys are stored with AES-256-GCM. API responses show only a fingerprint and key_configured. |
EDGEQUAKE_SECRETS_KEY |
| Write-only keys | You can set or replace a key. You can never read it back. | none |
| URL checks | Saved and tested URLs must pass the SSRF rules below. | allow_private_network per Connection |
| Admin only | Creating, updating, deleting and testing Connections, and the probe, need the admin role. | EDGEQUAKE_AUTH_ENABLED |
| Loopback publish | The quickstart publishes ports on 127.0.0.1 only. |
docker-compose.quickstart.yml |
Set the encryption key
Section titled “Set the encryption key”EDGEQUAKE_SECRETS_KEY must hold 32 bytes. Use one of three forms: 32 raw characters, base64 that decodes to 32 bytes, or 64 hex characters. Without a valid key, saving an API key returns 400 and nothing is stored.
export EDGEQUAKE_SECRETS_KEY="$(openssl rand -base64 32)"- Keep this value. If it changes or is lost, stored keys cannot be decrypted. See Fallback for what happens then.
EDGEQUAKE_SECRETS_KEY_ID(defaultv1) labels each stored envelope. The server loads one key, so there is no rotation. To change the key, enter each stored key again.quickstart.shgenerates a new key on each run unlessEDGEQUAKE_SECRETS_KEYis already exported. Export your own key before the first run.
Provider URL rules
Section titled “Provider URL rules”| URL | Result |
|---|---|
Scheme other than http or https |
Rejected |
Host names *.internal, metadata.google.internal, kubernetes.default, instance-data or metadata |
Always rejected |
169.254.x.x (cloud metadata), other link-local addresses, fe80::/10 |
Always rejected |
Decimal, hex or octal IP spellings such as 2130706433 or 0x7f000001 |
Rejected |
Loopback, 10.x, 172.16-31.x, 192.168.x, ::1, unique-local IPv6 (fc00::/7) |
Rejected unless allow_private_network is true |
| Public host name or address | Allowed |
allow_private_network defaults to true when locality is local. If you omit locality, EdgeQuake sets it from the URL: localhost, loopback and private addresses are local.
Host names are resolved. Saving a Connection and testing a URL look up the host name and check every returned address against the same rules. A name that does not resolve is an error. The check runs before the client connects, so a host name that later points somewhere else (DNS rebinding) can still get past it. Literal IP addresses skip the lookup.
The Settings page sets locality and allow_private_network from the URL. A host such as localhost, 127.*, 10.*, 192.168.*, 172.16-31.* or an fc/fd IPv6 address is saved as local with private networks allowed. Any other host is saved as cloud.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#E0E7FF","primaryBorderColor":"#6366F1","primaryTextColor":"#1E1B4B","secondaryColor":"#D1FAE5","secondaryBorderColor":"#10B981","secondaryTextColor":"#064E3B","tertiaryColor":"#FEF3C7","tertiaryBorderColor":"#F59E0B","tertiaryTextColor":"#6B7A90","lineColor":"#7A889C","clusterBkg":"rgba(99,102,241,0.07)","clusterBorder":"#A5B4FC","noteBkgColor":"#FEF9C3","noteTextColor":"#422006","textColor":"#6B7A90","titleColor":"#6B7A90","signalColor":"#7A889C","signalTextColor":"#6B7A90","loopTextColor":"#6B7A90","edgeLabelBackground":"#F1F5F9","actorLineColor":"#94A3B8"}}}%%
%% eq-theme:v1
flowchart TD
A["Provider URL"] --> B{"http or https?"}
B -- "No" --> X["Error: URL rejected"]
B -- "Yes" --> C{"Blocked host name or metadata IP?"}
C -- "Yes" --> X
C -- "No" --> D{"Private or loopback IP and not allowed?"}
D -- "Yes" --> X
D -- "No" --> E["Resolve host name (DNS)"]
E --> F{"Every returned address allowed?"}
F -- "No" --> X
F -- "Yes" --> G["Save or test the URL"]
%% eq-classes
classDef eqLlm fill:#FEF3C7,stroke:#F59E0B,color:#451A03
classDef eqBad fill:#FEE2E2,stroke:#EF4444,color:#7F1D1D
class A eqLlm
class X,C eqBad
Read it top to bottom. Each red end box is a refusal. Literal IP addresses skip the DNS step.
Server settings to check
Section titled “Server settings to check”EDGEQUAKE_AUTH_ENABLEDis read first and wins overEDGEQUAKE_DEV_MODE. The Docker quickstart sets dev mode on and auth off by default, so turn auth on before you expose the port.JWT_SECRETmust be at least 32 bytes and must not be the public default. Otherwise the server refuses to start, unlessEDGEQUAKE_DEV_MODEis on.EDGEQUAKE_SETUP_TOKEN: when set,POST /api/v1/setup/initializeneeds the headerX-EdgeQuake-Setup-Tokenwith the same value. Otherwise it returns401.- Production checklist: Runtime auth hardening. Also set
EDGEQUAKE_CORS_ORIGINS,ALLOW_REGISTRATION=falseandEDGEQUAKE_STRICT_STARTUP=1.
Run edgequake doctor to check the secrets key, the JWT secret, DATABASE_URL and the bind address in one step.
Related: Providers overview, Workspace roles.