Skip to content

Security model

Threat model, isolation, and what is left to you

Edit this page
On this page

What Loomscope protects, how, and what it deliberately leaves to you. This is the page to hand to a reviewer.

Threat model

Loomscope holds a map of your network, a list of your weaknesses, and credentials for your infrastructure. It is a high-value target by construction, and the design assumes:

  • A daemon host may be compromised. It is on the network being scanned, often in the least trusted segment.
  • The database may be exfiltrated. Backups travel to places databases do not.
  • A tenant may try to read another tenant. A deployment holding several customers is a normal deployment.
  • An operator may make a mistake. A forgotten WHERE clause should not be a breach.

Tenant isolation

Every business table carries organization_id. Row-level security is enabled and FORCEd on all of them, so the policy applies even to the table owner.

The consequence: a query that forgets its tenant filter returns nothing, rather than returning another organisation's estate. Isolation is in the database, not in the application, so it holds regardless of which code path reached the query.

Every endpoint derives the organisation from the authentication context. A client-passed organisation id is never trusted, anywhere. Cross-organisation access requires an explicit scope and writes an audit entry; there is no ambient superadmin.

Credentials at rest

SecretProtection
Stored credentialsAEAD encryption via pgcrypto, keyed by LOOMSCOPE_KMS_KEY
API keys (daemon, SCIM)SHA-256 hashed; shown once, never recoverable
PasswordsArgon2id
Invitation linksHashed; single-use, with an expiry
Session cookiesSigned with BETTER_AUTH_SECRET

LOOMSCOPE_KMS_KEY lives only in the environment. The database alone does not decrypt anything — which is what makes an exfiltrated dump much less useful than it looks, and why a backup restored under the wrong key comes back looking healthy and silently failing.

Daemon privilege

The daemon is the component most likely to be attacked, so it holds the least.

PropertySetting
Linux capabilitiesAll dropped except NET_RAW
privilegedNever
Filesystemread_only: true, with a tmpfs for /tmp
Privilege escalationno-new-privileges:true
Config mountRead-only
Image tagPinned semver, never :latest
Inbound connectionsNone. It polls outbound only
Database accessNone
Cloud credentialsNone — cloud discovery runs in the control plane

Where the host permits unprivileged pings (net.ipv4.ping_group_range), the daemon needs no capability and no root at all.

A compromised daemon gets an attacker a scanner and one API key scoped to posting observations for one organisation. It does not get the database, the cloud accounts, or the other daemons.

Network exposure

DirectionRequired
Daemon → control planeOutbound HTTPS. Nothing inbound to the daemon
Control plane → databasePostgreSQL
Control plane → cloud APIsOnly if a cloud account is configured
Control plane → AI providerOnly if the assistant is enabled with a hosted provider
Anything → the internetOtherwise nothing. No telemetry, no licence check, no update ping

Web application

  • CSRF on every cookie-authenticated state-changing endpoint. API keys are exempt, because CSRF is an attack on ambient credentials and a bearer token is not one.
  • No localStorage or sessionStorage for anything security-relevant in shared client paths.
  • Secrets never reach the client bundle. Server-only modules are marked and enforced.
  • Session revocation is immediate. Removing or deprovisioning a member ends the sessions they already hold, rather than stopping their next sign-in.

Logging

Structured JSON — pino for the control plane, zerolog for the daemon — with a redacting layer that drops credentials, API keys, SNMP community strings and session tokens before they reach a log line.

Structured fields are what make that enforceable; interpolated strings are not filterable.

Supply chain

  • Images pinned to a semantic version.
  • Dependencies audited in CI.
  • Secret scanning on every change, with any exception justified inline rather than by a broad ignore rule.
  • Migrations linted by squawk and applied against a seeded database before images publish.

What Loomscope does not do for you

Stated plainly, because a security page that only lists strengths is a marketing page:

  • No TLS termination. Port 3000 is cleartext. Put a proxy in front — see TLS and reverse proxy.
  • No resource limits in the shipped compose file. A runaway scan can consume the host.
  • The server and postgres services are not hardened the way the daemon is — neither sets read_only nor drops capabilities.
  • No rate limiting on sign-in beyond what Better-Auth provides.
  • No secret rotation tooling. Rotating LOOMSCOPE_KMS_KEY is a manual maintenance operation.
  • No SAML.

Reporting a vulnerability

Please do not open a public issue. SECURITY.md describes the private reporting channel.