Releases: 0typos/scanr
Releases · 0typos/scanr
Release list
v1.0.0-rc.7
Changed
run's stderr is grouped into bold-cyanOverview/Warnings/Results/Summary
sections in the samekey valuecolumns asplan: scan, transport with its fidelity
coloured by trust, scope, timing, scan id + seed; warnings in their own block; a dim
column header underResultson a terminal; the closing counts asresult/states
(each state in its colour) /probed/record. Warnings keep their wording.
Added
docs/tutorial/demos/: an asciinema cast per tutorial section, linked from each use case;demos/recordre-records them against the lab.
v1.0.0-rc.6
Added
- Release binaries for many more architectures, cross-compiled with
cargo-zigbuild
(no VMs — scanr is pure Rust with no C deps): static-muslaarch64,armv7,i686,
riscv64gcandpowerpc64le, and glibcaarch64, alongside the existing
x86_64 gnu+musl.scripts/build-all.shbuilds the whole set locally. Linux x86_64
stays the tested platform; the rest are built and smoke-run under emulation,
best-effort (docs/stability.md).
Changed
plangroups its rows into coloured sections — Overview, Transport, Scope, Timing,
Probing, Output, Projection, Host — for fast visual inspection. Section headers are
bold cyan;fidelityis coloured by trust (green full, yellow open_only, red unknown);
an armed TLS probe and the timeout-bound projection are highlighted; provenance stays
dim. The two projections are labelledrate-boundandtimeout-bound. Colour follows
the usual rules (a terminal, no--no-color/NO_COLOR); piped output is unchanged
plain text.planrendering is not a promised surface.- The tutorial lab is a single installable
uvscript,docs/tutorial/scanr-lab
(PEP 723,typer), replacing the bashdocs/tutorial/lab.upprints an aligned
summary of the services and proxies it owns (no longer echoinglab.py's banner with
its misleading "Ctrl-C to stop");checkshows a liveup/downper port including
the tunnel;downalso sweeps anssh -Dorphaned by a stale pidfile.install/
uninstallsymlink it intouv tool dir --binsoscanr-labruns from anywhere.
lab.pystays plain Python —tests/tutorial.rsruns it directly, so the test path
and CI are unchanged.
v1.0.0-rc.5
Changed
- Record filenames are
scan-<name>-<UTC time>-<id>.jsonl.gz, e.g.
scan-internal_web-2026_08_27T16_45_46Z-fafcc832.jsonl.gz
(scan-adhoc-…for an ad-hoc run). Wasscan-<epoch-ms>-<id>. The scan name (from
config;adhocwhen none) makes a directory of records self-describing, and the epoch
is now a readableYYYY_MM_DDThh_mm_ssZUTC stamp that still sorts chronologically.
-is the sole field separator and appears in no field: a hyphen in the scan name is
slugified to_(internal-web→internal_web) and the stamp splits date from time
withT, sobasename.split('-')yields exactly[scan, name, stamp, id]. The
scan-prefix stays, soscan-*globs andfind_recordare unaffected; the record's
ownscan_name/scan_id/started_at_epoch_msremain the authority. Filenames are not
a promised surface. RELEASING.mdrecords the release cadence: batch small non-surface changes into
[Unreleased]rather than cutting a release candidate for each; tag when a promised
surface moves or a batch is worth a build.
v1.0.0-rc.4
Changed
plan'sdnsrow names the effective mode's resolver, not just the mode:
auto -> local (names resolved on this host),transport (names handed to the proxy to resolve),disabled (hostname targets rejected). A pinned mode shows only itself;
autoshows both the request and what it resolved to, so it never leaves the reader
guessing which resolver a scan used.
v1.0.0-rc.3
Added
scan_config.tlsnow records the hello's offer, not just the versions:
offered_ciphers(suite names, the 1.3 suite first),offered_alpn,offered_groups
andoffered_sigalgs. The offer is fixed for every probe, so it is recorded once in
the config event; a record now states both what was offered and what each server
chose, without cross-referencingdocs/security.md. With--tls-versions,
version_hellosbecomes one object per survey version (ssl2,ssl3,1.0,1.1,
1.2) carrying that hello'ssent_bytesand the era-appropriateciphersit offers,
in place of the bare sizes.
v1.0.0-rc.2
Added
docs/tutorial.md, a learn-by-using guide with real output, anddocs/tutorial/, its
lab:./lab up|tunnel|check|downbrings up three loopback services (lab.py,
standard library), squid/tinyproxy/3proxy/dante in rootless podman, and anssh -D
through a throwawaysshd.tests/tutorial.rsparses everyscanrcommand in the
document against the CLI and re-runs its use cases — direct ones against the lab,
proxied ones against the fixtures — so the guide cannot rot.
Fixed
output resultsshows the banner and the TLS summary as a trailing column of its
table, exactly asrunprinted them live; replaying a record no longer hides the
evidence it was kept for.--full, onrunand onoutput results, shows banners
untruncated (the default cuts at 48 characters; the printable-ASCII filter applies
either way).- The TLS probe reads the leaf certificate.
tls.certcarries subject, issuer,
alternative names, validity at probe time (valid,expired,not_yet_valid),
whether issuer equals subject (self_signed) and the key type; result lines and
output resultssaycn=host self-signed expired— for records written before this
too, from the DER they already hold. Read, never verified; bounded and fuzzed
(x509_leaf). The leaf also carriesserial,sig_algandversion; a SHA-1 or
MD5 signature is flaggedsha1-signed/md5-signedon the line. - The TLS flight is read to ServerHelloDone rather than stopping at the leaf, so the
record gainschain(the certificates after the leaf, hashed and read, up to 8), the
ECDHEkx_groupandsig_schemefrom ServerKeyExchange,compression, and the
ServerHello'sserver_extensionswithsecure_renegotiation,extended_master_secret
andsession_ticketderived from it.negotiatedsaysssl3for an SSLv3 answer. - The TLS probe reads TLS 1.3 servers. The hello (now 218 bytes) offers 1.3 and 1.2;
when a server takes 1.3 the probe finishes the key exchange with a published X25519
key and decrypts the flight — certificate, chain, ALPN, signature scheme — instead of
recording aprotocol_versionalert.offeredsays"1.3,1.2"; a HelloRetryRequest
is recorded ashello_retry_requestand not pursued. X25519, HKDF and AES-128-GCM are
hand-rolled insrc/crypto.rs, each held to an RFC or NIST vector; nothing is sent
after the hello. --tls-versions(tls_versions = true, needstls): after the hello, ask SSLv2,
SSLv3, TLS 1.0, 1.1 and — when the server took 1.3 — 1.2 for themselves, each on its
own connection with a hello of its era. The record'stls.versionssays which are
accepted,legacy_onlywhen the newest is older than 1.2, andadvicenaming a client
that can still reach it; the result line endsversions:ssl3..1.3or
legacy-only:tls1.0. An SSLv2-only server's certificate is taken from its
SERVER-HELLO. Up to five more connections per silent open port;scan_config.tls
lists the hellos' sizes anddocs/security.mdtheir bytes.- SNI travels on the direct path when the target was given as a name: a locally resolved
address now keeps its hostname, so a virtual-hosting server answers with its certificate
instead of an alert. A real 443 answeredinternal_errorto the nameless hello. output results --format jsonnow carriesbanner,banner_hex,banner_bytesand
tlswhen the record has them. The reader built for handing results to other tools
dropped exactly the evidence those tools want;eventsalways had it.
Added
scanr planprojects the timeout-bound duration beside the rate-bound one:~3d21h17m if every probe times out (5s x 2 attempts / 512 in flight). The rate cap never binds on a
network of silent ports; this is the number that does.
Changed
- Direct scans are 2.5–4× faster. Workers hand results to the collector in batches
of up to 64 (anopenor a pressure event still flushes at once), and the hot path no
longer allocates per probe: target names are formatted once, reasons are borrowed,
attempt states live inline, the TLS observation is boxed. Measured on a 64-core box,
loopback/24, every port refused: 256k probes 1.54 s → 0.40 s at the default
concurrency (166k → 640k probes/s); 2.56M probes 14.1 s → 4.3 s. Throughput no longer
falls with concurrency (512 was 40% slower than 64; now within 5%). Record contents,
order and every reader output are unchanged — the compat corpus passes as pinned.
v1.0.0-rc.1
Added
docs/stability.md: what 1.x promises (record, CLI, config), what it does not, and
the deprecation rule.tests/compat/: eleven scenario records — direct (spans and rows), SOCKS5 full and
open-only, HTTP CONNECT, a mixed chain, a pool, banner + TLS, interrupted and resumed,
and a schema-1 record written by the 0.2.2 binary — with the exact output of every
reader pinned, plus theconfig inittemplate of every release, which must still
validate and plan.tests/compat.rsholds the build to all of it.docs/evidence.mdmaps every claim in the docs to the test, corpus scenario or
measured decision behind it;tests/evidence.rsfails if a cited test disappears.- The nmap differential now covers HTTP CONNECT, and CI checks the TLS probe against
openssl s_server.
Changed
output summarizetakes--format table|json, the same flag asoutput results.
Deprecated
output summarize --json: still works, warns on stderr, removed in 2.0.
v0.5.0
Added
- The TLS ClientHello probe,
--tls/tls = true— off by default, the one active
probe scanr has. On open ports that volunteered no banner it sends a fixed 163-byte TLS
1.2 ClientHello on the same connection and records what comes back: negotiated version,
cipher, ALPN, the leaf certificate (SHA-256, and the DER when ≤ 8 KiB) and chain length,
or the alert — a 1.3-only server answersprotocol_version. Verified againstopenssl s_server.scan_config.tls.sent_bytesstates what was sent (0when off);
docs/security.mdlists the exact bytes and a test holds it to them.tls_timeout(1s)
is the profile ceiling. Fuzz targettls_reply. No new dependencies.
v0.4.0
Added
- HTTP CONNECT proxies:
type = "http"with the same keys assocks5; Basic
authentication; usable as a chain hop (mixed withsocks5in either order) and a pool
member. Measured against squid 7.6, tinyproxy 1.11.2 and 3proxy 0.9.7: none has a
status meaning "refused" (503/500/502, the same for a timeout), so an http
transport isopen_onlyby construction — non-open results areerrorcarrying the
status line,fidelity_sourceisinherent, and declaringfidelity = "full"is
refused. Fuzz targethttp_connect_replycovers the response parser.
Changed
- A chain's fidelity is its exit hop's, not its weakest hop's. Only the last CONNECT
names the destination and its reply travels back untouched; measuredfullthrough
squid → 3proxy SOCKS5.fidelity_sourcefor a chain isexit_hop(wasweakest_hop);
chainhops[]and poolmembers[]entries now carrytype. transport testprintsstatus NNNfor HTTP replies besidereply 0xNNfor SOCKS5.
Fixed
- A chain or pool resolved hostnames locally and took the
directdefault profile.
supports_remote_dnswas true only for a singlesocks5, sodns = "auto"through a
chain leaked DNS from the host and the scan ran with no rate limit. Every proxied kind
now resolves through the transport; a pool does when every member does. - Chains and pools with
unknownoropen_onlyfidelity now get thefidelity_unknown
/fidelity_open_onlyplan warnings; only a singlesocks5did.
v0.3.0
Changed
schema_versionis2:probe_spanranges are counter indices, not matrix positions, so
collapse no longer decays with scan duration (10,023 → 595 ranges, 53,765 → 4,893 B on a
rate-limited 20,001-probe scan). Expansion needs the recorded seed. Writes 2, reads 1 and 2.docs/output-schema.mdstates which enumerations are closed (state,source) and which
are open (transport.type, fidelity fields, warning and error codes); a test pins the closed sets.- Docs compacted;
docs/design/decisions.mdis a terse register;ROADMAP.mdadded with
the 1.0 policy and plan.
Security
- The record is created mode 0600, including the
.partialfile. password_filemode is checked on the descriptor it is read from.- All 31 GitHub Actions references pinned to commit SHAs;
contents: writescoped to the
publish job; CI declarescontents: read. unsafe_codedenied crate-wide; each of the five blocks carries#[allow]and a safety
comment;docs/security.mdlists them and a test checks both directions.- Threat model covers chains: every hop sees the credentials of every hop after it.
build.rsno longer stamps an enclosing repository's commit when the tree has no.git.deny.tomlmultiple-versions = "deny", with the one known duplicate listed.
Fixed
output verifyexits 2 for a bad record and 1 for an unreadable file.output results --hosts <name>says on stderr when a name cannot match an address record.config initdocuments all keys (banner,compress,spans,services_file,
use_etc_services,banner_bytes,banner_timeout,hops,members), seven profiles,
and four transport types; drift guards are exhaustive destructurings.transport listno longer printsdirecttwice when a config redefines it.docs/cli.mdguards every subcommand's flags, not onlyrun's.output resultstakes--format json, not--json(docs corrected).RawDefaults::bannerdocumented as on by default.- Usage errors exit 1, not 2;
--helpand--versionexit 0. - The record's
exit_codereflects writer failures. output verifyaccepts every schema version the build can read.- A version-2 record with a missing or unparseable seed is reported, not expanded wrongly.