Encryption in transit
Every request is served over TLS, including customer domains.
HSTS is sent on every response with a two-year max-age, includeSubDomains and preload, so a browser that has seen the site once refuses to talk to it over plain HTTP again. Custom domains are served over TLS too; certificate issuance and renewal are handled by the hosting platform the operator deploys to.
Password storage
bcrypt at cost 12, for account passwords and link passwords alike.
Plaintext passwords are never written anywhere — not to the database, not to logs. Verification against a missing hash still burns the same amount of time as a real comparison, so response latency does not reveal whether an account exists.
API keys
Stored as a SHA-256 digest. The key itself is shown exactly once.
We cannot show you a key again because we do not have it — only its hash, which is what an incoming request is matched against. Keys carry explicit scopes and an optional expiry date, and can be revoked immediately.
SSRF protection
Every server-side fetch resolves DNS and checks the address before connecting.
A hostname check alone is not enough: an attacker controls their own DNS and can point a public name at a cloud metadata address. Each A and AAAA record is resolved and rejected if it lands in a private, loopback, link-local or CGNAT range, and the check runs again on every redirect hop.
Destination validation
Short links cannot be pointed at internal infrastructure.
Destinations are rejected when they use a non-HTTP protocol, embed credentials, target a private or loopback address — including decimal, octal, hexadecimal and IPv4-mapped IPv6 encodings — or use a port that is not a plausible public web service.
Destination screening
Blocklist, structural heuristics and optional Safe Browsing.
Destinations are checked against an operator blocklist, then against heuristics that look for brand names in unrelated paths and subdomains, credential-harvest vocabulary, punycode homographs and chained shorteners. Where an operator configures a key, Google Safe Browsing runs as a third layer. Heuristics mostly produce a warning interstitial; only the blocklist and Safe Browsing hard-block.
Rate limiting
Sliding-window limits on every sensitive endpoint.
Sign-in, registration, password reset, link-password entry, abuse reports, the contact form and the public API each have their own bucket, so pressure on one never consumes another’s allowance. Responses carry standard rate-limit headers, and the limiter fails open rather than locking customers out if the cache blips.
Audit logging
Every state change a customer could later ask about is recorded.
The log stores the actor, the action, the target and what changed. It is what answers “who changed this destination and when” — including for team accounts, where more than one person can edit the same link.
Role-based access control
Admin, Editor and Viewer roles, re-checked on every request.
Ownership is part of the database query rather than a comparison made after fetching a row, so a guessed id returns nothing rather than someone else’s data. Administrative role is refreshed from the database periodically instead of being trusted from a long-lived token.
No raw IP retention
The analytics table has no column for an IP address.
An address is used in memory to derive a daily-rotating visitor hash and to apply rate limits, then discarded. Nothing that is stored can be turned back into it.