Skip to content
This project is part of pgEdge Labs and is under active development. APIs and features may change without notice.

Telemetry and privacy

Every network contact the binary makes, when it makes it, and every file it leaves on your disk is listed here. The list is an engineering description of the code. It is not a commitment about retention or third parties.

The CLI collects no usage data

The CLI imports no analytics, telemetry, crash-reporting or usage-tracking library. Nothing records which commands you run, which flags you pass, how long they took, or whether they failed, and nothing transmits any of that anywhere.

OpenTelemetry and Prometheus packages do appear in the Go module graph. They are there for different reasons and neither reports anything.

Prometheus reaches the module graph through a dependency of a dependency, and no package of it is compiled into the binary at all.

OpenTelemetry is compiled in: twenty of its packages, reached through self update's signature verification, where Sigstore's transparency-log client is built on an OpenAPI runtime that carries tracing hooks. The generated pgEdge API clients are not the route and contribute none of it. What makes it inert is that nothing configures an exporter, so the global tracer resolves to OpenTelemetry's own no-op implementation and every span it creates is discarded before it reaches anything. Nothing in the CLI's own source imports either library.

What the binary contacts

Six hosts across five rows, and only one of them is a pgEdge host by default. Every one of the GitHub and Sigstore calls happens on a command you ran deliberately.

The complete outbound set:

Host Reached by What for
The Starfleet API base URL, https://api.pgedge.com unless a profile or --api-url says otherwise every starfleet, byoc and managed command The API call the command exists to make.
The Control Plane base URL you configured every controlplane command The API call the command exists to make.
http://localhost:3000 every controlplane command, when no profile and no --base-url supplies one The same call, against the address the Control Plane serves on by default.
api.github.com and github.com pgedge self update and pgedge doctor (not under --no-version-check) Listing releases, and downloading an archive when an update proceeds.
tuf-repo-cdn.sigstore.dev pgedge self update performing a real update Refreshing the Sigstore public-good trust root that the signature is checked against.

Neither Control Plane row is a pgEdge-owned address. That module has no login and no hosted service, so it reaches either a host you named or, when you have named none, http://localhost:3000, which is where the Control Plane serves by default. That fallback is the one address in the table you can reach without having configured anything, and it is plain HTTP and loopback, so nothing leaves the machine.

One host that turns up in a strings dump of the binary is never contacted at all. cli.github.com appears inside a single error message, the one telling you where to install gh when the CLI cannot find it, and no code path fetches that URL.

Nothing runs in the background

The CLI has no startup version check, no periodic check and no deferred reporting. Contact with GitHub happens on exactly two commands, and contact with Sigstore on one:

  • pgedge self update lists releases, and on a real update also downloads the archive and refreshes the Sigstore trust root. --check returns after the release listing, before any download or verification, so it reaches GitHub and not Sigstore.
  • pgedge doctor lists releases to fill in its latest-version row, under a five-second bound, and downloads nothing. Pass --no-version-check to skip that lookup.

Every other command in every module talks only to the API base URL it was configured with. The one thing that does run before the command tree is even built is a Windows-only cleanup that deletes a stale backup file left behind by a previous self-update swap, and it touches no network.

The GitHub lookup itself has two rungs. The CLI first tries the public GitHub API unauthenticated, and falls back to shelling out to the gh CLI only when that fails. On the second rung the request is made by gh under your own GitHub credentials rather than by the CLI.

What is written to local disk

The CLI's own files live under ~/.pgedge/cli, and it creates that directory at mode 0700. The link and env pull commands also write into a project folder. The paths, and what puts them there:

Path Written by
~/.pgedge/cli/config.yaml starfleet auth login, controlplane config set and the other config writers.
~/.pgedge/cli/cache/<profile>-<module>.json A successful token exchange, one file per profile and module.
~/.pgedge/cli/cache/sigstore/ pgedge self update, caching the Sigstore trust root.
Your shell's completion directory, or one line in your shell startup file pgedge completion install, and the regeneration that follows a successful self update.
The directory the running binary sits in pgedge self update, staging the new binary beside the old one before swapping.
.pgedge/link.yaml in a project folder starfleet managed database link and database create --link. It holds IDs, never a credential.
.env in a project folder, or the file --file names pgedge env pull, starfleet managed database env pull, and starfleet managed database link run with no ID when you accept its offer to write .env. It holds a live database password.

starfleet auth login also writes one OS keychain entry per profile and config file, under the service name pgedge-cli, unless it stores the secret in the config file instead. starfleet auth logout removes it.

Diagnostics never reach a file. --verbose and --debug write to stderr and the CLI opens no log file anywhere.

The client secret lives in the OS keychain

pgedge starfleet auth login stores the client secret in the OS keychain. config.yaml holds the client_id, the API URL, the Control Plane certificate paths and your output preference. A program running as your user can read your keychain entries, so the keychain keeps the secret out of backups and copied files, not away from software on your machine.

config.yaml stores client_secret as a plain YAML string in three cases: login could not use a keychain, login ran with --insecure-storage, or an earlier version wrote it there. The CLI writes the file at mode 0600 and re-applies that mode on every save, so a file that had been loosened is tightened again the next time a command writes it. The write is atomic in the same way as the cache write below: staged beside the file, then renamed over it, so an interrupted save leaves the previous config intact rather than a truncated one. One consequence is that a config directory you cannot write to refuses the save, even when the file itself is writable. A staging file left by a process killed mid-save carries the same secrets at the same 0600 mode and is named .config.yaml.tmp followed by digits.

The token cache holds a live token in clear text

Each cache file carries three fields and nothing else: access_token, expires_at, and binding_fingerprint. The access token is stored unencrypted, in a file written at mode 0600 inside a directory at 0700. The keychain holds the client secret, never the token.

binding_fingerprint is the reason your client secret is not in that file. It is a SHA-256 digest over a domain-separation prefix followed by the resolved API base URL, the client ID and the client secret, each preceded by a NUL byte, rendered as hex behind a sha256: prefix. The digest is one-way and is never printed. The only thing the CLI does with it is compare it against a fingerprint recomputed from the connection it is about to dial, so a cached token cannot be replayed against a host that did not mint it. A cache file with no fingerprint counts as a mismatch and costs one silent re-exchange rather than trusting an unbound token.

The cache write is atomic. The CLI creates a temporary file in the same directory, sets 0600 on it, writes, then renames it over the target, and starfleet auth logout sweeps any staging file an interrupted write left behind. There is no fsync, so a power loss can leave an unreadable cache file, whose only consequence is that the next command re-authenticates.

Secrets in diagnostic output

--verbose and --debug print HTTP traffic, and both redact rather than echo. A password embedded in a URL's userinfo is masked before the request line is printed, and at --verbose an Authorization header is printed as a mask rather than its value. At --debug, where request and response bodies are dumped, a JSON body carrying any key the CLI classifies as credential-bearing has that value masked, and a body it cannot parse well enough to mask precisely is replaced with a summary of its top-level keys rather than printed.

That classification covers password, so a database connection block dumped under --debug does not print the database password. The list also covers access_token, client_secret, the cloud-provider keys and the service-config secrets.

Avoiding the GitHub and Sigstore calls

The Sigstore trust-root call belongs to self update alone, so an install that never runs that command never makes it. Building from a checkout with make build, or installing with go install, both leave you with a binary you update through the tool that put it there, and the updating guide covers which installs self update refuses outright.

pgedge doctor reaches api.github.com for its latest-version row unless you pass --no-version-check, which skips that lookup and leaves every other check in place. With the flag, doctor contacts nothing off the machine. Avoiding GitHub entirely then means avoiding self update, not doctor.

Next steps

  • The configuration guide covers what the config file holds and the short list of environment variables the CLI reads.
  • The authentication guide covers how credentials resolve and what the token cache binding protects against.
  • The updating guide covers the release lookup, the signature verification and the installs self update refuses.