URL shorteners sit at an unusual position in the attack surface map. They are, by design, opaque redirectors: a recipient who receives a short link cannot tell where it goes before clicking. That opacity is the product's core value proposition. It is also what makes a poorly-secured shortener a useful tool for abuse.
This post covers the realistic threat model for URL shortener platforms, then works through a concrete security checklist — ten controls that a serious provider should be able to demonstrate in 2026. Where Elido implements a control, I will say exactly how. Where it doesn't yet, I will say that too.
The threat model#
Four categories of abuse come up repeatedly in incident reports and security audits of link infrastructure:
Phishing and malware distribution. A shortened link is structurally identical whether it points to a legitimate landing page or a credential-harvesting form. Threat actors create accounts, shorten malicious URLs, and distribute them before automated abuse detection catches up. The asymmetry is significant: creating a hundred short links costs seconds; cleaning them up after they've been distributed costs weeks of investigation.
Leaked API keys. API keys that appear in client-side JavaScript, public GitHub repositories, or build logs represent a broad access path. If an API key is stored in plaintext in the provider's database, a single database breach exposes every key from every customer. Unlike passwords, API keys are rarely rotated — once compromised, they stay compromised until someone notices unusual API activity.
Bot-driven analytics inflation. Click counts in a URL shortener's dashboard are a proxy metric for campaign performance. If those counts include every uptime monitor, link preview bot, crawlers, and scripted request without filtering, the signal is noise. Beyond annoying dashboard numbers, inflated click counts can affect billing in volume-based pricing models, and fraudulent click volume can be used to manipulate attribution systems.
Mass redirect abuse. A single workspace with unrestricted API access can create tens of thousands of short links per minute and point them at phishing infrastructure, DDoS amplification endpoints, or content delivery systems for malware. Without per-workspace rate limiting, one compromised account can impose availability costs on every tenant on the platform.
The security checklist#
1. URL scanning before activation#
When a user submits a destination URL, the platform should check it against threat intelligence feeds before the link goes live. Checking only at creation time misses URLs that are clean on day one but later added to blocklists; the correct architecture runs an async background scan on a schedule as well.
Elido's url-scanner service runs four independent sources in parallel against every submitted URL: Google Safe Browsing v4 (checking MALWARE, SOCIAL_ENGINEERING, UNWANTED_SOFTWARE, POTENTIALLY_HARMFUL_APPLICATION), PhishTank, SURBL, and a structural heuristic that examines URL properties without external calls. Each source returns a 0–100 risk score; the composite result uses the maximum score across all sources — so a confident hit on any single feed is sufficient to block the link. Links scoring 80 or above are blocked immediately; links scoring 40–79 are quarantined and queued for a deeper async scan. Sources run under a 200ms wall-clock budget; a slow external API times out and is logged as degraded rather than blocking the creation flow.
Ask your current provider: which specific feeds are checked, what happens when a destination URL is added to a feed after the link was created, and whether there is an async re-scan job.
2. HMAC-signed webhooks with timestamp-bound replay protection#
Webhooks are a server-to-server notification mechanism. A provider who sends unsigned HTTP POST requests to your endpoint is giving you no way to verify the request came from them rather than an attacker who discovered your webhook URL. The standard solution is to sign every payload with an HMAC-SHA256 over the concatenation of the Unix timestamp and the raw payload body.
The timestamp component matters as much as the signature. Without it, an attacker who intercepts a valid signed payload can replay it indefinitely. With it, your receiver can enforce a tolerance window — typically 5 minutes — and reject any payload where now - timestamp exceeds that window.
Elido's webhook signing is v1=HMAC-SHA256(secret, "${unix_timestamp}.${body}"), delivered in the X-Webhook-Signature header alongside X-Webhook-Timestamp. The signature format is the same convention used by Stripe (v1=hex) so any existing Stripe webhook verification code adapts with minimal changes. Receivers are expected to reject payloads older than their configured tolerance window.
Ask your current provider: what algorithm, what header name, and whether the timestamp is bound into the signed message or sent separately (the latter allows timestamp substitution attacks).
3. Per-workspace rate limiting with tier-aware caps#
IP-level rate limiting alone is insufficient for API-based abuse. A determined attacker uses rotating IPs; the binding constraint should be the workspace itself. Per-workspace token buckets ensure that even a legitimate user who runs a runaway automation script against their own workspace doesn't generate unbounded API load.
Tier-aware caps make the constraint accurate rather than arbitrary. A free workspace with ten links and minimal traffic needs a lower burst allowance than a business customer running automated link creation for a campaign pipeline. A flat rate applied uniformly either throttles paying customers or leaves free tier accounts under-constrained.
Elido's ratelimit.TieredLimiter maintains one token bucket per workspace, sized by the workspace's billing tier (free, paid, business). The tier is resolved through a TTL-cached lookup — resolver failures degrade to the free tier to prevent database incidents from blocking paying customers. Buckets are per-workspace, independent of per-IP limits, so both fire when applicable. The TieredMiddleware is mounted on the /v1/workspaces/{workspace_id}/** route group and returns 429 Too Many Requests with X-RateLimit-Scope: workspace on breach.
4. API keys hashed with a pepper, not stored in plaintext#
The question is not whether to hash API keys — the question is which algorithm and whether a server-side secret (pepper) is mixed in.
Plaintext storage is indefensible. A database backup, a misconfigured query result, or a breach of read-only replica access exposes every key from every customer. bcrypt is better but carries a known limitation: it truncates inputs at 72 bytes, which affects long random tokens. The current recommendation for new systems is Argon2id.
The pepper adds a second factor: even if the database is fully compromised, keys cannot be cracked offline without also compromising the application server's secret. The hashed key in the database is useless without the pepper on the server.
Elido's API key storage uses HMAC-SHA256 keyed on a server-side pepper (handler.HashToken). The plaintext token is returned exactly once at creation time and immediately discarded from application memory. Subsequent API calls hash the inbound Bearer token and look up the result by hash — the plaintext is never stored or logged. The password package (used for link password protection, not API keys) uses Argon2id with a 16-byte random salt, 64 MiB memory, 2 iterations, and 4 threads — PHC-encoded so parameters are stored with the hash and can be updated per-hash during a migration.
Ask your current provider: can they confirm hashed-at-rest and confirm the algorithm? If the answer is "we hash passwords but keys might be different," that is worth pressing on.
5. Per-link password protection#
Not every short link is intended for the general public. Internal links distributed inside a company, early-access landing pages, and staged content all benefit from an additional layer that requires the recipient to prove they should have access.
Link passwords are hashed before storage — the platform should never be able to recover the plaintext. The verification flow at redirect time checks the submitted password against the stored hash without any database query that returns the hash to the application layer unguarded.
Elido hashes link passwords with the same Argon2id implementation used for user credentials. The API response for a link never returns password_hash; instead it returns a boolean password_set field. A recipient hitting a password-protected link receives a 401 with password_required: true and a challenge token; they must submit the correct password in a follow-up request. The hash is verified in-process with subtle.ConstantTimeCompare to prevent timing-based enumeration.
6. Link expiration and max-clicks cap#
Permanent short links are an operational liability. A link created for a campaign that ended two years ago can still be targeted, still be distributed in phishing messages, and still appears in click analytics. Expiration controls let workspace owners set a bounded lifetime on a link at creation time.
Max-clicks caps add a different constraint: the link deactivates after a set number of successful redirects. This is useful for single-use download links, limited-access previews, and any case where the expected audience size is known in advance.
Both controls are in Elido's link model. The expires_at field deactivates a link at a timestamp; the max_clicks field deactivates it after N successful redirects. Both are enforced at the redirect layer before click events are recorded. Neither is a substitute for manual link management — but both reduce the blast radius of a link that gets distributed beyond its intended audience.
7. Audit log surfaced to admins#
Knowing that an API key was used is not the same as knowing which endpoints were called, what was created or modified, and when. An audit log that records every mutating action — link creation, deletion, settings changes, member invitations, API key issuance and revocation — gives workspace admins the evidence they need to investigate suspicious activity after the fact.
The audit log should be searchable, filterable by actor and action type, and exportable. For enterprise customers with SIEM integration, audit events should be streamable to external systems in near-real time.
Elido's audit emitter fires on every successful mutating handler call. Events are written to Postgres with (workspace_id, actor_user_id, kind, target_type, target_id, metadata, ip_address, user_agent, timestamp). Workspace admins access the last 90 days via GET /v1/workspaces/{id}/audit-events with filtering by kind and date range. Audit events are also fanned out to the webhook bus as audit.event envelopes so SIEM-kind webhook endpoints receive the full audit stream automatically. The evidence export endpoint (/v1/workspaces/{id}/evidence) bundles 90 days of audit events as a CSV alongside member and IP-rule exports for compliance packaging.
8. IP allow/deny lists at the workspace level#
For API-first customers where all traffic originates from known infrastructure, an IP allowlist is a straightforward second factor: if a request arrives with a valid API key but from an IP not in the allowlist, reject it. This limits the blast radius of a leaked key to an attacker who also has access to the allowed IP range — a significantly higher bar.
Elido's IPAllowlistChecker is a TTL-cached per-workspace middleware. Prefixes are stored as CIDR ranges; the check is a prefix containment test against the parsed client IP (normalized through X-Forwarded-For after Caddy sets it). When the allowlist is enabled and non-empty, any request from an IP not in the configured prefixes receives a 403 Forbidden regardless of credential validity.
9. Bot filtering on the edge#
Every uptime monitor, search engine crawler, social preview generator, and link-unfurling client that follows a short link should not appear in your click analytics. Beyond the data quality problem, unfiltered bot traffic can trigger rate limits, inflate per-click billing, and obscure the human traffic signal in attribution pipelines.
Elido's edge-redirect service runs a User-Agent based bot detector on every request before emitting a click event. The detector checks a list of around 50 lowercase substrings covering search-engine crawlers (Googlebot, Bingbot, Yandexbot, Baiduspider), social preview bots (Twitterbot, LinkedInbot, Discordbot, Slackbot, Telegrambot, WhatsApp, Facebot), uptime monitors (UptimeRobot, Pingdom, StatusCake), HTTP clients (curl, wget, python-requests, axios, PostmanRuntime), and empty User-Agents (which are flagged as bot unconditionally — real browsers always send one). Bot-matched requests still receive the redirect; only the click event emission is suppressed. A Prometheus counter tracks how many click events were filtered per process restart.
The suspicion scorer (internal/suspicion) runs separately and flags clicks as suspicious without blocking them: missing User-Agent, missing Accept-Language header, and per-IP rate window triggers each contribute a soft signal. Two or more soft signals mark the click as suspicious in ClickHouse, where analytics queries can filter it.
10. SSO / SAML / SCIM for enterprises#
For organizations that manage access through an identity provider, requiring employees to maintain a separate password for the URL shortener creates a credential hygiene problem. SSO eliminates that problem: the shortener validates the session against the IdP and never handles the password itself. SCIM automates the provisioning and deprovisioning lifecycle, so when an employee leaves the company, their access is revoked without a manual ticket.
Elido's SSO is built on WorkOS. The SsoHandler exposes admin-only configuration CRUD, a public domain-discovery endpoint (GET /v1/sso/discover?domain=) for the login flow, and a connection-lookup endpoint for the OAuth callback. WorkOS handles the SAML/OIDC protocol details and SCIM provisioning; Elido maps the resulting profile to a workspace member through the Kratos identity layer.
What to ask your current provider#
If you are evaluating whether to stay with or migrate away from your current shortener, these are the questions that separate documented security posture from marketing claims:
- Where are API keys stored — plaintext, bcrypt, or a pepper-keyed hash? Ask for the algorithm, not the reassurance.
- How are outbound webhooks signed, and does the signature bind a timestamp to prevent replay?
- What threat intelligence feeds does URL scanning use, and is there an async re-scan job for URLs that go stale?
- What are the per-workspace API rate limits, and are they differentiated by billing tier?
- Is there a workspace-level audit log accessible without a support ticket, and is it exportable?
- Are per-link password hashes stored with Argon2id or bcrypt, not MD5 or SHA-256?
- What is the bot detection mechanism on the redirect path, and how many distinct bot signatures are in the list?
- Does the platform support IP allowlists at the workspace level, not just at the account level?
If a provider cannot answer questions 1, 2, 3, and 5 in writing within a business day, the security documentation does not exist yet or is inaccessible to you.
Where Elido is still hardening#
Honest security communication means acknowledging what isn't done.
Elido's rate limiting is currently process-local token buckets (in-process Go rate.Limiter). This works correctly for single-replica deployments and provides independent per-IP and per-workspace enforcement. In a multi-replica horizontal scale-out, each replica maintains its own bucket state — meaning a workspace can exceed its nominal limit by up to N times across N replicas before any single limiter fires. The solution is a Redis-backed distributed limiter, which is queued but not yet shipped. The existing limiter's own package comment documents this explicitly.
There is no built-in WAF beyond rate limits and bot filtering. DDoS amplification through mass redirect traffic is partially mitigated by rate limits and Caddy's upstream connection handling, but there is no application-layer firewall that inspects redirect payloads for injection patterns or applies geo-blocking. Enterprise customers with high-volume public-facing links should plan for an external WAF in front of the edge layer.
The url-scanner re-scan job runs on a schedule for recently-created links but does not yet maintain a retroactive scan queue across the full link corpus. A link created six months ago and never rescanned could point to infrastructure that has since been repurposed for abuse. Full-corpus async rescanning is on the roadmap.
API key rotation is manual — there is no automated expiry policy that forces rotation on a schedule, only an optional expires_at field set at creation time. Organizations with key-rotation requirements should set this field and build a rotation workflow into their automation.
The GDPR-focused checklist covers the data residency, IP truncation, and right-to-erasure requirements that sit alongside these security controls. For pricing tier details and which controls are available on each plan, see the pricing page. Elido's privacy policy covers how click-event data is handled across the full redirect chain.