Skip to content

Roadmap

What is planned, and what has been ruled out

Edit this page
On this page

This is what Loomscope intends to build, in the order it intends to build it, and — just as importantly — what it has decided not to build.

Dates are deliberately absent. Loomscope is maintained by one person; sequence is a commitment, timing is not.

Current release: 0.13.1. Usable, covered by CI, not yet stable.

Every release below carries an entry in CHANGELOG.md with the same four headings, in this order, and a heading is omitted only when it is genuinely empty:

  • Security — anything that changed a boundary. First, always, because it is the section that decides whether an upgrade is optional.
  • Added — new capability, named from the operator's side.
  • Fixed — with the symptom stated, not just the cause. "The linter had never loaded its configuration" is more useful to a reader than "fix lint config".
  • Known defects — what shipped broken, and the workaround. A release that hides one of these buys a week and spends a customer's trust.

How to read this

Each item carries a state:

StateMeaning
ShippedIn main and covered by CI.
NextCommitted to, and the next thing to be worked on.
PlannedAgreed in principle, with an ADR where the design is non-obvious.
ConsideringUnder analysis. May be declined.
DeclinedDecided against. The reasoning is recorded so it is not relitigated.

0.2 — Close the credibility gaps

The theme is removing the distance between what Loomscope claims and what it does. Nothing here is a new capability; all of it is making the existing surface trustworthy.

ItemStateNotes
Restore a working linterShippedA root flat config now runs in CI. See ADR-0008.
Test coverage on the HTTP APINextTwenty-one of twenty-eight. The whole daemon protocol — register, heartbeat, jobs/poll, observations, credentials — plus /api/v1/metrics and the six public collections, whose suite runs every invariant against every collection so that one endpoint quietly disagreeing with the others fails the build. That is the entire surface a third party integrates against. The session-authenticated routes are now covered too, by one suite asserting the property that matters uniformly across them — a caller the route cannot identify is refused, and refused with a response rather than a thrown error. It found two routes resolving the session with no guard, so an expired cookie produced a 500 with a stack trace where a 401 belonged. The remainder are third-party adapters (Better-Auth, tRPC) and the SCIM surface, which scripts/scim-e2e.sh covers.
Test tenant isolation directlyShippedIt did not hold. The application connected as a superuser, which PostgreSQL exempts from row-level security entirely. Scoped transactions now drop to an unprivileged role, and eight tests assert the boundary against a real database.
Lazy configuration loadingShippednext build and every pure-logic test now run with no environment at all.
Replace the magic-link stubShippedIt no longer prints a single-use credential to stdout; it refuses instead, until a mail transport exists.
Design token passShippedEvery colour literal is gone; one token set drives both themes. See ADR-0007.
A visual direction, rather than a defaultShippedBlueprint: warm neutrals, a copper accent that frees blue to mean information, square controls, hairlines instead of shadows, and data set in mono. One token file moves the application and the documentation site together. See ADR-0022.
Daemon enrolment in the UIShippedSettings → Daemons: enrol, rotate a key, reassign a site, retire. The key is shown once and only its hash is stored. Verified end to end — a key issued by the screen registers a daemon and passes a heartbeat, and is rejected against any other daemon id.
Revocable topology sharesShippedThe share panel lists every link for a topology — when it was made, whether it carries a password, when it expires — and revokes one. Verified: a live link serves 200, and 404s the moment it is revoked.
Operator surface — actions, settings, topologyNextADR-0009. The dead ends are closed, and the topology visualiser of item 4 now produces drawings that fit a screen rather than 21:1 ribbons. Item 6 gained the piece that was blocking everything else: Settings → API keys, which the REST API, the CLI and the MCP server all told people to use and which did not exist — every key used to build them was inserted with SQL. Member management shipped earlier and this row claimed otherwise. What remains of item 6 is organisation rename and retention; item 5 has its first piece: host removal and restore, with a page to restore from — without one, "Remove" is indistinguishable from "Destroy" to whoever presses it. Tagging, credential rotation and topology rename remain.
Browser tests on the critical pathsPartialPlaywright is installed and nine tests are written against the four paths the README named — sign-in and its redirect, the host list with search and row navigation, a topology that actually paints nodes, the vulnerability filters. They have never been run. Chromium needs system libraries that playwright install --with-deps installs with sudo, which was not available where this was written, so the suite is unverified rather than passing. One command closes it: pnpm -C apps/server exec playwright install --with-deps chromium, then make test-e2e-browser.
Realtime stream trusted a client-supplied org idShipped/api/sse read the tenant from a query parameter and middleware exempts the route from its cookie check, so nothing verified membership. It resolves the org from the session now.
Server-side sort, filter and pagination on every listShippedEvery list page ended in a hardcoded LIMIT with no way past it. Keyset cursors throughout, verified against real row counts for duplicates and gaps.
WCAG 2.1 AA on both themesShippedTwo pairings failed. A test over the token file keeps them passing.
Documentation siteShippedloomscope.dev — the Markdown in docs/ rendered on the product's own tokens. It had not built at all for weeks; CI builds it now.
Decide the fate of eBPFShippedDecided and done in 0.3.2: removed. This row contradicted the one twelve lines below it, which had recorded the same work as shipped. All that remains is a capabilities.ebpf boolean in the daemon contract, which every daemon reports as false — harmless, and cheaper to leave than to break the contract over.

0.2.1 — A deployment you can actually start

The 0.2 audit found that the product has no front door. Nothing in the shipped code ever creates an organisation or a membership — the only such statements live in tests and the seed script — and there is no sign-up page, no invitation and no role change. The quick start says "sign in"; a new installer has nothing to sign in with.

ItemStateADR
First-run setup, invitations, role changesShipped 0.3.0ADR-0011
CVE feed on by default, with a staleness lineShipped 0.3.1ADR-0015
One Postgres connection budget per processShipped 0.3.1ADR-0019
The job runner no longer deadlocks itselfShipped 0.4.1ADR-0021
OIDC single sign-on with JIT provisioningShipped 0.5.0ADR-0016
Slack and Teams alerting adaptersShipped 0.6.0ADR-0013
Failed scheduled ticks are retriedShipped 0.6.1ADR-0012
TLS chain validationShipped 0.7.0ADR-0014
PagerDuty and Jira alerting adaptersShipped 0.8.0ADR-0013
SCIM 2.0 provisioning and deprovisioningShippedUsers, Groups, group-to-role, and deprovisioning that ends the sessions somebody already holds.
SCIM Groups and group-to-role mappingShipped 0.10.0ADR-0016
Public REST API, CLI and MCP serverShipped 0.11.0ADR-0003
The daemon on Windows and macOSShipped 0.11.0ADR-0025
nmap detected and reported (not yet used)Shipped 0.11.0ADR-0024
/hosts responds to in-page navigationShipped 0.5.1Was a <form action="/hosts"> string action

Both features here are small. They are grouped as a patch release because each removes a way a first-time installation silently does nothing useful.

0.2.2 — Work that happens without a browser open

ItemStateADR
Scheduled work moves to RiverShippedSuperseded. A dedicated worker container with PostgreSQL advisory locks covers the need; River stays a maybe. See ADR-0012.
Jobs run in their own worker containerShippedThe compose stack ships a worker service, and LOOMSCOPE_JOBS_IN_WEB defaults to on so a deployment without one still runs its scans.
Retry, backoff, dead letter and historyShippedPartial. A failed tick retries within a run, with backoff derived from the job's interval. Not across a restart — that gap is listed under Known gaps.

This is the release after which the README stops telling operators to treat the server as a single instance.

0.3 — Integrations and alerting

Loomscope currently tells you things only if you go and look. This is the release where it can tell you without being asked.

ItemStateNotes
Outbound webhooks with HMAC signaturesShippedThe generic case, and the one that unblocks everything else via n8n or Zapier.
Slack and Microsoft Teams deliveryShippedIncoming webhooks, where the URL is itself the credential.
Jira and Linear issue creationShippedPartial. Jira ships; Linear does not.
PagerDuty eventsShippedEvents API v2, with the endpoint supplied rather than typed.
Alert rule builderShippedEvent type plus condition already shipped with the pipeline; what was missing was the dry run, and it is the half that stops a rule from paging somebody four hundred times over a weekend. Each rule now carries what it would have done over the last 30 days, reconstructed from the tables the emitters read — not from notification_deliveries, which only holds events that already matched a rule and would answer a different question while looking like an answer. service.changed and daemon.offline say they cannot be previewed rather than returning a confident zero: neither keeps the history a replay needs.
Delivery pipelineShippedExponential backoff, five attempts, per-event-key deduplication so a flapping daemon cannot spam a channel.

Event types: host.added, host.removed, service.changed, tls.expiring, vuln.critical, vuln.high, snapshot.diff_significant, daemon.offline.

The pipeline is the decision, not the destination list — see ADR-0013. Two items ride on it:

ItemStateADR
Certificate expiry warnings and weak-algorithm findingsShippedADR-0014
Remove the eBPF scaffoldShipped 0.3.2ADR-0018

0.4 — Programmable Loomscope

Three interfaces onto the same inventory, for three different consumers. Each has its own ADR because each could reasonably have been declined.

ItemStateADR
Stable public REST API with versioning and API keysShippedADR-0003 accepted 2026-08-18. The read surface ships: eight collections — hosts, services, networks, sites, certificates, vulnerabilities, snapshots, topologies — behind one reader, so they cannot each grow their own opinion about limits, cursors and unknown parameters. lsk_ keys with scopes floored by the membership's current role, RFC 9457 problem details with a request id, keyset pagination that refuses an out-of-range limit rather than clamping it. A test compares each endpoint's accepted parameters against the published document in both directions, so the contract cannot silently drift from the server. The four write operations ship too — register a network, trigger a scan, change a finding's state, take a snapshot — each leaving an audit row attributed to the key that acted. scan:run is a separate scope from inventory:write, because sending packets across a customer's network is a different power from changing a record.
loomscope command-line clientShippedADR-0004. A single static Go binary, generated against the same OpenAPI document as the daemon's client so the two cannot come to describe different APIs. Human tables by default, --output json or csv for a pipe, and exit codes that carry meaning — 3 for --fail-on-match specifically, so a build step can tell "I found the CVE you asked me to watch for" from "the server was unreachable"; collapsing those into 1 is how a broken check comes to look like a passing one. There is no --api-key flag: argv is readable in ps and lands in shell history, so typing one earns an error telling you to rotate the key rather than a silent success. See the CLI reference.
MCP server exposing the inventory to AI agentsShippedADR-0005. loomscope mcp serves the inventory over stdio to any MCP-capable client — read-only, and scoped to the key it starts with, because every call goes through the same public API with the same row-level security. It never calls a model: the in-app assistant calls a model from Loomscope, this exposes Loomscope to somebody else's. The Streamable HTTP transport the ADR also specifies ships too, for clients that cannot spawn a process — authenticated with a bearer token it refuses to start without, bound to loopback by default, and validating every request's Origin because a browser will send credentialed requests to 127.0.0.1 on behalf of any page the operator has open. What is still not built is an endpoint in the control plane, where one could serve a whole team alongside the session and the audit log. Write tools are deliberately deferred — an agent that can start scans can put traffic across an estate on a misread instruction. See the MCP reference.

The ordering is deliberate. The REST API comes first because the CLI and the MCP server are both clients of it — building either one first would bake in an unstable contract.

0.5 — Operable at scale

Proved on a cluster, 2026-08-19

The Helm chart was linted, rendered and schema-checked by CI and had never been installed anywhere. Installing it into a k3s node with ingress-nginx and cert-manager took about an hour and found three defects, every one of which made the product unusable at any real hostname:

  • A chart helper named a Secret the chart never creates. cve-ingest could not start whenever the bundled PostgreSQL was enabled.
  • The auth client had localhost:3000 compiled into the browser bundle. NEXT_PUBLIC_* is substituted at build time, so every released image told visitors to authenticate against their own machine. Sign-in span forever rather than failing, because the rejected fetch had nothing waiting on it.
  • The session cookie was set, sent, and never recognised. Better-Auth prefixes its cookie with __Secure- over HTTPS; the middleware looked only for the bare name, so a successful sign-in bounced straight back to the login page.

All three share a signature: they exist only when the product is served behind TLS at a real hostname. 721 tests, a kubeconform-validated chart and a CI suite that had grown all week could not see any of them.

The install path is now scripts/k3s-install.sh, with scripts/k3s-load-images.sh for a cluster with no registry credentials and scripts/vps-recon.sh for looking at a machine before putting anything on it. The docker compose quickstart remains unproven — the node used had no Docker, and installing one beside a running production cluster was not a change worth making for a test.

ItemStateNotes
Backup and restoreShippedscripts/backup.sh and scripts/restore.sh. One archive holding the dump, its checksum and a fingerprint of the encryption key; the restore refuses a key mismatch rather than leaving every credential silently undecryptable.
Helm chartShipped 0.4.0External PostgreSQL by default; the chart never generates the encryption key. See ADR-0017.
Prometheus /metricsShippedGET /api/v1/metrics, text exposition format, authenticated with a metrics:read API key and scoped to that key's organisation. Exposes what the monitoring guide already asked operators to watch by hand — job run ages above all, since a stopped scheduler produces no error anywhere else.
Performance pass at 100k hostsShippedMeasured at 100 001 hosts, not asserted. Every list sort is an index scan with no sort node: 0.15 ms for last-seen, 0.14 ms for hostname, 0.23 ms for device type, 7.7 ms for a trigram search. No index was added — the existing ones already produce the optimal plan, and an index no query uses is a write cost with no reader. The p99 target remains unverified on production hardware, which is a deployment rather than a laptop. ADR-0023
Documented upgrade pathShippedUpgrading: the Compose and Helm procedures, why migrations never run automatically, what expand-and-contract buys a rolling deploy, when a rollback is a tag change and when it is a restore, and the five checks worth running afterwards.

1.0 — Enterprise readiness

ItemStateNotes
SSO — OIDC and SAMLShippedPartial. OpenID Connect with PKCE, JIT provisioning and group-to-role mapping ships, exercised against a real Keycloak in CI. SAML does not.
SCIM 2.0 provisioningShippedUsers, Groups, group-to-role, and deprovisioning that ends the sessions somebody already holds.
Audit log surfacePartly shippedReadable, filterable by subsystem and paginated under Settings. Search, CSV export and configurable retention are not built.
Verified air-gap bundlePlannedA single offline archive — images, CVE feeds, signatures, documentation — proven on an isolated machine rather than assumed to work.

1.0 means the interfaces are stable and upgrades are supported, not that the feature list is finished.


Considering

Ideas under analysis. Each will get an ADR before any code is written.

IdeaWhy it is interestingWhy it might be declined

Candidates from a field comparison, 2026-08-19

Prompted by looking at L0p4Map, a PyQt6 desktop scanner. It is not a competitor — a single-user desktop tool and a multi-tenant inventory platform do not replace each other — but its feature list named things we do not do, and some are cheap.

Ordered by what they are worth, not by effort.

CandidateWhy it mattersState here
nmap and NSE, when the host has nmapThe tool DevOps and network teams already trust, with twenty years of service-version probes and a script library covering protocols we will never write a scanner for. Invoked, never shipped — so no redistribution obligation. See ADR-0024.Shipped, unreleased — the daemon runs it and attributes the results. No scan-form control yet.
The daemon on Windows and macOSAsked for by sites that will not introduce Docker for one binary. It already cross-compiles to all three targets — what is missing is ARP, which today degrades silently off Linux. See ADR-0025.Shipped in 0.11.0. All three platforms read the neighbour cache, and CI runs each one.
Make mac_vendor work, with an OUI databaseThe pattern engine advertises mac_vendor in its grammar and CLAUDE.md documents it, but pattern.go returns Matched: false unconditionally and nothing populates a vendor from a MAC. Every signature using it fails silently.Fixed, unreleased. All three IEEE registries ship, longest prefix wins. ADR-0026
Traceroute-based L3 mappingOur layer-3 topology is built from SNMP routing tables, so an estate that will not give us a community string gets no map. Traceroute groups hosts under their last hop without any credential.Shipped, unreleased. One trace per scanned range; the adjacent router is recorded with source = 'traceroute'. Needs CAP_NET_RAW, and says so where it is missing.
mDNS and NetBIOS name resolutionscan/dns does reverse DNS only. Printers, IoT and Apple devices frequently have no PTR record and answer mDNS; Windows hosts answer NetBIOS. Today they appear unnamed.Shipped, unreleased. Unicast to the host, only for what DNS could not name.
Default-credential device flaggingFlagging the products that ship with a documented factory login turns an inventory entry into a finding, which is the product's whole argument. The note here used to say "banner-grabbing already identifies iLO, Zebra, SATO and XPort" — that was L0p4Map's list, not ours. We had ten core signatures and none of them was such a device.Shipped, unreleased. The mechanism, plus Grafana, Zabbix and Redis; the YAML field lets an operator add the rest without a rebuild. Nothing is ever tried against a host.
TTL-based OS hintA cheap first guess (Linux/macOS vs Windows vs network device) for hosts that answer ICMP and nothing else — which is most of a client subnet.Shipped, unreleased. In metadata, never in os_name.
Rename a node on the topologyOperators name things their own way, and a map they cannot annotate is one they stop trusting. Cosmetic, and cheap while the topology work is open.Absent.
A live flow viewWe collect NetFlow v5/v9 and sFlow and show it aggregated. A per-device live view is more immediate, and immediacy is what a demonstration rewards.Partial — collection exists, the view does not.

A correction. This paragraph previously declined full nmap/NSE integration outright, on the grounds that it would mean shipping a restrictively-licensed binary and its update cadence inside an air-gap bundle. The observation was right and the conclusion was wrong: the licence problem is with shipping nmap, not with using it. Invoking the nmap the operator already installed carries no redistribution obligation, needs no OEM licence, and leaves the air-gap bundle exactly as auditable as it is today. It is in the table above, and the reasoning is recorded in ADR-0024.

The honest reading of that comparison. In a ten-minute demonstration to a network engineer, a desktop tool that launches and draws a live graph beats a platform that wants Docker, an enrolled daemon and a scan cycle. Our advantage only appears at the second question — who changed what, on which site, six months ago, and can you prove it to an auditor. That is a real difference and a real weakness at the same time, and the answer is to make first-run work faultlessly rather than to add features.

| Agentless Windows inventory over WinRM | Closes a real gap for mixed estates | Credential handling on Windows is a large surface, and SNMP plus port fingerprinting already identify most Windows hosts | | Passive discovery via SPAN/mirror ports | Finds hosts that never answer a probe | Requires switch configuration most users cannot change, and overlaps with the NetFlow path | | Config-drift detection on network devices | Natural extension of snapshots | Turns Loomscope into a configuration manager, which is a different product | | Multi-instance federation | Some estates genuinely cannot route to one control plane | Doubles the security model. Explicitly out of scope for multi-site, which solves the common case |

Declined

Recorded so the reasoning survives.

ProposalDecision
gRPC between daemon and control planeDeclined. REST and SSE meet the need, and gRPC would add a second transport, a second schema toolchain and proxy complications for no user-visible gain. ADR-0001
A message broker for job distributionDeclined. PostgreSQL with LISTEN/NOTIFY and job rows covers the volume Loomscope targets. A broker is one more component every operator has to run, back up and upgrade. ADR-0002
Redis for caching or pub/subDeclined. Same reasoning. PostgreSQL is already required; Redis would be a second stateful dependency. ADR-0002
A privileged daemon containerDeclined. Capabilities only, always. The security review is a feature.
Telemetry or phone-home, even anonymousDeclined. Loomscope is deployed by people who chose self-hosting specifically to avoid it.
Cloud API calls from the daemonDeclined. Cloud discovery runs in the control plane so daemon hosts never hold cloud credentials.
A hosted SaaS editionDeclined for the foreseeable future. It would change what the project optimises for.

Known gaps

Carried from the changelog so this document is honest on its own:

  • Scheduled work does not retry across a restart. A failed tick is retried within the same run, with backoff, where the job's interval is long enough for the wait to matter — but a worker that dies mid-retry resumes the normal schedule rather than resuming the attempts. Closing it properly needs a durable queue, which is a dependency decision still open.
  • SAML is not implemented. OpenID Connect and SCIM cover the directories that speak either; a provider that only speaks SAML cannot federate.
  • There is no public inventory API. /api/v1/ carries the daemon protocol and a few administrative endpoints; the UI talks over tRPC, which is internal and not a stable interface.
  • There is no /metrics endpoint. Prometheus support is planned.
  • Nothing prunes itself. Snapshots and audit entries accumulate indefinitely, so disk grows with inventory size times days retained.
  • Automated tests reach a minority of the HTTP routes, and no browser test framework is installed, so the UI is verified only by hand.
  • The published OpenAPI specification can drift from the Zod contracts if the pre-commit hook is bypassed.

Influencing this roadmap

Open an issue describing the problem you have, not the feature you want. The most useful contributions to this document have been descriptions of an environment Loomscope handled badly.