# Security model

> What the agent enforces, what Blueprintr stores and for how long, where credentials are kept, and how releases are signed.

Continuum Local runs connector requests and SNMP sweeps inside your network for
Blueprintr. Blueprintr decides what to ask for. The agent decides what it will
do, by a policy that only its configuration file sets, and treats every job from
Blueprintr as untrusted input.

## Trust boundaries

| Party | Runs where | Decides |
| --- | --- | --- |
| Your configuration file | The agent's host | Which systems the agent serves, the hosts each may reach, the address ranges it may sweep, and the credentials it uses |
| The agent | A host, container or pod you run | Whether each job fits that policy, before a socket opens |
| Blueprintr | Blueprintr's servers | Which requests to propose, and what to do with the answers |

The agent dials out and listens on no port. Every exchange starts on your side:
it asks Blueprintr for work, runs it locally and posts the result back. The deb,
rpm and MSI installs let the agent's own account read `config.json` but not
change it, so a compromised agent process cannot rewrite its own configuration.

## What Blueprintr stores

| Data | What it contains | Why | Who can see it | How long |
| --- | --- | --- | --- | --- |
| Agent record | The name you gave it, its version, platform, protocol version and capabilities, the integration IDs it serves, its discovery ranges and when it was last seen. A keyed hash of its secret, never the secret | To send each job to an agent that can run it, and to show its status | People who can manage Continuum integrations on the organisation or team that owns it. Blueprintr staff who run the service see its name, owner, status, version, platform and capabilities | Until you delete the agent. Revoking keeps the record |
| Enrolment token | A hash of the token | To enrol the agent once | Nobody. The token is shown once, when it is created | Deleted 30 days after it is used or expires |
| Relay job | The request Blueprintr asked the agent to make, with placeholders where credentials go, and the answer the agent returned | To pass the answer to the connector that asked for it | Blueprintr's servers only. Staff see counts of outcomes, not contents | The job expires two minutes after it is created, and is deleted at the agent's next poll after that or by Blueprintr's scheduled clean-up |
| Saved panel | The fields the connector takes from that answer, saved as the tab's snapshot | To show it on the stratum | Everyone who can read the blueprint, including anonymous readers, embeds and exports on a public blueprint. NetBox panels are for Blueprint editors unless you choose all Blueprint readers, and Infoblox and Veeam Backup & Replication panels are for Blueprint editors only. The [connector reference](/foliums/blueprintr-user-guide/continuum/connector-reference) lists what each panel shows | Until an editor refreshes or removes the tab. Unlinking cannot recall copies already in versions, exports or embeds |
| Sweep results | The devices, ports and links each [network sweep](/foliums/blueprintr-user-guide/continuum/local-agent/network-discovery) finds, and the MAC and IP addresses those devices have learned. Never a device's contact field, or which SNMP credential or version answered | To show your estate on the sweep results page | People who can manage Continuum integrations on the owning organisation or team. Staff see counts only | 90 days from when a sweep starts. The latest complete sweep from each agent is kept until a newer one completes. Deleting an agent deletes its sweeps |
| Audit log entries | Agent created, enrolled, renamed, revoked and deleted, tokens issued, sweeps and schedules, and credentials sent or removed: who, with a hash of their IP address and their browser's user agent, which agent, which integration, and the outcome. Never a credential | Accountability | People who can read the organisation's audit log, and the team's for a team's agent | With the rest of the audit log, which does not expire automatically |
| Download record | Which file and version you downloaded from the download page, and when, with a hash of your IP address and your browser's user agent | To answer who downloaded which build, for example after an incident | Blueprintr staff who run the service. It is recorded against your account, not the organisation, so it is not in the organisation's audit log | With the rest of the audit log, which does not expire automatically |

Deleted rows stay in Blueprintr's database backups for up to 7 days.

## Where credentials are kept

Blueprintr never stores a credential for an on-premise system.

| Credential | Where it is kept |
| --- | --- |
| Monitoring system credentials you configure | `config.json`, or an environment variable or file it refers to (`env:NAME`, `file:/path`), or a `credentialFile` your secret manager writes |
| SNMP communities and SNMPv3 passphrases | The `discovery` block of `config.json`, or the variables and files it refers to |
| Credentials sent from Blueprintr | The agent's state directory, one file per integration, mode 0600 |
| The agent's own identity for Blueprintr | The agent's state directory, mode 0600 |
| Access tokens minted by WhatsUp Gold and Veeam Backup & Replication | The agent's memory only |

The agent does not encrypt these files itself. They are protected by file
permissions, so use disk encryption on the host if your policy requires
encryption at rest.

For a credential in the agent's configuration, Blueprintr never receives the
value. Its request has an unguessable placeholder where the credential goes.
The agent fills in the local value and refuses to send the request if any
placeholder is left in it. When an answer repeats a credential the agent sent,
or a token it kept, the agent replaces it with `[redacted]` before the answer
leaves, in plain, JSON, percent-encoded and form-encoded spellings. Values
shorter than six characters are not scrubbed.

### Credentials sent from Blueprintr

This is off unless the agent's configuration sets
`"allowRemoteCredentials": true`. When it is on, a credential typed into
**send credentials** takes this path:

1. From your browser to Blueprintr's web application, over HTTPS.
2. From the web application to Blueprintr's agent gateway, as an encrypted,
   single-use message.
3. From the gateway to the agent, over the agent's open connection to
   `agents.blueprintr.io`.
4. The agent checks it against its own limits and writes it to its state
   directory.

Nothing on that path writes the credential to a database, a queue or a log.
There is nowhere to keep it in the meantime, so the agent has to be online and
connected to the gateway at the time, or the send fails and nothing is queued.
Where Blueprintr does not offer the gateway, every send is refused as not set
up.

The agent applies limits that only its configuration can set:

- `remoteCredentialScope` lists the hosts, domain suffixes and address ranges
  Blueprintr may configure. Addresses are checked again at every connection,
  against the address the name resolves to at that moment.
- Loopback, link-local (including `169.254.169.254`), unspecified addresses and
  `localhost` names are refused unless a `cidrs` entry in that scope covers them.
- Certificate settings sent from Blueprintr are refused unless the scope sets
  `allowTlsOverrides`. `allowWriteOperations` is never accepted from Blueprintr.
- An integration in `config.json` always wins. Blueprintr can neither replace
  nor remove it.
- Credentials sent to an agent are deleted if it is enrolled again as a
  different agent.

## What the agent enforces

Every job is checked before a socket opens:

- A relay or probe must be for an integration in the agent's configuration, or
  one sent from Blueprintr, and a relay's provider must match that entry's.
- A relay's host must be in that integration's `allowedHosts`, written exactly,
  with no wildcards, ports or paths. The check runs again after the credential
  is filled in, and on each of up to three redirects. A redirect to another host
  drops credential headers, and a request with a credential in its URL or body
  is not followed to another host.
- An answer larger than the request allows (8 MB at most) is refused. So is a
  result larger than one poll to Blueprintr can carry, which is about 1 MB of
  answer. Both are reported as `response_too_large`. A relay runs for 10
  minutes at most.
- A sweep reaches only addresses inside `discovery.cidrs` and outside
  `exclude`. An SNMP probe of one device reaches only an address a sweep may
  reach or a host in the integration's `allowedHosts`, and a TCP connection
  test only a host in `allowedHosts`. An agent with no `discovery` block sends
  no SNMP at all.
- Whatever `allowWriteOperations` says, a request that sets `Host`,
  `Transfer-Encoding`, `Connection`, `Keep-Alive`, `Upgrade`, `TE`, `Trailer`,
  `Expect`, `Proxy-Connection` or `HTTP2-Settings`, or sets one header twice in
  different letter case, is refused. The agent writes `Content-Length` itself.
- `Set-Cookie` and `Set-Cookie2` headers are removed from every answer, and
  Blueprintr removes them again before storing a result.

### The read-only guard

The allow-list decides where the agent may connect. The read-only guard decides
what a request may do there: the agent relays only requests that read, checked
for each provider on method, path and, where the product needs it, the query or
body. For NetBox, Infoblox, Veeam Backup & Replication and PRTG it accepts only
the exact endpoints the connector uses. For SolarWinds, Zabbix, Checkmk,
Icinga 2, ManageEngine OpManager and WhatsUp Gold it accepts any read that
product's API offers, so give the agent an account that can read only what
Blueprintr should see. The rules come from the provider in your configuration,
never from the job. Anything else is refused with `operation_not_allowed`.

Some reads are refused too, because their answers would contain a stored
secret: PRTG's `getobjectproperty.htm` and `getpasshash.htm`, OpManager
operations that name keys, passwords or credentials, NetBox's token and config
context endpoints, and Infoblox's credential fields. The guard also refuses a
method-override header asking for anything but `GET`, and a path with encoded
slashes, backslashes, encoded dots, path parameters or empty segments.

Setting `"allowWriteOperations": true` on one integration in `config.json` lifts
the guard for that integration only. The agent logs a warning about it at every
start.

### The token vault

WhatsUp Gold and Veeam Backup & Replication exchange a username and password for
an access token. The agent replaces `access_token` and `refresh_token` in that
answer with a handle (`BPLV_` and 32 hex characters), keeps the token in memory,
and puts it back when the handle appears in a later request for the same
integration.

- A handle works only for the integration it was issued for.
- Tokens are never written to disk. Each expires with the token, after a day at
  most, and all are lost on restart, after which the connector asks for a new
  one.
- Blueprintr can ask for more fields to be kept back, never fewer.
- An answer the agent cannot fully check for a token is refused with
  `credential_unresolved`. A refresh grant is refused.

## Certificate checking

| Connection | Trusted roots | Turning checks off |
| --- | --- | --- |
| To Blueprintr | Node.js's bundled roots, the operating system's store (`trustSystemCa`, on by default), `NODE_EXTRA_CA_CERTS` and `extraCaFile`. `apexUrl` must be `https://` | The agent has no setting for it |
| To your systems | The same, except `extraCaFile`. An integration with a `caFile` trusts that file alone | `allowInsecureTls` or `allowPlaintextHttp` on one integration, each logged as a warning at every start |

The agent adopts a gateway that Blueprintr advertises only when it is
`https://` on your `apexUrl`'s host or a subdomain of it. The proxy is used for
traffic to Blueprintr only, never for connections to your systems, as
[Proxies](/foliums/blueprintr-user-guide/continuum/local-agent/network-requirements#proxies)
describes.

## Service hardening

| Install | Runs as | Restrictions |
| --- | --- | --- |
| deb, rpm | `continuum-local`, a system account with no login shell | `config.json` is `root:continuum-local`, mode 0640, in a 0750 directory. The systemd unit sets no new privileges, an empty capability set, a read-only filesystem apart from `/var/lib/continuum-local` (0700), no access to home directories, a private `/tmp` and devices, protected kernel settings, IPv4, IPv6 and Unix sockets only, restricted namespaces and a 512 MB memory ceiling. Write-execute memory stays allowed, because the JavaScript engine's compiler needs it |
| Windows MSI | `NT AUTHORITY\LocalService` | `C:\ProgramData\Continuum Local` has its own access list with no inherited entries: SYSTEM and Administrators have full control, and the service may read `config.json` but not change it, and write only to `state` and `logs`. A first install leaves the service stopped and set to Manual |
| Container | The unprivileged `node` user (uid 1000) | The state directory is 0700. npm, npx, corepack and yarn are removed from the image |
| Helm chart | uid 1000, non-root | Read-only root filesystem, every capability dropped, no privilege escalation, the runtime's default seccomp profile, one replica, and no Service or Ingress |

Logs mask every credential and secret reference value the agent loaded, and
anything shaped like a placeholder. The agent writes a warning at every start,
whatever the log level, for each setting that widens what it may do: write
operations, certificate checks off, `http://`, credentials from Blueprintr with
no scope or with certificate overrides, a gateway off your Blueprintr domain, a
configuration or credential file others can read, and a CA file that contains a
private key.

## Supply chain

- The agent has no runtime dependencies. Its SNMP stack, including SNMPv3
  security and DES, is part of the agent, and the bundle is one unminified
  JavaScript file your team can read.
- The deb, rpm and MSI include Node.js 24. The build refuses a Node.js archive
  whose SHA-256 is not pinned in the agent's repository, and the pinned values
  were checked against the Node.js project's signed checksum list. The Windows
  service wrapper, WinSW, is pinned by checksum as well. The container image
  starts from the official Node.js 24 Alpine image, pinned by digest.
- Every release is signed with Sigstore keyless signing by the agent's release
  workflow, and recorded in Sigstore's public transparency log. Accept a
  signature only from that workflow at the version's tag, with the
  [verification commands](/foliums/blueprintr-user-guide/continuum/local-agent/releases#verify-the-downloads).
  `SHA256SUMS` covers the downloads, and the container image and chart are
  signed by digest.
- From 0.2.0, each release also includes a CycloneDX SBOM bound to the bundle by
  a signed attestation, and the container image's package list and
  vulnerability scan. A release is not published if that scan finds a critical
  vulnerability with a fix available.

## If Blueprintr were compromised

An attacker in control of Blueprintr could:

- make the reads each connector's rules allow, to the hosts you listed, with
  your credentials, and see the answers without the credentials, tokens or
  cookies in them. A least-privilege, read-only account limits what those
  answers contain;
- start sweeps inside your discovery ranges, and SNMP probes there or on hosts
  you listed, on a UDP port it chooses, with your SNMP credentials, and see the
  results within the collectors you allow;
- test TCP connections to hosts in an integration's `allowedHosts`;
- with `allowRemoteCredentials` on, read credentials typed into Blueprintr as
  they pass through, and configure integrations for any host your scope allows.
  With no `remoteCredentialScope`, that is any host except loopback, link-local
  and unspecified addresses;
- with `allowWriteOperations` on an integration, send writes to its hosts;
- stop sending work, or show you wrong results.

It could not:

- reach a host outside `allowedHosts`, `discovery.cidrs` or your scope;
- see the credentials in `config.json`: Blueprintr's requests have
  placeholders, and the agent scrubs the values it inserted from each answer, in
  the spellings listed above;
- see an SNMP community or passphrase, which never appears in a result, or a
  token the vault keeps;
- send a write where `allowWriteOperations` is off, turn it on, or send
  certificate settings without `allowTlsOverrides`;
- change `config.json`, or replace or remove an integration defined there;
- widen a sweep, or read ARP, forwarding or contact data through a raw probe
  when you have turned those collectors off;
- connect to the agent, redirect its connection to a host outside your
  `apexUrl`'s domain, upgrade it, or give it code to run. Every job is one of a
  fixed set of request types.

## Reporting a vulnerability

Email **support@blueprintr.io** with "Security: Continuum Local" in the subject.
Include the agent version (`continuum-local --version`), how it is installed,
what you found, how to reproduce it and what an attacker gains. Remove
credentials, host names and customer data from any proof of concept, and never
send a `config.json` or state file: both contain live credentials.

Blueprintr aims to acknowledge a report within 3 working days and to give its
assessment within 10, agrees a disclosure date with you, and credits you in the
release notes if you want. Test only against an agent and a Blueprintr account
that are yours. Fixes ship as a new release, and only the latest release is
supported before version 1.0.
