Every claim opens back into the bytes that produced it.

Local-first network reconnaissance. One binary, deterministic output, and a diff that tells you what changed since the last scan.

cortex scan --ports 22,80,443,5432,6379,8443 --evidence full 10.20.0.0/24

Scan
01JQ7XR4K2
Fingerprint DB
cortex-fp/2026.08.1
Seed
0x5f3a91c4

Planned interface The command above is the designed v1 CLI. It does not run today.

Recorded fixture Illustrative recorded-format fixture for the finished Cortex model, from a purpose-built lab network. Not output from today's engine.

10.20.0.7:80/tcp nginx 1.27.2 confidence 98 probe 18 B sent 296 B received
Sent probe GetRequest / 18 bytes
Payload sent to 10.20.0.7:80/tcp by probe GetRequest
00000000 474554202f20485454502f312e300d0a GET / HTTP/1.0..
00000010 0d0a ..

GET / HTTP/1.0\r\n\r\n

Received 296 bytes
Response received from 10.20.0.7:80/tcp. The span highlighted and underlined is the text the fingerprint pattern matched.
00000000 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d H T T P / 1 . 1 2 0 0 O K .
00000010 0a 53 65 72 76 65 72 3a 20 6e 67 69 6e 78 2f 31 . S e r v e r : n g i n x / 1
00000020 2e 32 37 2e 32 0d 0a 44 61 74 65 3a 20 54 75 65 . 2 7 . 2 . . D a t e : T u e

HTTP/1.1 200 OK\r\nServernginx/1.27.2.2\r\nDate: Tue, 25 Aug 2026 09:14:16 GMT\r\nContent-Type: text/html\r\nContent-Length: 615\r\nLast-Modified: Thu, 21 Aug 2026 08:31:09 GMT\r\nConnection: close\r\nETag: "68b0a4f7-267"\r\nAccept-Ranges: bytes\r\n\r\n<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n

248 further bytes held in the evidence store.

Matched
nginx/1.27.2 offset 25, length 12
Pattern
m|^HTTP/1\.[01] \d\d\d .*\r\nServer: nginx/([\w._-]+)\r\n|s p/nginx/ v/$1/
Fingerprint DB
cortex-fp/2026.08.1
Evidence
SHA-256 of the 296 bytes above
Timing
2026-08-25T09:14:16.771Z round trip 17.7 ms

Getting it

Two different things, kept apart on purpose: what you can do with this project today, and how the finished tool is designed to be installed.

Today, from source

In development
git clone https://github.com/Viktor25104/Cortex.git
cd Cortex

The repository is public and the clone works. The build does not: at commit eabc16d the Go workspace file is malformed, so go build ./... fails before it reaches any package. Restoring the build is the first task of the engine plan, and until it lands there is nothing here you can run.

Target v1 distribution

Planned
  • One static binary per platform Linux, macOS and Windows on amd64 and arm64, with checksums and signatures.
  • No data directory, no runtime dependency The fingerprint database is compiled into the binary, so a fresh machine needs nothing else.
  • No privileges for the default scan Connect, TLS and HTTP observation need no elevation anywhere. Raw sockets are opt-in.

There is no release yet, so there is no download URL, and none is printed here. When binaries exist they will be published on the repository's releases page with the checksums beside them.

A claim is a summary of some bytes. Keep the bytes.

Scanners tell you a port is running nginx. Almost none of them can still show you the response that made them say so, because the moment the process exits, the reasoning is gone.

Cortex writes every probe exchange to a content-addressed store: the exact payload sent, the exact bytes received, the pattern that matched and where it matched, the version of the fingerprint database that supplied it, and a SHA-256 over the response.

A finding then references that hash rather than restating a conclusion. False positives become diagnosable instead of mysterious, results become auditable, and the captured responses become the regression corpus that keeps fingerprinting honest as it grows.

Recorded fixture Illustrative recorded-format fixture for the finished Cortex model, from a purpose-built lab network. Not output from today's engine. Open a row to descend from the claim to the bytes.

10.20.0.7:22/tcp OpenSSH 9.6p1 confidence 96 probe 0 B sent 23 B received
Sent probe NULL / 0 bytes

The NULL probe sends nothing. It waits to see whether the service speaks first, which is itself evidence.

Received 23 bytes
Response received from 10.20.0.7:22/tcp. The span highlighted and underlined is the text the fingerprint pattern matched.
00000000 53 53 48 2d 32 2e 30 2d 4f 70 65 6e 53 53 48 5f S S H - 2 . 0 - O p e n S S H _
00000010 39 2e 36 70 31 0d 0a 9 . 6 p 1 . .

SSH-2.0-OpenSSH_9.6p1\r\n

Matched
9.6p1 offset 16, length 5
Pattern
m|^SSH-([\d.]+)-OpenSSH[_-]([\w.]+)[ -]?(\S*)\r?\n| p/OpenSSH/ v/$2/ i/protocol $1/
Fingerprint DB
cortex-fp/2026.08.1
Evidence
SHA-256 of the 23 bytes above
Timing
2026-08-25T09:14:10.116Z round trip 26.5 ms
10.20.0.33:6379/tcp Redis key-value store 7.2.5 confidence 93 probe 14 B sent 186 B received
Sent probe RedisInfo / 14 bytes
Payload sent to 10.20.0.33:6379/tcp by probe RedisInfo
00000000 2a310d0a24340d0a494e464f0d0a *1..$4..INFO..

*1\r\n$4\r\nINFO\r\n

Received 186 bytes, truncated at the evidence cap
Response received from 10.20.0.33:6379/tcp. The span highlighted and underlined is the text the fingerprint pattern matched.
00000000 24 31 32 38 34 0d 0a 23 20 53 65 72 76 65 72 0d $ 1 2 8 4 . . # S e r v e r .
00000010 0a 72 65 64 69 73 5f 76 65 72 73 69 6f 6e 3a 37 . r e d i s _ v e r s i o n : 7
00000020 2e 32 2e 35 0d 0a 72 65 64 69 73 5f 67 69 74 5f . 2 . 5 . . r e d i s _ g i t _
00000030 73 68 61 31 3a 30 30 30 30 30 30 30 30 0d 0a 72 s h a 1 : 0 0 0 0 0 0 0 0 . . r
00000040 65 64 69 73 5f 6d 6f 64 65 3a 73 74 61 6e 64 61 e d i s _ m o d e : s t a n d a
00000050 6c 6f 6e 65 0d 0a 6f 73 3a 4c 69 6e 75 78 20 36 l o n e . . o s : L i n u x 6
00000060 2e 38 2e 30 2d 34 35 2d 67 65 6e 65 72 69 63 20 . 8 . 0 - 4 5 - g e n e r i c
00000070 78 38 36 5f 36 34 0d 0a 61 72 63 68 5f 62 69 74 x 8 6 _ 6 4 . . a r c h _ b i t
00000080 73 3a 36 34 0d 0a 70 72 6f 63 65 73 73 5f 69 64 s : 6 4 . . p r o c e s s _ i d
00000090 3a 31 0d 0a 74 63 70 5f 70 6f 72 74 3a 36 33 37 : 1 . . t c p _ p o r t : 6 3 7
000000a0 39 0d 0a 75 70 74 69 6d 65 5f 69 6e 5f 73 65 63 9 . . u p t i m e _ i n _ s e c
000000b0 6f 6e 64 73 3a 34 31 31 0d 0a o n d s : 4 1 1 . .

$1284\r\n# Server\r\nredis_vers7.2.5.2.5\r\nredis_git_sha1:00000000\r\nredis_mode:standalone\r\nos:Linux 6.8.0-45-generic x86_64\r\narch_bits:64\r\nprocess_id:1\r\ntcp_port:6379\r\nuptime_in_seconds:411\r\n

Matched
7.2.5 offset 31, length 5
Pattern
m|^\$\d+\r\n# Server\r\nredis_version:([.\d]+)\r\n| p/Redis key-value store/ v/$1/
Fingerprint DB
cortex-fp/2026.08.1
Evidence
SHA-256 of the 186 bytes above
Timing
2026-08-25T09:14:13.550Z round trip 11.0 ms
10.20.0.21:25/tcp Postfix smtpd confidence 88 probe 0 B sent 52 B received
Sent probe PostfixBanner / 0 bytes

The NULL probe sends nothing. It waits to see whether the service speaks first, which is itself evidence.

Received 52 bytes
Response received from 10.20.0.21:25/tcp. The span highlighted and underlined is the text the fingerprint pattern matched.
00000000 32 32 30 20 6d 61 69 6c 2e 6c 61 62 2d 61 2e 69 2 2 0 m a i l . l a b - a . i
00000010 6e 74 65 72 6e 61 6c 20 45 53 4d 54 50 20 50 6f n t e r n a l E S M T P P o
00000020 73 74 66 69 78 20 28 44 65 62 69 61 6e 2f 47 4e s t f i x ( D e b i a n / G N
00000030 55 29 0d 0a U ) . .

220 mail.lab-a.internal ESMTP Postfix (Debian/GNU)\r\n

Matched
Postfix offset 30, length 7
Pattern
m|^220 ([\w.-]+) ESMTP Postfix| p/Postfix smtpd/ i/$1/
Fingerprint DB
cortex-fp/2026.08.1
Evidence
SHA-256 of the 52 bytes above
Timing
2026-08-25T09:14:12.004Z round trip 57.3 ms

The hash shown on each record is the real SHA-256 of the bytes displayed above it. Copy the response out of the page and hash it yourself if you want to check.

One probe, end to end

This is the whole detection path for the record in the hero, in the order it happens, with what exists in the repository today marked against each step.

  1. Select

    Probes are chosen by port and by rarity rather than tried in file order, so a filtered port cannot occupy a worker for minutes.

    probe GetRequest Planned
  2. Send

    The probe payload goes out on its own connection. Reusing one socket for every probe is what makes most hand-rolled scanners produce nonsense after the first response.

    18 bytes out In development
  3. Receive

    The response is read under a deadline derived from a measured round-trip estimate, capped, and kept verbatim. Binary stays binary.

    296 bytes in In development
  4. Match

    Patterns come from a parsed service-probe grammar with dynamic delimiters and escape normalisation. Constructs Go regexps cannot express are recorded as unsupported, not silently counted as parse failures.

    offset 25, length 12 In development
  5. Extract

    The version template turns the capture groups into product, version and extra information, with a confidence value and the method that produced it.

    nginx 1.27.2, confidence 98 Planned
  6. Record

    The response is stored by content address, de-duplicated across hosts, and the claim keeps a reference to it rather than a copy of it.

    sha256:86319f1a…37c7c4 Planned

The parser that reads the probe grammar is the oldest real code in this project and the reason it exists. Everything marked planned above is designed and unbuilt, and the repository does not compile at its current commit.

The second scan is the useful one

Nobody runs a scanner once. Cortex keeps every scan as a structured record, so the question it can answer on the second run is not what is open, but what moved.

Recorded fixture Illustrative recorded-format fixture for the finished Cortex model, from a purpose-built lab network. Not output from today's engine.

01JQ7WA9F1 compared with 01JQ7XR4K2 10.20.0.0/24 5 changes
  • 10.20.0.33:6379/tcp opened Redis key-value store 7.2.5
    Previous

    Not present in this scan.

    Current

    Redis key-value store 7.2.5 no authentication required

    Not present in the previous scan of this range.

  • 10.20.0.7:80/tcp version changed 1.24.0 1.27.2
    Previous

    nginx 1.24.0

    Current

    nginx 1.27.2

    Same service, same evidence shape, different version token.

  • 10.20.0.7:22/tcp evidence changed, claim identical OpenSSH 9.6p1 hash changed
    Previous

    OpenSSH 9.6p1 evidence ev-91ab

    Current

    OpenSSH 9.6p1 evidence ev-91ac

    The version claim did not move. The bytes behind it did: the banner lost its distribution comment.

  • 10.20.0.14:8443/tcp certificate rotated sha256:c73b…41e9
    Previous

    sha256:1f0c…8ad2 valid to 2026-08-19

    Current

    sha256:c73b…41e9 valid to 2027-08-20

    Validity, previous certificate above, replacement below
    2025-08-19 2027-08-20

    Replaced one day after the previous certificate expired. Still self-signed.

  • 10.20.0.42:21/tcp closed vsftpd 3.0.5
    Previous

    vsftpd 3.0.5

    Current

    No longer present. Now answering with a connection refusal.

    Now answering with a connection refusal.

  • 142 subjects unchanged no movement

    Ports, services, certificates and evidence hashes that were byte-identical across both scans. Collapsed by default so that the five rows above are the whole of what a reader has to look at. They are not enumerated in this fixture.

The local interface

Cortex is a command-line tool that can serve a local viewer over its own scan store, bound to loopback, for the times when a table beats a terminal. This is that viewer, running on the recorded fixture.

Recorded fixture Illustrative recorded-format fixture for the finished Cortex model, from a purpose-built lab network. Not output from today's engine. Nothing here contacts a network. Selection and view switching happen in the page.

Scan 01JQ7XR4K2 Targets 10.20.0.0/24 State completed Rate limit 1000/s Concurrency 128 Privilege unprivileged Local errors 0
Hosts and ports in the recorded scan
Address Port State Service Conf. Method
22 open OpenSSH 9.6p1 96 probe
80 open nginx 1.27.2 98 probe
5432 open PostgreSQL 40 port_default
22 filtered - - -
443 open nginx 62 tls_alpn
8443 open nginx 58 tls_alpn
25 open Postfix smtpd 88 probe
6379 open Redis key-value store 7.2.5 93 probe
21 closed - - -

Excluded from this design on purpose: summary tiles, a chart wall, and anything resembling threat intelligence. A scan is not a business metric, and a viewer for one should stay an instrument.

How a scan is put together

Seven stages in a line, and four concerns that cut across all of them. The four are the reason the seven can be trusted, and they are where most hand-rolled scanners quietly have nothing.

  1. Target spec CIDR, ranges, hostnames, files, exclusions
  2. Expander lazy iterator, deduplicated, shuffled from a recorded seed
  3. Discovery which hosts answer, and by what reason
  4. Port scan connect by default, raw sockets only when asked
  5. Protocol detect probe selection, match, version extraction
  6. Enrichment TLS chain and HTTP surface, observed not judged
  7. Store SQLite records, evidence by content address
Scheduler
A token bucket in probes per second, plus a separate ceiling on how many can be in flight, plus a per-host cap so no single machine takes the whole scan at once.
Progress
One event stream consumed by the terminal renderer, the local viewer and the machine-readable output, so all three see the same scan.
Cancellation
A cancelled scan flushes what it already found, records where it stopped, and can be resumed from that position.
Evidence
Every stage that touches the network writes what it saw, capped and content-addressed, before anything interprets it.

One process. No broker, no queue service, no database server, no container required. The finished tool is a single binary that holds the engine, the fingerprint database, the store and the viewer.

Restraint, as a feature

Aggressive defaults are easy to build and hard to live with. These are the constraints the finished tool is designed around, and most of them cost throughput on purpose.

  • Unprivileged by default

    Connect scanning, TLS observation and HTTP observation need no elevation on any platform. That path is what most people should ever use, and it is the default.

  • Privileged mode is explicit, and never silent

    Raw sockets are opt-in. Asking for a SYN scan without the capability fails with the exact command needed to grant it, rather than quietly falling back to a different scan that produces different results.

  • Privileges are dropped before hostile input is read

    Raw handles are acquired at startup, then the process drops to the invoking user. Parsing whatever a target chooses to send back happens unprivileged, because that parser is the largest attack surface in a scanner.

  • Rate control is on by default

    A token bucket, a concurrency ceiling and a per-host cap, with conservative defaults and adaptive backoff when a target starts rate-limiting. A scanner that cannot be slowed down is a liability on someone else network.

  • Large scans have to be confirmed

    Past a threshold, a scan prints the computed host count, probe count and estimated duration and waits. Accidentally scanning a /8 should take more than one typo.

  • Local failures are never reported as target states

    No route, permission denied and file descriptor exhaustion are scan errors with their own class. Reporting them as filtered is silent data corruption, and it is the single most common defect in this category.

  • Evidence is treated as sensitive

    Captured responses will contain credentials sooner or later. The store is written owner-only, known credential patterns are redacted before persistence with the redaction recorded, and reports can be exported redacted.

  • Read-only interactions, permanently

    Probes send the bytes a legitimate client would send. No exploitation, no brute forcing, no fuzzing, no denial of service, no evasion features. This boundary is not a phase, it is the product definition.

Against nmap, honestly

Nmap is the reference implementation of this entire category and it wins most of these rows. Cortex is not trying to replace it. It is trying to answer a different question about the same network.

nmap Cortex
  • OS fingerprinting

    nmap is better

    Around 2,600 curated fingerprints built on sixteen carefully sequenced probes, with two decades of community submissions behind them.

    None. Not in the plan either: one person cannot build that corpus, and pretending otherwise would waste the years it would take.

  • Scripting

    nmap is better

    NSE, roughly six hundred scripts and the community that maintains them.

    No scripting. If it ever arrives it will be sandboxed and narrow, and it will not be a plugin ecosystem.

  • Raw packet handling

    nmap is better

    Correct link-layer framing, ARP, routing, fragmentation and IPv6, hardened across a dozen operating systems.

    The current raw path is structurally wrong and is being rewritten. The unprivileged path comes first because it is what most scans need.

  • Portability and field testing

    nmap is better

    Twenty-five years of platform work. It runs where you need it and it has been wrong in public often enough to be right now.

    Three platforms are targeted. Zero releases exist. Nothing here has been field tested by anyone.

  • Speed

    different goals

    Adaptive timing, retransmission and congestion control. For pure throughput, masscan and its relatives are faster still.

    Not competing. No throughput number has been measured, so none is published, and that race is already won by other tools.

  • Change between scans

    different goals

    ndiff compares two saved XML files and reports host and port differences.

    Diff is a verb over a persistent store, so it can also compare evidence hashes, certificate rotation and version movement, not only presence.

  • Machine-readable output

    Cortex is designed better

    XML designed in 1997, which every consumer wraps in a parser library. There is still no JSON output upstream.

    JSON and JSONL from the first line, schema-versioned and deterministically ordered so two runs can be compared byte for byte.

  • The bytes behind a claim

    Cortex is designed better

    Debug output is unstructured text on a terminal, and it is gone when the process exits.

    Every probe exchange is stored by content address and referenced by the claim it supports. This is the reason the project exists.

  • Distribution and licence

    Cortex is designed better

    Needs installation and a data directory, under a custom licence restrictive enough that many commercial products avoid embedding it.

    Targets one static binary with the fingerprint database compiled in, under Apache-2.0.

Four rows to nmap, three to Cortex, two that are not the same question. A comparison table where one column wins everything is an advertisement, not information.

What is actually built

Nothing on this page is a released capability. At commit eabc16d the repository does not compile, there are no tests, and no binary has ever been published.

This site describes the tool Cortex is designed to be. It exists ahead of the engine on purpose, as the specification the engine is being built against. Every screen you have scrolled past is driven by an authored fixture in the finished data format, and every one of them says so on the object itself.

The register below is the whole truth of the current state, written the way it would have to be written if a reviewer opened the repository straight afterwards. They can, and they should.

Available today 0 In development 6 Planned 14
Capability status against the current repository
Capability Status What is in the repository
TCP connect scanning, three-state classification backend/scanner/tcp_connect.go In development Implemented, including the pre-read that separates a reset from a timeout. Not release quality.
Service-probe grammar parser backend/scanner/probes.go In development The oldest real code here. Dynamic delimiters, escape normalisation, and constructs Go regexps cannot express recorded as unsupported rather than as failures. Soft matches and fallback chains are not handled.
Probe selection by port and rarity backend/scanner/probes.go Planned The rarity, ports and sslports directives are parsed and discarded, so every probe is tried in file order.
Probe exchange and banner capture backend/scanner/tcp_connect.go In development Probes run, but all of them share one connection and the first response of any kind wins. A name is extracted; product, version, extra information and CPE are not.
Plain text and JSON output backend/cli/cli.go In development Both exist. Ordering is not deterministic yet, which is a prerequisite for diff.
SYN scanning backend/scanner/tcp_syn.go In development Present and structurally wrong: a capture handle per port, no link-layer framing, no retransmission. Scheduled for rewrite, and it may be deferred past the first release.
UDP scanning backend/scanner/udp.go In development Sends a single null byte. The parsed UDP probe corpus is loaded and never used.
Product and version extraction backend/scanner/probes.go Planned Designed. The version template parser is an open task in the source.
TLS and HTTP observation Planned Designed. Nothing imports a TLS library today.
Content-addressed evidence store Planned Designed. This is the centre of the product and none of it is built.
Scan store, list and show Planned Designed against SQLite. Today results are held in memory or in a Redis instance that is being removed.
Diff between scans Planned Designed. Depends on the store and on deterministic ordering.
Rate limiting, concurrency ceilings, cancellation Planned Designed. The current engine has none of the three, which is its most dangerous property.
Host discovery Planned Designed. Every listed host is currently assumed to be up.
Command interface: scan, list, show, diff, report, ui backend/main.go Planned Designed as verbs. Today the binary takes positional arguments and switches to server mode on a raw string match.
Local viewer, cortex ui frontend/src/app/services/scan.service.ts Planned Designed, and previewed on this page from a fixture. The interface in the repository today is a mock that never contacts the backend.
Single binary with embedded fingerprints Planned Designed. The binary currently refuses to start without an external data file that is not shipped.
Released, signed binaries Planned None exist. No installation path is published anywhere.
Reference documentation and changelog Planned Neither exists. The README describes a repository layout that has already changed.
Automated tests Planned There are none in the repository. Zero, of any kind.

No performance figure appears anywhere on this site, because no measurement of any kind exists yet. When a benchmark harness is published, the numbers will arrive with the hardware, the settings and the commit that produced them, and with the cases where nmap is faster.