1. Documentation
  2. Resources
  3. Security
ReadmenpmGitHub
  • Introduction
  • Quickstart
  • Configuration file
  • Monitors
  • Checks and scheduling
  • Notifications
  • Incidents and maintenance
  • Subscribers
  • Metrics and badges
  • Identity and appearance
  • Telemetry
  • The check run
  • Uptime and history
  • Automatic incidents
  • Notification delivery
  • Public subscriptions
  • StatusPage component
  • Themes
  • UI primitives
  • Colour mode
  • React Router
  • Next.js
  • Astro
  • SQLite
  • Cloudflare D1
  • PostgreSQL
  • Schema and migrations
  • Cloudflare Workers
  • Vercel
  • Self-hosting
  • API overview
  • Status endpoints
  • Incident and maintenance endpoints
  • Subscriber endpoints
  • Badges, feeds and metrics
  • Admin endpoints
  • CLI commands
  • Package exports
  • Security
  • Troubleshooting

Security

The defaults, the guarantees, and the checklist before you publish.

A status page is public infrastructure that holds operational detail and, if you enable subscriptions, visitor addresses. These are the defaults Heyo Status ships with and the decisions left to you.

Defaults

AreaDefault
Management APIDisabled until a token is configured — 503, never open.
Vercel cron routesDisabled until CRON_SECRET is set — 503.
Public subscriptionsDisabled.
Visitor webhooksOff even when subscriptions are enabled; needs an explicit opt-in.
Email subscriptionsDouble opt-in.
PrometheusDisabled.
Automatic migrationsNever run from a request handler.

Secrets

SecretProtectsRotate by
HEYO_STATUS_API_TOKEN/admin/*updating the platform secret
CRON_SECRETVercel cron routesupdating both sides
Webhook secretSignature of outbound webhook deliveriesrotating with the receiver
Database credentialsAll stored historyyour provider's flow
bash
openssl rand -hex 32

Both bearer comparisons are constant time, so a token cannot be discovered byte by byte.

What the API exposes

Public, by design:

  • Monitor names, groups, descriptions and current state
  • Uptime history, latency and the day grid
  • Incidents, their timelines and severities
  • Maintenance windows

Never exposed publicly:

  • Monitor targets — URLs, hosts and ports — appear in notifications, not in the API or the page
  • Request headers and credentials from HTTP monitors
  • Subscriber addresses and webhook URLs
  • Confirmation tokens
  • Anything from a private: true monitor
Monitor names and descriptions are public

Everything you type into name, description and group appears on the page. Use private: true for internal services you still want to monitor but not advertise.

Public subscriptions

Enabling subscribers creates an unauthenticated write endpoint. The protections built in:

  • Destination-based deduplication, so one address cannot be signed up twice
  • Double opt-in by default, with single-use 32-byte random tokens
  • Responses that never reveal whether a destination is already subscribed
  • no-store and no-referrer on the confirmation response
  • Unsubscribe as a POST behind a confirm page, so a mail scanner's prefetch cannot unsubscribe anyone
  • Webhook URL validation against loopback, private, link-local, metadata, CGNAT, multicast and reserved-suffix destinations, and against credentials in the URL

What remains yours:

  • Rate limiting. There is no built-in limiter. Put one in front of POST /subscribers at the edge.
  • Egress policy. URL validation cannot see through DNS or stop rebinding. If visitor webhooks are enabled, restrict outbound network access.
  • Email reputation. Confirmation mail goes out through your transport, under your domain.

Deployment checklist

  1. 1

    Set the admin token

    HEYO_STATUS_API_TOKEN as a platform secret, never in the repository. Verify: an unauthenticated POST /admin/incidents must answer 401, and a configured deployment must never answer 503.

  2. 2

    Set siteUrl

    Without it, feed links, notification buttons and confirmation URLs are built from the request origin, which a proxy can rewrite.

  3. 3

    Review monitor visibility

    Anything internal gets private: true. Check that no description leaks a hostname or an internal service name.

  4. 4

    Decide on subscriptions

    Leave them off unless you want them. If you enable webhooks, add the egress policy in the same change.

  5. 5

    Decide on Prometheus

    /metrics is public when enabled. Restrict the path at the edge if it should not be world-readable.

  6. 6

    Protect the cron routes

    On Vercel, CRON_SECRET must be set — cleanup deletes history and checks sends outbound requests to every monitor.

  7. 7

    Migrate deliberately

    Run heyo-status db migrate as a release step, not from a request path.

Monitor credentials

HTTP monitor headers are sent verbatim on every check. Keep them in environment variables, scope the token to a health endpoint, and prefer an unauthenticated health route when one exists.

ts
{  name: "API",  type: "http",  url: "https://api.acme.com/health",  headers: { authorization: `Bearer ${process.env.HEALTH_TOKEN}` },}

Credentials never reach the database: results store a name, a type, a status, a latency and a message.

Webhook receivers

Verify the signature before trusting a payload:

ts
import { verifyWebhookSignature } from "@heyo-sh/heyo-status/notifications";const body = await request.text();if (!(await verifyWebhookSignature(secret, request.headers.get("x-heyo-signature") ?? "", body))) {  return new Response("Unauthorized", { status: 401 });}

The signature covers ${timestamp}.${body}; reject old timestamps to prevent replay.

Reporting a vulnerability

Report privately through the repository's security policy rather than a public issue: github.com/heyo-sh/heyo-status.

Related

  • Public subscriptions
  • Admin endpoints
  • Telemetry
Package exports< PreviousTroubleshootingNext >

Powered by heyo

On this page

DefaultsSecretsWhat the API exposesPublic subscriptionsDeployment checklistMonitor credentialsWebhook receiversReporting a vulnerability