NDJSON Schema Reference¶
This page describes the fields emitted in each offline NDJSON connection record.
The sniffer writes ECS-style JSON with a custom pqc.* namespace for quantum-safety and handshake details. Fields are
omitted when the related protocol data was not observed or could not be parsed.
Record Shape¶
Each line is one complete JSON object:
{
"@timestamp": "2026-05-11T18:40:13.141Z",
"event": {},
"source": {},
"destination": {},
"network": {},
"observer": {},
"pqc": {}
}
Protocol-specific objects such as tls, ssh, quic, and tcp are present only when that information is available.
Core Fields¶
| Field | Type | Description |
|---|---|---|
@timestamp |
string | Event timestamp in ISO-8601 UTC format. |
event.id |
string | Connection identifier. |
event.category |
array[string] | ECS category; currently includes network. |
event.type |
array[string] | ECS event type; currently includes connection and protocol. |
event.kind |
string | ECS event kind; currently event. |
event.dataset |
string | Dataset name; currently pqc.network. |
event.duration |
integer | Connection duration in nanoseconds when available. |
tags |
array[string] | Optional tags, typically including the customer ID. |
Conversation Fields¶
These are the fields most useful for top-talkers, port grouping, and address grouping.
| Field | Type | Description |
|---|---|---|
source.ip |
string | Source IP address. |
source.port |
integer | Source transport port. |
source.bytes |
integer | Payload bytes observed from source to destination. Transport and network headers are not included, so a packet carrying no payload adds nothing. |
source.packets |
integer | Packets observed from source to destination, over the whole connection. Every captured packet in that direction is counted once, including those carrying no payload: TCP handshake, pure acknowledgements, FIN and RST. See Emission Rules for when the record is written, which is what makes these totals final. |
destination.ip |
string | Destination IP address. |
destination.port |
integer | Destination transport port. |
destination.bytes |
integer | Payload bytes observed from destination to source, counted as source.bytes is. |
destination.packets |
integer | Packets observed from destination to source, counted as source.packets is. |
network.transport |
string | Transport protocol, such as tcp or udp. |
network.protocol |
string | Application/protocol classification, such as tls, https, http/3, quic, ssh, dtls, ikev2, mqtt, smtp, smtps, dns-over-tls, dns-over-https, or http. See How the protocol is decided — read it with pqc.app_protocol_basis, which says what the classification was based on. |
network.bytes |
integer | Sum of source and destination bytes. |
network.packets |
integer | Sum of source and destination packets. |
network.community_id |
string | Optional community ID when available. |
tcp.state |
string | TCP state when tracked. |
tcp.client_isn |
integer | Optional client initial sequence number. |
tcp.server_isn |
integer | Optional server initial sequence number. |
TLS Fields¶
These are also populated for DTLS connections (network.protocol = dtls), whose handshake
has the same structure; for QUIC connections (quic or http/3), whose handshake is
TLS 1.3 — see QUIC Fields; for SMTP sessions (smtp) upgraded with
STARTTLS, from the handshake that follows the upgrade; and for NTS-KE sessions (nts), which
are TLS 1.3 on TCP 4460. An SMTP session that stays plaintext has no tls object, and neither
does an NTS-KE session first seen after its handshake.
| Field | Type | Description |
|---|---|---|
tls.version_protocol |
string | Negotiated TLS version label: TLSv1.3, TLSv1.2, TLSv1.1, TLSv1.0, SSLv3, or Unknown. |
tls.cipher |
string | Negotiated cipher suite name when known. |
tls.client.server_name |
string | Client SNI value when present. |
tls.next_protocol |
string | Negotiated ALPN protocol when present. |
tls.client_offered_pqc_kex |
string | Comma-separated PQC KEX algorithms offered by the client when detected. |
tls.established |
boolean | Whether the TLS connection was established. |
TLS Handshake Fields¶
TLS parser details are emitted under pqc.handshake.* so quantum-safety consumers can keep crypto evidence with the
assessment.
| Field | Type | Description |
|---|---|---|
pqc.handshake.client_hello.version |
string | ClientHello version. |
pqc.handshake.client_hello.ciphersuites |
array[string] | Client-offered cipher suites. |
pqc.handshake.client_hello.compression_methods |
array[integer] | Client-offered compression methods. |
pqc.handshake.client_hello.supported_groups |
array[string] | Client-offered supported groups/key-share groups, under the names the IANA TLS Supported Groups registry gives them, such as x25519 and secp256r1. A group the registry does not name is reported under the name this build knows it by, which for a post-quantum group is the OQS provider's, such as X25519MLKEM768. Anything else falls back to its hex code point. |
pqc.handshake.client_hello.signature_algorithms |
array[string] | Client-offered signature algorithms, under the names the IANA TLS SignatureScheme registry gives them, such as ecdsa_secp256r1_sha256 and rsa_pss_rsae_sha256. A scheme the registry does not name, which so far is every post-quantum one, is reported under the name this build knows it by, such as mldsa65. Anything else falls back to its hex code point. |
pqc.handshake.client_hello.alpn_protocols |
array[string] | Client-offered ALPN protocols. |
pqc.handshake.client_hello.supported_versions |
array[string] | Client-offered TLS versions. |
pqc.handshake.server_hello.version |
string | ServerHello version. |
pqc.handshake.server_hello.selected_ciphersuite |
string | Selected cipher suite name, or hex value if unknown. |
pqc.handshake.server_hello.selected_alpn |
string | Selected ALPN protocol. |
pqc.handshake.server_hello.key_share.group |
string | Selected TLS 1.3 key-share group. |
pqc.handshake.server_hello.key_share.key_exchange_length |
integer | Selected key-share length. |
pqc.handshake.server_key_exchange.kex_type |
string | TLS 1.0-1.2 key exchange type, such as ECDHE or DHE. |
pqc.handshake.server_key_exchange.curve_name |
string | ECDHE curve name when available. |
pqc.handshake.server_key_exchange.public_key_length |
integer | ECDHE public key length. |
pqc.handshake.server_key_exchange.dh_p_length |
integer | DHE prime modulus length. |
pqc.handshake.server_key_exchange.dh_g_length |
integer | DHE generator length. |
pqc.handshake.server_key_exchange.dh_Ys_length |
integer | DHE public value length. |
pqc.handshake.key_exchange_algorithm |
string | Parsed key exchange algorithm. |
pqc.handshake.signature_algorithm |
string | Parsed signature algorithm. |
pqc.handshake.encryption_algorithm |
string | Parsed encryption algorithm. |
pqc.handshake.ciphersuite_name |
string | Parsed cipher suite name. |
pqc.handshake.client_offered_pqc_kex |
string | PQC KEX algorithms offered by the client. |
pqc.handshake.kex_quantum_safe |
string | KEX component safety: Safe, Vulnerable, or Unknown. |
pqc.handshake.sig_quantum_safe |
string | Signature component safety: Safe, Vulnerable, or Unknown. |
pqc.handshake.enc_quantum_safe |
string | Encryption component safety: Safe, Vulnerable, or Unknown. |
Changed in 2.1.0
Before 2.1.0 two offered signature algorithms were reported under OQS's names for them
rather than the registry's: 0x0403 as ecdsap256 and 0x0804 as rsa3072. The second
is the one to watch for in stored records: 0x0804 is rsa_pss_rsae_sha256 and carries
no key size at all, so a record asserting rsa3072 stated a 3072-bit key the handshake
never mentioned. Both now appear under their registered names. A query, dashboard or
saved search matching either old value will stop matching; nothing else in this field
changed, and no other field changed at all.
PQC Assessment Fields¶
| Field | Type | Description |
|---|---|---|
pqc.quantum_safety |
string | Overall classification: Secure, Vulnerable, or Unknown. Note Secure, not Safe — the per-component *_quantum_safe fields above use Safe, this one does not. Unknown/unmapped TLS cipher suites or key-share groups produce Unknown rather than being treated as vulnerable classical crypto. |
pqc.readiness_score |
integer | Readiness score from 0 to 100. |
pqc.has_pqc_kex |
boolean | Whether the key exchange the server selected is post-quantum. Hybrids count. Not whether the client offered one — that is tls.client_offered_pqc_kex. |
pqc.has_pqc_sig |
boolean | Whether the signature algorithm the server selected is post-quantum. Always false on TLS 1.3, where the certificate is encrypted — see the note below. |
pqc.pqc_kex_is_hybrid |
boolean | Whether the PQC KEX is hybrid. |
pqc.pqc_sig_is_hybrid |
boolean | Whether the PQC signature is hybrid. |
pqc.pqc_downgrade_detected |
boolean | Whether downgrade behavior was detected. |
pqc.app_protocol_basis |
string | What evidence produced network.protocol: alpn, payload, port, or none. See How the protocol is decided. |
pqc.pqc_kex_algorithm |
string | Detected PQC KEX algorithm. |
pqc.pqc_sig_algorithm |
string | Detected signature algorithm — not only post-quantum ones, despite the name. See Where the signature algorithm comes from. |
pqc.vulnerability_reason |
string | Human-readable reason for a vulnerable or unknown classification. Always present when pqc.quantum_safety is Vulnerable. |
pqc.assessment.overall_safety |
string | Detailed overall assessment. |
pqc.assessment.vulnerability_score |
integer | Vulnerability score from 0 to 100. The inverse of pqc.readiness_score. |
pqc.assessment.vulnerable_components |
array[string] | Components contributing to vulnerability. |
pqc.assessment.recommended_algorithms |
array[string] | Suggested safer algorithms. |
pqc.assessment.remediation_priority |
string | Critical, High, Medium, Low, or None — see Remediation priority. |
What the client offered and what the server chose are separate questions. The has_pqc_*
booleans and the *_quantum_safe strings describe the connection that actually happened: they
follow the server's selection, from the ServerHello key share and the certificate. What the
client was willing to use is reported separately, in tls.client_offered_pqc_kex. The
distinction is the point of the fields — a fleet whose clients already offer ML-KEM needs its
servers upgraded, which is a different job from a fleet whose clients cannot speak it at all,
and pqc.assessment.remediation_priority grades the two differently.
When the client offered a post-quantum key exchange and the server chose a classical one,
pqc.pqc_downgrade_detected is true, pqc.has_pqc_kex is false, and
pqc.handshake.kex_quantum_safe is Vulnerable. The offer is still recorded. Nothing about
the client's capability raises the assessment of a session that did not use it.
pqc.has_pqc_sig and TLS 1.3
On TLS 1.3 the certificate is encrypted, so a passive observer never sees the server's
signature algorithm. pqc.has_pqc_sig is false on every TLS 1.3 connection and
pqc.handshake.sig_quantum_safe is Vulnerable. This is a limit of passive observation
rather than a gap in parsing — the bytes are not on the wire to read. Take the field to
mean "no post-quantum certificate was observed", not "the server does not have one".
On TLS 1.2 and earlier the Certificate message is in the clear and is parsed, so a
post-quantum leaf certificate does set pqc.has_pqc_sig. That includes HSS/LMS, XMSS and
XMSS^MT, which have no TLS SignatureScheme codepoint and so can never be named by the
cipher suite. For the equivalent question on SSH, where the host key is always visible on
the wire, use ssh.has_pqc_host_key.
These fields reported the client's offer before pqc-sniffer 2.0.1.
pqc.has_pqc_kexandpqc.has_pqc_sigwere set from the ClientHello, overwriting the verdict taken from the ServerHello, so any connection whose client merely offered post-quantum algorithms was published withhas_pqc_kextrue,pqc.handshake.kex_quantum_safeSafeandtls.quantum_ready_kxtrue— including downgrades, where the same record also setpqc.pqc_downgrade_detectedand named a classical group. Because every OpenSSL 3.5 client offers ML-DSA by default,has_pqc_sigwastrueon ordinary RSA certificates. Separately, a classical TLS 1.3 connection carried nopqc.vulnerability_reasonat all. Data collected before this release overstates post-quantum adoption; counts ofhas_pqc_kexare not comparable across the upgrade boundary. |pqc.assessment.assessed_at| string | Assessment timestamp in ISO-8601 UTC format. |
Where the signature algorithm comes from¶
pqc.pqc_sig_algorithm has two sources, and which one filled it depends on the TLS version.
From the cipher suite. On any handshake, the negotiated suite names an authentication
method, and that is what the field carries: RSA from TLS_ECDHE_RSA_..., ECDSA from
TLS_ECDHE_ECDSA_.... This is all a cipher suite can say, and it describes a method rather
than the key the server actually holds.
From the server's leaf certificate. On TLS 1.2 and earlier, the certificate is sent in the clear, so the algorithm of the server's own key can be read directly out of it. This is the only way some algorithms can be seen at all: HSS/LMS, XMSS and XMSS^MT have no TLS codepoint and no cipher suite of their own — they exist only in certificates — so a server holding one of those keys negotiates an ordinary suite and would otherwise be recorded under whatever that suite's authentication field said.
The certificate value is used when it names a post-quantum algorithm, which a cipher suite could not have named, or when nothing else set the field. Otherwise the cipher-suite value stands.
Values are algorithm names where recognised — HSS-LMS, XMSS, XMSS-MT, ML-DSA-44/65/87,
SLH-DSA-*, RSA, ECDSA, Ed25519 — and a dotted-decimal OID where not. An OID in this
field is not an error; it means a certificate presented an algorithm newer than this
release's table, which is worth knowing rather than discarding.
TLS 1.3 and QUIC never contribute a certificate-derived value. Both encrypt the Certificate message, so passive monitoring cannot read it. On those connections the field reflects the cipher suite alone, and a hash-based certificate is indistinguishable from any other. This is a limit of passive observation, not a gap in parsing.
How the protocol is decided¶
network.protocol is decided from the strongest evidence the handshake produced, and
pqc.app_protocol_basis records which that was. The two should be read together: both a
proven and a guessed classification say https, and only the basis separates them.
pqc.app_protocol_basis |
Meaning |
|---|---|
alpn |
The TLS handshake negotiated an ALPN naming the protocol — h2 or http/1.1 for HTTPS, h3 (or a draft h3-NN) for HTTP/3. This is proof of what the tunnel carries. |
payload |
Bytes on the wire matched a protocol signature — a request line beginning GET, or, on QUIC, the ALPN the client's ClientHello offered. An offer is an observation but not an agreement, which is why it is not alpn. |
port |
TLS was present on a port that names the protocol by convention and no ALPN was negotiated: a port listed in connection_tracker.https_ports (https), or port 465 (smtps). This is inference, though see the note on 465 below. |
none |
No classification evidence was recorded. |
HTTPS is therefore no longer "TLS on port 443". Before this changed, that was the entire
definition, so a web service on 8443 or 9443 was reported as tls however plainly it was
HTTPS — and the negotiated ALPN, which the sniffer already parsed and publishes as
tls.next_protocol, was consulted by nothing.
Two consequences worth knowing:
- A negotiated ALPN that names a protocol other than HTTP (say
imap) suppresses the port fallback. The handshake has stated what the tunnel carries, and a port number does not override an observation. - Setting
connection_tracker.https_ports: []disables the port tier entirely, so HTTPS is reported only where an ALPN proves it. That is the right setting if the inventory is being used as evidence rather than as a survey.
To exclude inferred classifications from a query, filter on the basis:
network.protocol="https" AND pqc.app_protocol_basis="alpn"
SNI is deliberately not used to classify. A hostname names the server the client intended to reach; it does not establish the protocol inside the completed tunnel, and after the handshake the sniffer cannot read HTTP requests to confirm.
DNS-over-HTTPS is the one exception, and it is worth being exact about what it means. A
dns-over-https classification means the SNI matched a configured resolver hostname —
not that DNS queries were observed. They cannot be: a DoH query travels inside the completed
TLS tunnel, so the name the client asked for is the last thing a passive observer can read.
The classification is kept rather than flattened into https because it is strictly more
specific, and the tls.* and pqc.* fields beside it are measured from the handshake as
usual.
Because it is a list of names, a resolver that is not on the list is reported as https.
The default list covers the well-known public resolvers only, so a site running its own DoH
endpoint should add it — see
Identifying DNS-over-HTTPS resolvers.
SMTPS is kept for the same reason, decided from the port rather than from a name. Port
465 is implicit TLS for SMTP and is reserved for nothing else (RFC 8314), so TLS seen on
it is smtps and is not flattened into tls. The basis is port, because a TLS
handshake is present on a great many ports and the port is the part that discriminates.
Unlike the https_ports inference, though, this one is not configurable and not a local
convention, so it is the most certain of the port-based classifications rather than the
least.
Only 465 qualifies. A TLS handshake on 25 or 587 is a session upgraded through STARTTLS,
which keeps the smtp label from its plaintext phase and carries its tls.* fields from
the handshake that followed the upgrade. A cleartext session on 465 stays smtp too, and
has no tls object at all.
The whole pqc.assessment object is present only for connections that carried a
cryptographic handshake the sniffer analysed. Cleartext traffic still gets
pqc.quantum_safety: Unknown but no assessment, so ordinary unencrypted flows are not
counted as outstanding remediation work.
Remediation priority¶
pqc.assessment.remediation_priority answers how much work is outstanding and how soon,
which is a different question from is this quantum-safe. Two axes drive it.
Is the connection broken today, independently of quantum computers? A negotiated
version of TLS 1.1 or older, or a cipher suite rated High or Critical by the intel
database, outranks any amount of migration work and produces Critical on either safety
classification.
How much of the migration is already done, which for a vulnerable connection is really a statement about who has to act.
| Priority | Emitted when |
|---|---|
Critical |
Already weak by today's standards: a negotiated version of TLS 1.1 or older, or a cipher suite rated High or Critical by the intel database (CBC modes, 3DES, RC4, static RSA key exchange, SHA-1). Applies to quantum-safe connections too — PQC over TLS 1.0 is still broken. |
High |
Quantum-vulnerable, and neither end advertised post-quantum support. Both the client fleet and the server need upgrading: the largest job, and the most common tier on a network that has not started migrating. |
Medium |
Quantum-vulnerable, but the client advertised PQC algorithms and the negotiation stayed classical without a downgrade being flagged. Partial readiness on one side. Also used when a handshake was analysed but could not be classified at all. |
Low |
Either a detected downgrade — the client offered PQC and the server declined it, so one server-side configuration change closes it — or a quantum-safe connection that is not finished, such as hybrid key exchange or PQC on only one side of the handshake. The smallest remaining work in both cases. |
None |
Fully post-quantum on both key exchange and signature. Nothing to do. |
The tiers are ordered by remaining effort, not by how alarming each sounds. A detected
downgrade is Low because the endpoint fleet already speaks PQC and only the server needs
changing — it is the cheapest item in the vulnerable population, not the least serious
one. A downgrade that warrants investigating as an active attack is surfaced separately
through pqc.handshake.downgrade_detected and the
Active Threat and Anomaly Detection
dashboard.
All four of Critical, High, Medium and Low are reachable by vulnerable connections
alone. This matters because every panel on
Vulnerability Analysis is filtered to
pqc.quantum_safety: "Vulnerable", and its four priority KPIs are documented as summing to
the vulnerable count — a tier that required Secure could never appear there.
The cipher rating that drives Critical is the same one published in the same record
under tls.handshake.server_hello.cipher_suite_intel.overall_risk, so a priority can
always be traced to the field that produced it. An unrecognised cipher suite does not
escalate — a missing intel row is a gap in the database, not evidence of weakness.
The priority is deliberately not banded from pqc.readiness_score as a severity proxy.
That score measures PQC migration maturity only: it awards points for offering and
selecting post-quantum algorithms and knows nothing about TLS version, cipher strength, or
certificate size. A TLS 1.0 / RSA-1024 / CBC connection scores 0, and so does a healthy
TLS 1.3 / X25519 / ECDSA-P256 connection, because neither involves PQC. Banding on the
score would rank those two identically.
Generating one connection per tier, for validating a dashboard against live traffic:
Critical openssl s_server -tls1 -cipher AES128-SHA
High openssl s_server -tls1_3 -groups X25519, client with no PQC support
Medium client with -groups X25519MLKEM768:X25519 against a classical-only server,
where no downgrade is flagged
Low client with -groups X25519MLKEM768 against a server without the OQS provider
(downgrade), or a hybrid X25519MLKEM768 handshake with a classical certificate
None hybrid or pure PQC key exchange with an ML-DSA certificate
SSH Fields¶
| Field | Type | Description |
|---|---|---|
ssh.client.protocol_version |
string | Client SSH protocol version. |
ssh.client.software_version |
string | Client SSH software version. |
ssh.client.comments |
string | Optional client banner comments. |
ssh.client.kex_algorithms |
array[string] | Client-offered SSH KEX algorithms. |
ssh.client.host_key_algorithms |
array[string] | Client-offered SSH host key algorithms. |
ssh.server.protocol_version |
string | Server SSH protocol version. |
ssh.server.software_version |
string | Server SSH software version. |
ssh.server.comments |
string | Optional server banner comments. |
ssh.selected_kex_algorithm |
string | The key exchange algorithm the two ends negotiated — the client's highest preference the server also supports. |
ssh.has_pqc_kex |
boolean | Whether the negotiated key exchange is post-quantum. Hybrids such as mlkem768x25519-sha256 count. |
ssh.kex_quantum_safe |
string | Safe when ssh.has_pqc_kex is true, Vulnerable otherwise. |
ssh.selected_host_key_algorithm |
string | The host key algorithm the server authenticated with. |
ssh.has_pqc_host_key |
boolean | Whether that host key is a post-quantum signature algorithm — ML-DSA, SLH-DSA or FN-DSA, under those names or the Dilithium/SPHINCS+/Falcon names they shipped under. |
Key exchange and host key are separate questions. ssh.has_pqc_kex describes the KEM that
protects the session; ssh.has_pqc_host_key describes the signature that authenticates the
server. A connection can be post-quantum in one and classical in the other, and today most
are: OpenSSH has shipped post-quantum key exchange since 9.0 and offers no post-quantum host
keys at all, so has_pqc_host_key is normally false even on an otherwise modern handshake.
Harvest-now-decrypt-later risk follows the key exchange; forgery risk follows the host key.
These algorithm fields were absent or wrong before pqc-sniffer 2.0.1. SSH handshake analysis stopped after the version banner, one TCP segment before the algorithms arrived, so the four list and selection fields were never written and
ssh.has_pqc_kexwas alwaysfalsewithssh.kex_quantum_safealwaysVulnerable— including on connections that had negotiated a post-quantum key exchange.ssh.has_pqc_host_keyandssh.selected_host_key_algorithmwere never emitted at all. Connections whose key exchange never parsed were also dropped without producing a record at all, so SSH is under-represented in data collected before this release. Comparisons across the upgrade boundary will show both more SSH connections and more of them quantum-safe; neither is a change in the estate.
QUIC Fields¶
| Field | Type | Description |
|---|---|---|
quic.version |
string | Parsed QUIC version. |
A QUIC connection's handshake is reported in the same tls.* and pqc.* fields as TLS: the
negotiated cipher suite and key-exchange group, the client's offered groups and key shares,
and the quantum-safety verdict. The sniffer reads it from the connection's first packets
(QUIC "Initial" packets), which QUIC encrypts with keys any observer can derive, including
a ClientHello split over several packets, as post-quantum key shares make it.
Some QUIC records have no tls object:
- The flow was first seen after its handshake, for example because it was already running when capture started. Its record is written after 60 seconds with what was observed.
- The connection uses QUIC version 2 (RFC 9369), whose first packets are not read.
The negotiated ALPN is not observable on QUIC: TLS 1.3 sends the server's selection in
EncryptedExtensions, which no passive observer has the key for. The ALPN the client
offered is observable, because it travels in the Initial packets the sniffer already
decrypts, and it is read — so a QUIC flow offering h3 is reported as http/3 with
pqc.app_protocol_basis of payload, on any port, rather than being named after port 443.
An offer is weaker evidence than an agreement and is not presented as one. It is acted on
only when every token in it names the same protocol: a client offering ["h3", "h3-29"] is
HTTP/3 whichever the server picks, while a client offering ["h3", "h2"] leaves the choice
to a ServerHello that cannot be read, so that flow keeps the port classification and its
port basis. HTTP/3 is a classification, not a parse — the HTTP/3 frames themselves travel
in QUIC streams encrypted with keys an observer cannot derive, so no HTTP/3 request detail
appears in a record.
HTTP/3 before 2.0.1
http/3 meant "QUIC on UDP port 443" and nothing more, with pqc.app_protocol_basis of
port. HTTP/3 on another port was reported as quic.
QUIC before 2.0.1
QUIC records never carried handshake fields. The first packets were never decrypted,
so QUIC connections, including browsers' post-quantum X25519MLKEM768 sessions, were
reported without a key exchange or a quantum-safety verdict.
IKE Fields¶
Present on IKEv2 connections once the cleartext IKE_SA_INIT exchange has been seen. Every
later IKE exchange is encrypted, so this is all an observer can learn about the SA.
| Field | Type | Description |
|---|---|---|
ike.offered_key_exchanges |
array of strings | Key exchange methods the initiator offered, including RFC 9370 additional key exchanges, e.g. ["Curve25519", "ML-KEM-768"]. |
ike.key_exchange |
string | The negotiated key exchange: the KE method, then each additional key exchange joined with +, e.g. Curve25519+ML-KEM-768. Absent until the responder's choice is seen. |
ike.encryption |
string | Negotiated encryption transform, with key length, e.g. AES-GCM-16-256. |
ike.prf |
string | Negotiated pseudorandom function, e.g. HMAC-SHA2-256. |
ike.integrity |
string | Negotiated integrity transform. Absent with AEAD ciphers such as AES-GCM, which need none. |
An IKEv2 SA is reported quantum-safe when the negotiated key exchange includes ML-KEM, either
as the key exchange method itself or as an additional key exchange; pqc.pqc_kex_is_hybrid
tells the two apart. When ML-KEM was offered but the responder chose a classical exchange,
pqc.pqc_downgrade_detected is true and pqc.vulnerability_reason says so.
IKEv2 before 2.0.1
IKEv2 records carried no algorithm at all, IKE behind NAT traversal (UDP 4500) was not identified, and the post-quantum check recognised ML-KEM only under unregistered private-use codepoints — so a genuine ML-KEM SA would have been reported as classical.
MQTT¶
Plaintext MQTT (network.protocol = mqtt) has no key exchange to assess: the finding is that
the session is unprotected. Its records carry pqc.quantum_safety = Vulnerable and a
pqc.vulnerability_reason naming the MQTT version and whether the CONNECT sent a username
or password in cleartext. The credentials themselves are never read or recorded. MQTT over TLS
(usually port 8883) is reported as TLS.
Observer Fields¶
| Field | Type | Description |
|---|---|---|
observer.name |
string | Sniffer ID/name. |
observer.type |
string | Observer type; currently sniffer. |
observer.vendor |
string | Vendor name; currently Tychon. |
observer.product |
string | Product name; currently Automated Cryptography Discovery & Inventory (ACDI) Sniffer. |
Dashboard Notes¶
For top-talkers grouped by port, address, and KEX method, use:
- Port:
destination.port, withsource.portavailable for client-side grouping. - Address:
source.ipanddestination.ip. - Conversation count: count records or unique
event.idvalues, depending on the aggregation window. - KEX method: prefer
pqc.handshake.key_exchange_algorithm; fallback topqc.handshake.server_hello.key_share.groupfor TLS 1.3 key-share group detail. - Safety: use
pqc.quantum_safetyfor the overall connection classification.