Repository navigation
Releases: offsetsecurity/cipherhorizon
Release list
CipherHorizon v0.4.0
Security
- Built with Go 1.27.3 (GO-2026-6809).
crypto/x509now distrusts compromised intermediate certificates, and the
trust check uses it.
Added
-
Classical strength for every detected algorithm (PRD-010,
ENH-001). Strengths are computed with NIST's own methods, and the tests
reproduce NIST's published tables:- RSA and finite-field DH by the General Number Field Sieve estimate (FIPS 140-3 IG D.B, SP 800-56B Rev. 2
Appendix D); - elliptic curves at half the order (SP 800-186);
- ciphers and hashes by SP 800-57 Part 1 Tables 2 and 3;
- post-quantum algorithms by their NIST category.
Where it shows:
- TLS report and PDF: each cipher suite's word now comes from the server and is as strong as its weakest
part (cipher, key exchange, certificate key, MAC), with its bits and that part named. Groups and certificate
keys show their bits. - API: the evaluation (
eval/9) carriessuiteStrengthandkeyStrength; stored scans are re-evaluated
on start. - CLI:
tls scan --detailsprints each suite's and group's strength. - Inventory: a Classical strength column, and
classicalStrengthon every algorithm and key row of
/api/v1/inventory. - SSH reports (page and PDF): a strength for every key exchange, host key (by the presented key's size),
cipher and MAC. - CBOM:
classicalSecurityLevelon every algorithm with a strength (1.6 and 1.7).
- RSA and finite-field DH by the General Number Field Sieve estimate (FIPS 140-3 IG D.B, SP 800-56B Rev. 2
-
Every algorithm by class: asymmetric, symmetric, hash (PRD-011, #20).
- A TLS report's Configuration ends with Every algorithm, by class: one lane per class, each algorithm with its
role, a bits bar, its post-quantum status and the suites or certificates that use it, the weakest ringed. - Every suite row shows its parts (cipher, key exchange, signature, MAC, key-derivation hash) as cells with their
bits, the weakest ringed. - The Inventory filters algorithms by class (
?class=, a segmented control), and its Kind says the class. - The report's CBOM tab has a Classical strength column.
- The PDF and
tls scan --detailslist the algorithms by class, and the CLI lists each suite's parts. - API: the evaluation (
eval/10) carriesalgorithms, and every suite part carries itsclass.
- A TLS report's Configuration ends with Every algorithm, by class: one lane per class, each algorithm with its
-
Open any certificate and read the whole X.509 document (PRD-012, #21).
- Every certificate in the chain and the CBOM tab opens its page.
- The page shows the chain it was served in, its validity, the key and the issuer's signature with their strengths
and post-quantum status, its names and uses, every extension with its criticality and decoded value, the
identifiers to copy, and the PEM (folded) to copy or download. - It is decoded from the DER the scan kept (
GET /api/v1/certificates/{sha256}/x509), so earlier scans have it
too.
-
Instruments, not paragraphs: the report, the certificate page and the Inventory are rebuilt
(PRD-013, UX-DESIGN-011).
One set of shared instruments draws every quantity the same way on every page: a 0 to 256 bits bar over the
strength bands, a strength chip, a part cell, a post-quantum glyph, a status tile, a validity bar, the hygiene
gauge and the readiness stepper. Each says in words what it shows.- Summary: four figures (readiness, hygiene, the weakest classical strength, the certificate), Fix first as
the three things to fix first, each with one chip saying when, the rest folded under a line that counts them, one bar of every check's result, and a tile per area that opens it. - Configuration is one place to check the whole setup, in a reviewer's order: protocols and cipher suites
(what a current client gets, then each version with its groups and its suites in the server's order), the
certificate chain (each card opens its certificate; a missing issuer is drawn), key exchange and signatures,
revocation, protocol features, and every algorithm by class. Every TLS version starts folded to one line that
says whether it should be on and how its suites rate. A row of chips at the top jumps to each part, and
each part ends with the issues found in it. HSTS shows the header as sent. - Inventory: algorithms By class and By classical strength (
?strength=: broken, below-112, 112, 128,
192, 256, not-rated), each bar opening its list; the list is weakest first, with a bits bar. - SSH reports (PRD-014,
UX-DESIGN-012) get the same one-stop Configuration: version and
software (what a current client gets, as a banner), key exchange (readiness, then each method as a row with its
bits bar, post-quantum glyph and baseline mark), host keys (a card per key), ciphers, MACs and compression, and
the baseline (a tile per requirement linking to its source, the changes as chips in their parts, the
sshd_configlines). The Summary has the same four-figure strip, and the baseline is line 1 of Fix first.
Checked by CRYPTO-REVIEW-002. - Scanner: a TLS scan records whether the server negotiates the heartbeat extension (RFC 6520).
- Every number and claim on these screens was checked against its source
(CRYPTO-REVIEW-001).
- Summary: four figures (readiness, hygiene, the weakest classical strength, the certificate), Fix first as
Changed
-
Breaking (API): the evaluation is now
eval/12, and stored scans are re-evaluated on start. Thebandfield
uses the new hygiene band names (below), and a section'sstatuscan beunknown(revocation, when nothing
vouches for the certificate). Integrations that match on band names or on the set of statuses must be updated. -
The TLS hygiene bands have new names: Excellent, Good, Fair, Poor, Failing (they were Strong, Sound, Fair,
Weak, Broken). Strong and Weak now mean only a suite's strength (UX-REVIEW-010, #22). The scores and the
thresholds are unchanged. Stored scans are migrated, and the API'sbandfield uses the new names. -
Each suite gains the hash that derives its keys as a part, rated in that use (SP 800-57 Table 3). With NIST's
numbers it changes no word. -
Signature algorithms are rated as their hash and their key together, so
SHA256withRSAfrom an imported CBOM
now reads 128 bits instead of not rated. -
The report's Summary:
- the line under N things to fix names the first one;
- the Cipher suites row names the weakest suite and its bits;
- the Overview's TLS endpoints tile keeps to its count and the exposed figure.
-
The Inventory explains every not rated strength on the row.
-
Suite strength words follow the weakest-part rule, so some suites read differently:
- a suite authenticated by an RSA-2048 certificate (112 bits) is now Acceptable, not Strong;
- a suite with a 1024-bit DH prime is Weak.
The words are defined in docs/methodology.md and explained in Help.
Fixed
- Reports on real servers say the right thing (PRD-015,
CRYPTO-REVIEW-003; eval/12, stored scans are re-evaluated on start):- a server whose only suites Go's TLS client will not offer (3DES, RC4, NULL, small DH) had its no-SNI fallback
certificate reported as its own; the served chain is now read from the scanner's raw TLS 1.2 handshakes, which
also finds an RSA certificate a server keeps for RSA-only clients. Scan such servers again; - a leaf signed straight by an unknown root is no longer called a missing intermediate;
- a chain under Superfish, eDellRoot or DSDTestProvider is The certificate chains to a root whose private key is
public (critical); - revocation reads Status unknown, not Good, unless a stapled OCSP response vouches for the certificate;
- an untrusted root is described as a private CA or a public root since removed, not only as a private one;
- a composite DH modulus is critical, and a DH prime that is not a safe prime is low;
- at equal bits, the weakest classical strength picks a disallowed algorithm (3DES) over an allowed one.
- a server whose only suites Go's TLS client will not offer (3DES, RC4, NULL, small DH) had its no-SNI fallback
- SSH evaluation (eval/11; stored scans are re-evaluated on start):
- a truncated MAC is as strong as its tag: hmac-sha1-96 is 96 bits, not 128 (NIST SP 800-107 Rev. 1);
- the next steps name OpenSSH releases only to a server that identifies as OpenSSH;
- a baseline rule with nothing to judge (the RSA size rule on a server with no RSA key) says it does not apply
instead of Met.
- Single-DES suites (56-bit key) are Insecure, not Weak (#18).
- The TLS hygiene figure on a report's Summary opened About this report. It now opens how the score was worked
out, on Weaknesses. - A running scan's connection count could go backwards. Concurrent probes reported counts out of order, and
progress writes could land out of order. The count now only moves forward, and writes are serialised. This was
found by a CI run of the workflow test.
Verify what you downloaded
sha256sum --check --ignore-missing checksums.txt
cosign verify-blob checksums.txt --bundle checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github.com/offsetsecurity/cipherhorizon/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe inventory scripts a server of this version hands out are attached too, with
inventory-scripts.sha256; cipherhorizon scripts list prints the same hashes offline.
Full instructions, including the container image and build provenance: docs/engineering/DISTRIBUTION.md.
CipherHorizon v0.3.0
Changed
- The remote TLS CBOM says how it knows each fact, and keeps every identifier it read
(AUDIT-TLS-CBOM-001). All of it is additive: new components,
cipherhorizon:*properties, and CycloneDX 1.7 fields the schema defines. See docs/cbom-model.md.-
Evidence states. Every component has
cipherhorizon:evidence:observed: a ServerHello, or a certificate and what it states;accepted: a group or signature scheme a single-item probe was answered for;derived: what an accepted suite's IANA entry names;negotiated: used by the one handshake a current client completed.
A negotiated component also says which version (
negotiated-in). -
Coverage. The document's metadata states, per scan stage, whether it was done, not tested, not applicable,
not reached, cut by the budget or inconclusive, and whether the scan was complete. An absent component can now
be told apart from an untested one. -
No identifier is dropped.
- A certificate signature or key algorithm the catalogue does not know is kept by the OID read from the
certificate (identifier-status = unrecognised), with no family guessed. - Every accepted group is listed with its IANA code point, including brainpool, binary and GOST curves.
- The TLS 1.3 signature schemes are also listed with their code points.
- A certificate signature or key algorithm the catalogue does not know is kept by the OID read from the
-
RSASSA-PSS parameters are decoded per certificate: hash, mask generation, salt length and trailer field, with
RFC 8017 defaults marked. -
Suites reference their MAC or hash: HMAC-SHA-1, HMAC-SHA-256, the new HMAC-SHA-384 (1.2.840.113549.2.10),
HMAC-MD5, or the SHA-256/SHA-384 of an AEAD suite's PRF or HKDF. -
Certificates carry every extension.
- The 1.7 common names: subjectAlternativeName, authorityInformationAccess, crlDistributionPoints,
authorityKeyIdentifier, subjectKeyIdentifier, signedCertificateTimestamp. - Every other extension as a 1.7 custom extension named by its OID: name constraints and the TLS Feature
decoded, unknown ones markednot decoded, critical flagged.
- The 1.7 common names: subjectAlternativeName, authorityInformationAccess, crlDistributionPoints,
-
The served chain is linked:
issued-by, only to a certificate the server sent. Each certificate says which
handshakes returned it (seen-in,no-sni). -
Public keys carry their SPKI SHA-256 (the 1.7
fingerprint) and RSA exponent.
-
tls-report/2gains optional certificate fields:signatureParams,authorityKeyId,subjectKeyIdand
extensions. Older reports read as before.
Verify what you downloaded
sha256sum --check --ignore-missing checksums.txt
cosign verify-blob checksums.txt --bundle checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github.com/offsetsecurity/cipherhorizon/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe inventory scripts a server of this version hands out are attached too, with
inventory-scripts.sha256; cipherhorizon scripts list prints the same hashes offline.
Full instructions, including the container image and build provenance: docs/engineering/DISTRIBUTION.md.
CipherHorizon v0.2.0
Added
- Charts you can act on. Every chart mark that counts something opens it, already filtered
(PRD-009, UX-DESIGN-008):- Overview:
- a readiness level opens the TLS endpoints or SSH servers at that level (both, as two links, when both
protocols are there); - a severity chip opens the findings of that severity;
- "N exposed to harvest-now-decrypt-later" opens that protocol's exposed endpoints.
- a readiness level opens the TLS endpoints or SSH servers at that level (both, as two links, when both
- Inventory:
- each ring segment and legend row opens the algorithms with that post-quantum status;
- each By purpose segment opens that purpose and status;
- a Replace first, protocol or software row opens exactly that item;
- each certificate-expiry segment opens the certificates in that window.
- A report's history: each point opens the scan it stands for.
- The same behaviour for every mark: a tooltip on hover and keyboard focus, the other marks dimmed, Tab and
Enter, a name that says what opens.
- Overview:
- Filters in the address. The TLS and SSH lists (
level,exposed), Findings (severity,status,kind),
Certificates (expires,key,sig,origin) and the inventory lists (q,name,quantum,purpose,
source) read their filters from the URL and write changes back. A filtered list can be bookmarked, and Back
returns to the chart. A filter the page has no control for shows as a removable chip.GET /api/v1/inventorygainsquantum(vulnerable, safe, unknown),purposeandname(exact).vulnerable=1
still works.
- Quiet motion. Everything is listed in DESIGN.md §9, and all of it is off under reduced motion:
- rings sweep to their share;
- inventory bars grow;
- history points arrive after their line;
- figures count to their value once;
- a list cross-fades when its filter changes;
- clickable cells show an accent edge;
- a pressed button settles 1px.
serve --port, andCIPHERHORIZON_PORT/CIPHERHORIZON_ADDR.- The port can be changed without retyping the host, and
--addrmay be a host alone (--addr 0.0.0.0 --port 9000).
--port 0takes any free port and prints it. - A port already in use now says so and suggests the next one, instead of a raw socket error.
healthcheckfollows the same variables, so the container image, compose file and Quadlet unit change port
with one variable. See Choosing the port.
- The port can be changed without retyping the host, and
Changed
- The CBOM lists what it used to leave out (CBOM-REVIEW-002).
Consumers keyed on component counts will see more components:- Legacy bulk ciphers: a suite's 3DES, RC4 (export RC4-40 included), DES and RC2 cipher is now an algorithm
asset, with its cited classical status. Before, a server accepting only such suites had none of them in its CBOM. - Named-curve key exchange: accepted groups
secp256r1,secp384r1,secp521r1andx448are now
ECDH-P256,ECDH-P384,ECDH-P521(id-ecDH with the curve) andX448(1.3.101.111), judged
quantum-vulnerable. Before, they were missing. - Certificate signatures: a certificate's signature algorithm is now exact, with its OID. For example
ecdsa-with-SHA256(1.2.840.10045.4.3.2),sha256WithRSAEncryption(1.2.840.113549.1.1.11) orRSASSA-PSS
(1.2.840.113549.1.1.10), instead of the familyECDSA(no OID) orRSA(the key's OID). - ChaCha20-Poly1305 carries its OID, 1.2.840.113549.1.9.16.3.18 (RFC 8103).
- CycloneDX 1.7 certificates fill
serialNumberandfingerprint(SHA-256). 1.6 keeps the
cipherhorizon:cert-serialproperty. - A static-RSA suite lists RSA once, not twice.
- Legacy bulk ciphers: a suite's 3DES, RC4 (export RC4-40 included), DES and RC2 cipher is now an algorithm
- The container image sets
CIPHERHORIZON_PORT=8080and runsserve --addr 0.0.0.0. Its health check no longer
names a port. The systemd unit passes--addr 127.0.0.1, soCIPHERHORIZON_OPTS="--port 9000"works.
Fixed
- Two keys with one name were one inventory row with a random status. Keys are named by their certificate's
subject, so an ECDSA and an ML-DSA certificate for the same host merged into one row. That row was
quantum-vulnerable or quantum-safe depending on the order the merge read them, so the Inventory's counts could
change between two loads of the same data.- A key's algorithm is now part of its identity: such keys are two rows, each with its own status, and the list
names the algorithm. - Any disagreement between sources resolves to the worse status, whatever the order.
- A key's algorithm is now part of its identity: such keys are two rows, each with its own status, and the list
Verify what you downloaded
sha256sum --check --ignore-missing checksums.txt
cosign verify-blob checksums.txt --bundle checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github.com/offsetsecurity/cipherhorizon/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe inventory scripts a server of this version hands out are attached too, with
inventory-scripts.sha256; cipherhorizon scripts list prints the same hashes offline.
Full instructions, including the container image and build provenance: docs/engineering/DISTRIBUTION.md.
CipherHorizon v0.1.1
Fixed
- Release notes. A GitHub release now carries its CHANGELOG section. The release configuration disabled
GoReleaser's changelog, which also dropped the notes file, so v0.1.0 was published with only the verification
footer (its notes were then set by hand). - The container smoke test passes against a published image on Podman. It declares the health check in exec
form (the image has no shell), says to passCERT=andKEY=whenopensslis missing, and no longer stops
at random underpipefail.
Changed
- Fewer entries per image in the container registry (5 instead of 9): no BuildKit SBOM and provenance
attestations, and GitHub's provenance attestation is no longer copied into GHCR. Provenance is still verified
withgh attestation verify; SBOMs are release assets, as before. - The release verifies the image signature before it publishes anything, with the command users are given.
- No website for now: the GitHub Pages workflow is removed;
site/is kept. - Dependencies:
modernc.org/sqlite1.60.1; GitHub Actionssetup-node7.1.0,upload-artifact7.0.2,
CodeQL 4.38.3,sbom-action0.24.3.
Verify what you downloaded
sha256sum --check --ignore-missing checksums.txt
cosign verify-blob checksums.txt --bundle checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github.com/offsetsecurity/cipherhorizon/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe inventory scripts a server of this version hands out are attached too, with
inventory-scripts.sha256; cipherhorizon scripts list prints the same hashes offline.
Full instructions, including the container image and build provenance: docs/engineering/DISTRIBUTION.md.
CipherHorizon v0.1.0
Post-quantum readiness for the protocols your organisation runs on: TLS, SSH and, next, VPN. A deep scan of
each, a readiness level from L0 to L4, and one CycloneDX CBOM, self-hosted. The first public release, a 0.x
pre-release.
Highlights
-
TLS endpoints. CipherHorizon has its own TLS implementation for scanning. It checks:
- every protocol from SSLv2 to TLS 1.3;
- every cipher suite and key-exchange group (hybrids such as
X25519MLKEM768included); - the TLS 1.3 signature schemes (ML-DSA included);
- the certificate chain against the Mozilla and Chrome root stores, or your own CA;
- OCSP stapling, Certificate Transparency and HSTS.
STARTTLS works for SMTP, IMAP, POP3, FTP, XMPP, LDAP and PostgreSQL. There are 55 cited checks and a TLS hygiene
score with its arithmetic shown. -
SSH servers.
- Reads what a server offers and negotiates as OpenSSH 10.6 would, the ML-KEM hybrid included.
- Fetches and verifies every host key: Ed25519, ECDSA, RSA, ML-DSA and the composite
ssh-mldsa44-ed25519. - Judges the server against the cited CipherHorizon SSH baseline 2026, and writes the
sshd_configlines
that would make it meet the baseline. - Never logs in.
- Checked against real OpenSSH 9.2 and 10.0.
-
Post-quantum readiness. L0 to L4 for every TLS endpoint and SSH server, each protocol by its own stated rules,
with harvest-now-decrypt-later exposure. -
Machines and inventory.
- Two readable inventory scripts (PowerShell, shell) list a machine's cryptographic software and certificates.
- CBOMs from other tools can be imported.
- Everything merges into one CycloneDX 1.7 (or 1.6) CBOM.
-
The web app.
- A page per protocol, Findings across everything with triage, and Systems ranked for migration.
- PDF reports, schedules and history.
- Sign-in when you want it: administrator, analyst and viewer roles, and an exportable activity log.
-
Command line and CI.
tls scanandssh scan, with--fail-ongates (exit 3), JSON and CBOM output, and a
REST API described at/api/v1/openapi.json.
Install
| Platform | How |
|---|---|
| Container | ghcr.io/offsetsecurity/cipherhorizon:0.1.0 (amd64, arm64), with docker-compose.yml or the Podman Quadlet unit in deploy/ |
| Debian, Ubuntu | sudo apt install ./cipherhorizon_0.1.0_amd64.deb |
| RHEL, Fedora | sudo dnf install ./cipherhorizon-0.1.0-1.x86_64.rpm |
| Windows | Unzip cipherhorizon_0.1.0_windows_amd64.zip, then run cipherhorizon.exe service install as administrator |
| Any OS | Run cipherhorizon serve from the archive |
Then open http://127.0.0.1:8080.
More: INSTALL.md.
Known limitations
- Machines. The host collector has run on Windows (standard user) and in fixture tests for Linux, but not on a
live Linux host or with administrator rights. macOS machines are not inventoried. - TLS. Validated against test servers and, for trust, against OpenSSL. A matrix of real TLS stacks (nginx,
Apache, Postfix) is not yet recorded. There are no active vulnerability probes, by design. - SSH. Servers that offer only
diffie-hellman-group18-sha512, group exchange or sntrup761 are judged on their
algorithm lists, and their host keys are not fetched. BSI TR-02102-4 is planned as a second baseline. - Storage. SQLite only.
Everything in this release, in detail: CHANGELOG.md.
Verify what you downloaded
sha256sum --check --ignore-missing checksums.txt
cosign verify-blob checksums.txt --bundle checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github.com/offsetsecurity/cipherhorizon/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
cosign verify ghcr.io/offsetsecurity/cipherhorizon:0.1.0 \
--certificate-identity-regexp '^https://github.com/offsetsecurity/cipherhorizon/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comThe inventory scripts a server of this version hands out are attached too, with inventory-scripts.sha256.
cipherhorizon scripts list prints the same hashes offline. Build provenance: gh attestation verify <file> --repo offsetsecurity/cipherhorizon. Full instructions:
DISTRIBUTION.md.