Skip to main content

Explanation

The read-only safety model

Elevarq Signals connects to your production databases to collect diagnostics. It is built so that connection cannot write, cannot escalate, and cannot leak its own credentials — and so that those properties hold even if one layer is misconfigured. This page explains why the safety model is built in layers, what each layer guarantees, and what that means for running Signals against production and for your compliance posture.

Signals reads statistics and settings; it never modifies your data or schema. That single property is the foundation of everything else: it is what makes Signals safe to point at a production cluster. But a stated intention is not a guarantee. The safety model exists to turn "Signals is read-only" from a promise into something the database itself enforces, the daemon verifies, and an auditor can check.

The dedicated signals role receives pg_monitor, passes runtime guards, reads PostgreSQL statistics only, and exports snapshots without credentials. Unsafe role attributes are blocked.
The safety model layers PostgreSQL privileges, runtime guards, and credential redaction.

Why three layers, not one

Any one of the following layers would, on its own, stop Signals from writing to your database in the normal case. They are stacked anyway — belt and suspenders — because the failure modes of each layer are different, and the cost of a write reaching a production database is high enough that one control is not enough. A misgranted role, a connection that loses its read-only posture, or a catalog query that was maliciously rewritten each defeats a different single layer; defeating all three at once is what the model is designed to make practically impossible.

The three layers, from the database outward to the daemon:

  • The role — least-privilege grants, so the database will refuse a write even if asked.
  • The connection — runtime enforcement on every session, so the transaction itself is read-only and bounded.
  • The daemon — validation before any collector query, so collection fails closed if the role is wrong.

Layer 1 — A least-privilege role

Signals connects as a single dedicated login role per target, granted nothing beyond the built-in pg_monitor role. pg_monitor aggregates exactly the read-only monitoring sub-roles Signals needs:

  • pg_read_all_settings — read all GUC values, including those marked superuser-only.
  • pg_read_all_stats — read all pg_stat_* views regardless of the owning role.
  • pg_stat_scan_tables — execute monitoring functions that may take ACCESS SHARE locks.

The role is created NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS. The grant is read-only: it confers no INSERT, UPDATE, DELETE, CREATE, schema change, replication, or row-security bypass. The guarantee this layer gives is that the database will refuse a mutating statement from this role on its own authority — even if a query in the collector catalog were somehow rewritten to attempt one, the engine denies it.

Withholding replication and BYPASSRLSis deliberate beyond just "not needed". A replication role can read WAL streams; bypassing row-level security widens what the role can see and weakens tenant isolation. Diagnostic collection needs neither, so neither is granted — the role's visibility is kept as narrow as the job allows.

For the step-by-step SQL to create and verify this role, see the how-to: Create the monitoring role.

Layer 2 — Runtime enforcement on every connection

A correctly scoped role still benefits from a connection that cannot misbehave. On every connection, before any collector query runs, Signals sets the session posture explicitly:

  • SET LOCAL default_transaction_read_only = on— every collection query executes inside a read-only transaction, so a write is rejected at the transaction layer independently of the role's grants.
  • Short, conservative timeouts applied with SET LOCAL statement_timeout, lock_timeout, and idle_in_transaction_session_timeout. These bound the impact of any single probe so monitoring can never hold a lock or a transaction open long enough to disrupt the workload it is observing.
  • application_name = signals on every connection — so its sessions are identifiable in pg_stat_activity, and so the query-statistics collector can filter out Signals' own probe traffic rather than pollute your workload analysis.

Using SET LOCAL rather than a session-level SET is what makes this robust under pooling: the settings are scoped to the exact transaction that runs the query, so read-only posture and timeouts cannot leak into — or be leaked from — another pooled connection or a concurrent operation. The guarantee this layer adds is that the transaction doing the reading is itself read-only and time-bounded, regardless of how the role was granted.

Layer 3 — Role validation before any query

The first two layers assume the role was created correctly. The third layer stops assuming and checks. Before each collection cycle, the daemon queries the catalog for the connected role's attributes and refuses to proceed unless the role is safe. It verifies that the role is:

  • NOSUPERUSER — a superuser has unrestricted access and defeats every other layer.
  • non-replication — it cannot read WAL streams.
  • non-BYPASSRLS — it cannot bypass row-level security.
  • not a member of pg_write_all_data — it has no transitive write access.

This is a fail-closed check: if any of these conditions is not met, collection is blocked with an actionable error rather than proceeding on a hopeful assumption. The guarantee is the strongest of the three — Signals will not run a single collector query against a role it has not confirmed to be least-privilege. A role that was accidentally granted superuser, or quietly added to pg_write_all_data after setup, is caught here before any data is read, not discovered later in an audit.

The validation runs on every collection cycle, not just at startup, so a privilege change made to the role while Signals is running is caught on the next cycle.

Credentials never touch disk or exports

The safety model also governs the one secret Signals holds: the credential it connects with. By design that credential is never logged, never cached in memory beyond a single connection attempt, never written to the local store, and never included in snapshot exports or API responses. Error messages that might contain credential material are redacted before they are emitted.

This matters because the most sensitive material Signals handles is the one piece that never reaches disk. The local store holds collected diagnostic data and snapshot state only; the connection credential is not part of it. The practical consequence is that an exported snapshot, a leaked log line, or a /status API response cannot expose how Signals authenticates — the credential exists only in flight, only for as long as a connection attempt needs it.

What this means for compliance posture

Together these layers let you treat Signals as safe to run against production: it cannot write, cannot escalate, and cannot exfiltrate its own credentials, and those properties are enforced by the database, verified by the daemon, and visible to an auditor rather than asserted in documentation.

The one classification decision an operator still owns concerns what Signals collects, not what it can do. The high-sensitivity collector pack captures SQL definition text and live query text. That content is never user data, but it can carry application-authored SQL logic and internal naming, so it should be treated as data classification "Internal" or higher. The effective on/off state of that pack is recorded in the export metadata, so an auditor can tell at a glance whether a given snapshot may contain such material — keeping the documentation-to-reality link the safety model depends on intact.

Where to go next