Pulsobot security pack

Data flows: what leaves your account?

Security pack

Pulsobot runs inside your Databricks account. Your costs, workloads, job definitions, code, logs and users stay there. Pulsover receives only the Registration: an email, your Databricks account ID and the install's public key. No usage or workload data is sent. There is no telemetry in the product today; if it is ever added, it will be opt-in.

Summary

flowchart LR
  subgraph Customer["Your Databricks account"]
    I[pulsobot-installer]
    R[Rules job]
    A[API App]
    D[(Delta / Lakebase<br/>costs, workloads, orders)]
  end
  subgraph Pulsover["downloads.pulsobot.com"]
    REG["POST /api/register"]
    B["bundles/&lt;fingerprint&gt;/…"]
    L["latest.json"]
  end
  I -- "once at install:<br/>email, account ID, install public key" --> REG
  R -- "daily GET, no body:<br/>pointer, .age bundle, .age licence" --> B
  A -. "opt-in, off by default:<br/>plain GET" .-> L
  D --- R
  D --- A

Nothing in D is ever sent. The only body that leaves your account is the Registration.

Every outbound call to Pulsover

# Caller Method and URL When What is sent
1 Installer (your machine) POST https://downloads.pulsobot.com/api/register Once per install, right after the Install Key is created. Re-runs send it again The Registration payload below
2 Rules job GET https://downloads.pulsobot.com/bundles/<fingerprint>/latest.json Each rules run (daily, 00:00 UTC) Nothing: no body, no query string, no credential
3 Rules job GET …/bundles/<fingerprint>/<set_version>.age When the pointer names a new Rule Set version Nothing
4 Rules job GET …/bundles/<fingerprint>/licence.lic.age Each rules run Nothing
5 API App GET https://downloads.pulsobot.com/latest.json Only if you enable the update check (off by default): at start-up, then every 6 hours Nothing

<fingerprint> is the lowercase hex SHA-256 of the install's public key. It identifies the install directory on the store; it is not a secret and grants nothing. What the store sees for calls 2 to 5 is what any HTTPS server sees: source IP, time, path and Go's default User-Agent.

The downloaded files are encrypted to your Install Key (the .age files) and signed; the job decrypts and verifies them before use. A failure never stops a job: the install keeps its current rules and Licence.

Code: installer cmd/installer/cmd/registration.go; rules delivery adapters/repository-rules/delivery.go (paths in domain/installkey/layout.go); update check adapters/release-downloads/source.go, wired in web-databricks/wire.go.

The Registration payload

The installer prints this line before it sends anything:

Registering this install with Pulsover (downloads.pulsobot.com): email, Databricks account ID, install public key. Nothing else is sent.

The body is exactly internal/registration.Request, with three fields:

{"email":"ops@example.com","databricks_account_id":"0d26daa6-5e44-4c97-a497-ef015f91254a","install_recipient":"age1…"}
Field Content
email The address you give (--registration-email, PULSOBOT_REGISTRATION_EMAIL, or a prompt). A plain address, lowercased, at most 254 characters
databricks_account_id Your Databricks account ID (a UUID)
install_recipient The public half of the Install Key, an age X25519 recipient (age1…). The private half never leaves your secret scope

The request also carries an x-amz-content-sha256 header: the SHA-256 of that same body, which AWS requires to accept a POST. The service refuses unknown fields.

See it before it is sent: pulsobot-installer install --dry-run prints the exact body and sends nothing. Because a dry run creates no Install Key, it shows a placeholder for the recipient:

  [dry-run] Would POST to https://downloads.pulsobot.com/api/register exactly:
  {"email":"ops@example.com","databricks_account_id":"0d26daa6-…","install_recipient":"<install public key, age1…, created during the install>"}

In --non-interactive mode with no email, registration is skipped with a warning. A failed registration never fails the install.

What Pulsover keeps: one record per install (email, account ID, public key, tier free, first and last registration time, count). It is used to deliver your rules and for contact about the product.

Manual fallback: no egress at all

If the installer cannot reach downloads.pulsobot.com (no egress, proxy-only network), it prints a registration code and the install continues without rules:

PBR1.<base64url of the same JSON body>.<first 8 hex characters of its SHA-256>

The code holds the same three fields and nothing secret. From any machine with a browser:

  1. Open https://downloads.pulsobot.com/register, paste the code. The page sends the same request and downloads <set_version>.age.
  2. Copy that file to the install machine and run pulsobot-installer rules import <set_version>.age.

For later updates, the same page takes the install fingerprint and downloads the current bundle (and licence.lic.age if one exists), ready for rules import. The imported file is checked exactly like a downloaded one. To turn the runtime fetch off entirely, set PULSOBOT_RULES_DELIVERY_URL=off.

Network allow-list

Destination From Needed for
downloads.pulsobot.com:443 The installer machine Registration (optional: use the registration code otherwise)
downloads.pulsobot.com:443 Databricks serverless/job compute Rule and Licence refresh (optional: use rules import otherwise)
downloads.pulsobot.com:443 The API App Only if you enable the update check
github.com, api.github.com, objects.githubusercontent.com :443 The installer machine The installer downloads the Databricks CLI release from GitHub if it is missing
pricing.us-east-1.amazonaws.com:443 The cloud-price job Public AWS EC2 price list (AWS installs; no data sent)
Your Databricks workspace and account APIs Everything Normal operation

No inbound connection is ever opened. Pulsover has no access path into your workspace: the product holds no credential issued by Pulsover, and Pulsover holds none of yours.

Downloading the installer

Lead capture happens on the download page, not in the product: the page asks for a work email before it shows the links. That is a separate, browser-side step.