Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas

## [Unreleased]

## [0.4.0] - 2026-10-22

### Security

- Built with Go 1.27.3 (GO-2026-6809). `crypto/x509` now distrusts compromised intermediate certificates, and the
trust check uses it.

### Added

- **Classical strength for every detected algorithm** ([PRD-010](docs/product/PRD-010-classical-strength.md),
Expand Down Expand Up @@ -56,12 +63,12 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas
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
one numbered line per problem, one bar of every check's result, and a tile per area that opens it.
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. Each version folds to one line when all its
suites are Strong, and opens when it needs a look. A row of chips at the top jumps to each part, and
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.
Expand All @@ -78,6 +85,9 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas

### Changed

- **Breaking (API):** the evaluation is now `eval/12`, and stored scans are re-evaluated on start. The `band` field
uses the new hygiene band names (below), and a section's `status` can be `unknown` (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's `band` field uses the new names.
Expand Down Expand Up @@ -111,10 +121,6 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas
- 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.
- **Fix first** shows the three things to fix first, each with one chip saying when, and folds the rest under one
line that counts them by severity.
- **Every TLS version on Configuration starts folded**, its head saying whether it should be on and how its suites
rate; version heads, suite rows and Fix first rows line up.
- **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;
Expand All @@ -123,16 +129,10 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas
- **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.
- **The browser journey retries a browser that does not start**, instead of failing the whole run.
- **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.

### Security

- Built with Go 1.27.3 (GO-2026-6809). `crypto/x509` now distrusts compromised intermediate certificates, and the
trust check uses it.

## [0.3.0] - 2026-10-17

### Changed
Expand Down Expand Up @@ -874,7 +874,8 @@ the CBOM/report/eval schema versions, the database) can change in a minor releas
- **Storage:** SQLite only. Up to 50 snapshots of up to 32 MiB are kept per machine; there is no overall storage
budget yet.

[Unreleased]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.3.0...HEAD
[Unreleased]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.1.1...v0.2.0
[0.1.1]: https://github.com/offsetsecurity/cipherhorizon/compare/v0.1.0...v0.1.1
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ rules. Systems are ranked for migration by exposure and by how long their data m

**Scanners that show their work.** The TLS scanner is its own wire implementation: every protocol from SSLv2 to
TLS 1.3, every cipher suite and key-exchange group (including `X25519MLKEM768`), the signature schemes (ML-DSA
included), the certificate chain against the bundled root stores, OCSP stapling, CT and HSTS. Its 55 checks each say
what they found or that they passed, why it matters, what to do and their sources; the TLS hygiene score (0 to 100)
shows its arithmetic. The SSH auditor reads what a server offers, negotiates as OpenSSH 10.6 would (ML-KEM hybrid
included), the certificate chain against the bundled root stores, OCSP stapling, CT and HSTS. It reads the certificate
a server really serves even when only legacy suites reach it, names a root whose private key is public, and says
*unknown* for what it cannot know (revocation, without contacting the CA) instead of guessing. Its 58 checks each
say what they found or that they passed, why it matters, what to do and their sources; the TLS hygiene score (0 to
100) shows its arithmetic. The SSH auditor reads what a server offers, negotiates as OpenSSH 10.6 would (ML-KEM hybrid
included), verifies every host key, and never logs in. It judges the server against a cited baseline and writes the
`sshd_config` lines that would make it meet it. Both are checked against real servers: OpenSSL for trust, OpenSSH
9.2 and 10.0 for SSH.
Expand All @@ -67,7 +69,7 @@ included), verifies every host key, and never logs in. It judges the server agai
installing anything; CBOMs from other tools can be imported. TLS endpoints, SSH servers and machines merge into one
CycloneDX 1.7 CBOM, with PDF reports for each endpoint and for the whole inventory, and the OMB M-23-02 items per system.

| A TLS endpoint: verdict first, then every check | STARTTLS services, the same depth |
| A TLS endpoint: where it stands, then the three things to fix first | STARTTLS services, the same depth |
|---|---|
| ![A TLS endpoint report: readiness L3, the TLS hygiene score, what to fix](docs/img/report-hybrid.png) | ![An SMTP endpoint scanned through STARTTLS](docs/img/report-starttls.png) |
| **SSH servers: readiness and the baseline, never a score** | **The `sshd_config` lines that meet the baseline** |
Expand All @@ -77,9 +79,10 @@ CycloneDX 1.7 CBOM, with PDF reports for each endpoint and for the whole invento

| | |
|---|---|
| **TLS endpoints** | Every protocol from SSLv2 to TLS 1.3, every cipher suite and key-exchange group (hybrids such as `X25519MLKEM768` included), 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 for SMTP, IMAP, POP3, FTP, XMPP, LDAP and PostgreSQL. 55 cited checks and the TLS hygiene score. |
| **TLS endpoints** | Every protocol from SSLv2 to TLS 1.3, every cipher suite and key-exchange group (hybrids such as `X25519MLKEM768` included), 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; DH parameters tested for primality. STARTTLS for SMTP, IMAP, POP3, FTP, XMPP, LDAP and PostgreSQL. 58 cited checks and the TLS hygiene score. |
| **SSH servers** | Identification and algorithm lists, negotiation as OpenSSH 10.6 would (the ML-KEM hybrid included), every host key fetched and verified (Ed25519, ECDSA, RSA, ML-DSA and the composite `ssh-mldsa44-ed25519`), 18 cited checks, and the CipherHorizon SSH baseline 2026 with the `sshd_config` lines to meet it. It never logs in. |
| **Classical strength** | Every algorithm, key and cipher suite in bits, computed with NIST's own methods (the GNFS estimate for RSA and DH, half the order for curves, SP 800-57 for ciphers and hashes). A suite is as strong as its weakest part and names it, on the report, the PDF, the CLI, the Inventory, SSH reports and the CBOM (`classicalSecurityLevel`). Every algorithm is classed as asymmetric, symmetric or hash; each report shows every suite part by part and every algorithm by class, drawn as bits bars on one scale. |
| **One page to check a setup** | Each report has a Summary (readiness, hygiene, the weakest strength and the certificate as figures, then the three things to fix first) and a Configuration that reads in a reviewer's order: every TLS version folded to one line that says whether it should be on and how its suites rate, opening to its suites part by part; the certificate chain; key exchange and signatures; revocation; protocol features; every algorithm by class. SSH reports read the same way, ending with the baseline and its `sshd_config` lines. |
| **Certificates** | Open any certificate in a chain to read the whole X.509 document: its chain, validity, key and signature with their strengths, names and uses, every extension decoded, fingerprints, and the PEM to copy or download. |
| **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** | Two readable inventory scripts (PowerShell, shell) list a machine's cryptographic software and certificates; nothing is installed and no key material is read. |
Expand Down
2 changes: 1 addition & 1 deletion deploy/cipherhorizon.container
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ After=network-online.target

[Container]
ContainerName=cipherhorizon
Image=ghcr.io/offsetsecurity/cipherhorizon:0.3.0
Image=ghcr.io/offsetsecurity/cipherhorizon:0.4.0
PublishPort=127.0.0.1:8080:8080
Volume=cipherhorizon-data:/data
# Another port: set it here and in PublishPort (the health check follows it), e.g.
Expand Down
6 changes: 3 additions & 3 deletions docs/engineering/DISTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ tar -xzf cipherhorizon_0.1.0_linux_amd64.tar.gz
docker run -d --name cipherhorizon -p 8080:8080 \
-e CIPHERHORIZON_TOKEN="$(openssl rand -hex 24)" \
-v cipherhorizon-data:/data \
ghcr.io/offsetsecurity/cipherhorizon:0.3.0
ghcr.io/offsetsecurity/cipherhorizon:0.4.0

# container, built locally (docker-compose.yml at the repository root)
echo "CIPHERHORIZON_TOKEN=$(openssl rand -hex 24)" > .env
Expand Down Expand Up @@ -136,10 +136,10 @@ gh attestation verify cipherhorizon_0.1.0_linux_amd64.tar.gz --repo offsetsecuri

**Container image:**
```sh
cosign verify ghcr.io/offsetsecurity/cipherhorizon:0.3.0 \
cosign verify ghcr.io/offsetsecurity/cipherhorizon:0.4.0 \
--certificate-identity-regexp '^https://github.com/offsetsecurity/cipherhorizon/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
gh attestation verify oci://ghcr.io/offsetsecurity/cipherhorizon:0.3.0 --repo offsetsecurity/cipherhorizon
gh attestation verify oci://ghcr.io/offsetsecurity/cipherhorizon:0.4.0 --repo offsetsecurity/cipherhorizon
```

Air-gapped users: the archives are self-contained, so download on a connected machine, verify there, and move
Expand Down
2 changes: 1 addition & 1 deletion docs/engineering/INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ header lists the commands). The log goes to `~/Library/Logs/CipherHorizon/cipher
```sh
docker run -d --name cipherhorizon --restart unless-stopped \
-p 127.0.0.1:8080:8080 -v cipherhorizon-data:/data \
ghcr.io/offsetsecurity/cipherhorizon:0.3.0
ghcr.io/offsetsecurity/cipherhorizon:0.4.0
```

Or, from a checkout, `docker compose up -d` ([docker-compose.yml](../../docker-compose.yml): read-only
Expand Down
Binary file modified docs/img/endpoints.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/inventory.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/overview-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/report-hybrid-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/report-hybrid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/report-legacy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/report-ssh-baseline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/report-ssh.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/report-starttls.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/img/ssh-servers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 9 additions & 7 deletions docs/methodology.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,17 +206,19 @@ Every algorithm belongs to one class, from its CycloneDX primitive, and has a ro
A TLS evaluation lists every algorithm the endpoint uses (`algorithms` in the API): its class, role, strength and
post-quantum status, the suites or certificates that use it, and the weakest of each class. A hash is listed once per
use, because its strength depends on the use: SHA-256 deriving keys is 256 bits, signing it is 128. The report's
Configuration opens with this list (*Algorithms in use*); the PDF, `tls scan --details` and the Inventory's *Class*
filter show the same classes.
Configuration ends with this list (*Every algorithm, by class*: a lane per class, a bits bar per algorithm, the weakest
ringed); the PDF, `tls scan --details` and the Inventory's *Class* filter show the same classes.

### One certificate, decoded (PRD-012)

Every certificate in a report's *Chain as served*, its quick look and the CBOM tab opens the certificate's page,
Every certificate in a report's certificate chain and the CBOM tab opens the certificate's page,
`#/certificates/{sha256}`. The page is decoded from the DER the scan kept (`GET /api/v1/certificates/{sha256}/x509`), so
an older scan gains it without a rescan. In order, it shows: identity, names, the public key (with its classical
strength and post-quantum status), the signature (the weaker of its hash and the issuer's key), every extension with
its criticality and decoded value (one this program does not decode is shown as OID and hex), the fingerprints (SHA-1
as an identifier only) and the PEM, to copy or download. A certificate known only from a machine inventory or an
an older scan gains it without a rescan. In order, it shows: the chain it was served in, its validity (days
left on a lifetime bar), the public key and the issuer's signature as bits bars (the signature is the weaker of its hash
and the issuer's key) with their post-quantum status, the names it is valid for and what it may be used for (an
elliptic-curve key that claims key encipherment is marked, RFC 8813), every extension with its criticality and decoded
value (one this program does not decode is shown as OID and hex), the identifiers to copy (SHA-256, the SPKI
fingerprint, the serial, and SHA-1 as an identifier only) and the PEM, folded, to copy or download. A certificate known only from a machine inventory or an
imported CBOM keeps no DER; its page shows the fields that source gave and says so. Every value comes from the
certificate and is rendered as text.

Expand Down
7 changes: 6 additions & 1 deletion docs/ssh-scanning.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ not on the list, so they should be off. A server whose lists couldn't be read is
1. **A post-quantum hybrid key exchange is offered** (RFC 10042, RFC 9941).
2. **Strict key exchange is offered.** This is OpenSSH's countermeasure to Terrapin (CVE-2023-48795), in OpenSSH 9.6
and later.
3. **RSA host keys are at least 3072 bits** (NIST SP 800-57 Part 1: 128-bit security).
3. **RSA host keys are at least 3072 bits** (NIST SP 800-57 Part 1: 128-bit security). On a server with no RSA host
key this rule *does not apply*, rather than reading as met.

A truncated MAC is rated by its tag: `hmac-sha1-96` is 96 bits, not HMAC-SHA-1's 128 (NIST SP 800-107 Rev. 1). The
next steps and the changes name OpenSSH releases only to a server that identifies as OpenSSH; any other server is told
what to offer.

**What it leaves out, and why:**

Expand Down
Loading
Loading