Email Believability Index (EBI) Specification — v0.11.0
Status: Draft
Last Updated: 2026-01-28
Overview
EBI provides a structured, machine-readable assessment of email authentication that goes beyond pass/fail verdicts. It quantifies risk, flags vulnerabilities, and provides the evidence trail needed for informed decisions.
Email authentication mechanisms (DKIM, SPF, DMARC) are powerful but their results hide crucial nuance. A DKIM “pass” doesn’t tell you whether the signature covers the entire body, whether the signing domain matches the From address, or whether the domain enforces DMARC policy. EBI surfaces this information.
Prerequisite Reading
| Topic | What It Does | Specification |
|---|---|---|
| DKIM | Cryptographically signs messages to prove integrity and sender domain | RFC 6376 |
| SPF | Declares which servers may send mail for a domain | RFC 7208 |
| DMARC | Ties DKIM and SPF together with policy enforcement | RFC 7489 |
| ARC | Preserves authentication results across forwarding hops | RFC 8617 |
| Authentication-Results | Standard header format for recording auth results | RFC 8601 |
Email Authentication Summary
DKIM computes a cryptographic signature
over message headers and body, embedding the signature in a
DKIM-Signature header. Recipients verify
against a public key in DNS. A valid signature proves the
message hasn’t been tampered with and was signed by someone
with access to the domain’s private key.
SPF publishes a DNS record declaring which IP addresses may send mail for a domain. Receiving servers check whether the sending IP is authorized.
DMARC requires that either DKIM or SPF passes and that the authenticated domain aligns with the visible From address. Without DMARC, an attacker could pass SPF using their own domain while spoofing a different From address.
ARC handles forwarding. When an email passes through a mailing list, the original DKIM signature often breaks. ARC creates a chain of custody: each server signs a statement recording authentication results at the time of receipt. If the ARC chain is intact, recipients can trust the original authentication results.
EBI Analysis Depth
Traditional checks report pass/fail. EBI reports:
- DKIM passed but uses relaxed body canonicalization, signature covers only first 1024 bytes, and signing domain differs from From address
- SPF couldn’t be verified from static .eml (no envelope data), but ARC chain shows SPF passed at original receipt
- DMARC passed via DKIM alignment, but domain uses
p=nonepolicy andpct=50
These details surface as findings with severity ratings and recommendations.
Static .eml Limitations
When analyzing .eml files:
| Mechanism | Verifiable? | Reason |
|---|---|---|
| DKIM | Usually | Signature in headers, key in DNS |
| SPF | No | Envelope data not in .eml |
| DMARC | Partial | Alignment yes, policy via DNS |
| ARC | Yes | Full chain in headers |
EBI falls back to ARC chains and
Authentication-Results headers when direct
verification isn’t possible.
Analysis Modes
EBI operates in two distinct modes with different trust models:
| Mode | Context | SPF Verifiable | Envelope Available | Primary Use |
|---|---|---|---|---|
LIVE |
MTA integration | Yes | Yes | Real-time filtering decisions |
FORENSIC |
Static .eml analysis | Via ARC only | No | Post-incident investigation |
Implementations MUST set
metadata.analysis.mode appropriately. The mode
affects:
- SPF verification capability (see §7.2)
- Available evidence sources
- Appropriate confidence levels
Design Goals
EBI is:
- Deterministic: same input produces same output
- Auditable: verdict backed by evidence
- Composable: embeddable into other systems
- Policy-friendly: supports routing/allowlist/quarantine decisions
1. Technical Specification
1.1 What is EBI?
EBI is a structured JSON response summarizing email authenticity and integrity by analyzing:
- Domain alignment and policy enforcement (DMARC)
- Sender authorization (SPF)
- Message signing and integrity (DKIM)
- Forwarding/intermediate authentication chains (ARC)
- Known vulnerability conditions and weak configurations
- Display name and header anomalies
EBI surfaces edge cases and vulnerabilities (weak DKIM canonicalization, partial body signing, relaxed alignment), allowing downstream systems to assess risk beyond pass/fail.
1.2 Analogy
EBI is analogous to OCSP for email authentication: a standardized response format conveying a verdict and supporting evidence.
1.3 Non-goals
- EBI does not define how emails are transported or retrieved.
- EBI does not replace full raw forensic artifacts. It references or summarizes them.
- EBI does not include PII beyond what is required for authentication analysis.
1.4 Static .eml Limitations
When analyzing static .eml files:
- SPF cannot be verified because
.emlfiles lack the SMTP envelope information (MAIL FROM, connecting IP). - DKIM can be verified if DKIM-Signature headers exist and the signing domain’s public key is retrievable via DNS.
- ARC chains provide a mechanism to recover original authentication results from forwarded messages.
2. Response Schema
2.1 Top-level Object
An EBI response MUST be a JSON object with the following structure.
{
"ebi_version": "0.8",
"request_id": "string",
"timestamp": "RFC3339 string",
"message_id": "string | null",
"subject": "string | null",
"from": "string | null",
"from_display_name": "string | null",
"to": ["string"],
"request_context": { /* RequestContextObject */ },
"verdict": { /* VerdictObject */ },
"score": { /* ScoreObject */ },
"dkim": { /* DKIMObject */ },
"spf": { /* SPFObject */ },
"dmarc": { /* DMARCObject */ },
"arc": { /* ARCObject | null */ },
"header_analysis": { /* HeaderAnalysisObject | null */ },
"domain_details": { /* DomainDetailsObject | null */ },
"findings": [ /* FindingObject[] */ ],
"metadata": { /* MetadataObject */ }
}2.2 Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
ebi_version |
string | Yes | Spec version. MUST be "0.8" for this
spec. |
request_id |
string | Yes | A unique ID for correlation across systems. |
timestamp |
string | Yes | RFC3339 timestamp when analysis was completed. |
message_id |
string | null | No | Message-ID header from the evaluated email. |
subject |
string | null | No | Subject line (optional). |
from |
string | null | No | Header From email address (optional). |
from_display_name |
string | null | No | Display name portion of From header (optional). |
to |
string[] | No | Header To list (optional). |
request_context |
object | Yes | Context about the analysis request submission. |
verdict |
object | Yes | High-level believability verdict and explanation. |
score |
object | Yes* | Numeric score derived from verdict + findings. Required unless analysis is impossible. |
dkim |
object | Yes | DKIM authentication results and signature details. |
spf |
object | Yes | SPF authentication results and evaluation details. |
dmarc |
object | Yes | DMARC evaluation results and alignment checks. |
arc |
object | null | No | ARC chain validation (null if no ARC headers present). |
header_analysis |
object | null | No | Analysis of header anomalies and display name spoofing indicators. |
domain_details |
object | null | No | Domain threat intelligence including reputation, malware, and infrastructure analysis. |
findings |
FindingObject[] | Yes | List of vulnerabilities, anomalies, and noteworthy conditions. |
metadata |
object | Yes | Context for the analysis environment and optional raw evidence references. |
* score MAY be null only when
the system cannot compute any reliable verdict (e.g.,
missing message headers, parser failure). In that case
verdict.status SHOULD be
INCONCLUSIVE.
2.3 RequestContext Object
The request_context object provides metadata
about how the analysis request was submitted, enabling
report recipients to correlate reports with their
submissions without exposing PII from the analyzed
email.
2.3.1 RequestContextObject Schema
{
"forwarder_email": "string",
"received_at": "RFC3339 string"
}2.3.2 RequestContext Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
forwarder_email |
string | Yes | Email address of the user who submitted the email for analysis. |
received_at |
string | Yes | RFC3339 timestamp when the analysis request was received. |
Privacy Note: The
request_context contains only the submitter’s
email address and submission timestamp. It does NOT include
PII from the analyzed email beyond what is already present
in the standard email headers (from,
to, subject).
3. Verdict Object
3.1 VerdictObject Schema
{
"status": "AUTHENTIC" | "PARTIAL" | "FAILED" | "UNSAFE" | "INCONCLUSIVE",
"confidence": "HIGH" | "MEDIUM" | "LOW",
"code": "string",
"summary": "string",
"explanation": "string | null",
"flags": ["string"]
}3.2 status
Semantics
| Status | Meaning |
|---|---|
AUTHENTIC |
Strong authentication and integrity; no high-risk weaknesses detected. |
PARTIAL |
Some authentication succeeded but with missing signals or weak alignment. |
FAILED |
Authentication failed or indicates possible spoofing. |
UNSAFE |
Authentication passed but vulnerabilities or configuration weaknesses allow tampering or misattribution. |
INCONCLUSIVE |
Not enough information to determine authenticity. |
3.3 confidence
Confidence reflects the reliability of the verdict given available evidence.
| Confidence | When Used |
|---|---|
HIGH |
All expected signals present and consistent |
MEDIUM |
Some signals present but not all, or weak crypto used |
LOW |
Unable to make a confident determination |
3.4 Verdict Codes
The code field provides machine-readable
detail about why a particular verdict was reached.
| Code | Status | Description |
|---|---|---|
ALL_PASS |
AUTHENTIC | DKIM, SPF, and DMARC all passed |
DKIM_PARTIAL_BODY_SIGNED |
UNSAFE | DKIM l= tag present; body can be
extended |
DKIM_SIGNATURE_EXPIRED |
FAILED | DKIM signature x= timestamp has passed |
NO_AUTH_MECHANISMS |
FAILED | No DKIM signatures present and sender has DKIM keys published (possible spoofing) |
SENDER_NO_DKIM |
PARTIAL | Sender domain has no DKIM keys published (poor email hygiene) |
DMARC_FAIL |
FAILED | DMARC alignment check failed (with enforcing policy) |
DMARC_FAIL_POLICY_NONE |
PARTIAL | DMARC alignment failed but domain publishes p=none |
ALL_AUTH_FAIL |
FAILED | Had signatures but all authentication mechanisms failed |
WEAK_CRYPTO |
PARTIAL | DKIM passed but uses deprecated SHA-1 |
DKIM_ONLY |
PARTIAL | Only DKIM passed; SPF/DMARC absent or failed |
SPF_ONLY |
PARTIAL | Only SPF passed; DKIM/DMARC absent or failed |
ARC_VOUCHED |
PARTIAL | Valid ARC chain vouches for message despite expired signatures |
UNKNOWN |
INCONCLUSIVE | Unable to determine authenticity |
3.5 Verdict Determination Priority
Implementations MUST evaluate verdict conditions in the following priority order (first match wins):
SPF Trust Model Note: When SPF is
unverifiable (no valid ARC chain for static .eml files), SPF
does NOT count for or against verdict determination. The
rules below apply effectiveSpfPass which is
true only if SPF is both verifiable AND passed.
- UNSAFE: Any DKIM signature has
body_length.limited = true(l= tag present) - FAILED: Any DKIM signature has expired
(finding
DKIM_SIGNATURE_EXPIREDpresent) AND message is NOT vouched by a valid ARC chain or Authentication-Results (see §3.6) - PARTIAL: No DKIM signatures AND sender
domain has no DKIM keys published
(
SENDER_NO_DKIM) (see §3.8) - FAILED: No DKIM signatures AND sender
domain HAS DKIM keys published
(
NO_AUTH_MECHANISMS) — possible spoofing - FAILED: DMARC result is
FAILAND domain policy is NOTp=none(see §3.7) - PARTIAL: DMARC result is
FAILAND domain policy isp=none, confidence=MEDIUM (DMARC_FAIL_POLICY_NONE) - FAILED: DKIM failed AND SPF verifiably
failed (
ALL_AUTH_FAIL) - PARTIAL: DKIM passed but uses weak cryptography (SHA-1), confidence=MEDIUM
- AUTHENTIC: DKIM passed AND DMARC passed AND (SPF passed OR SPF unverifiable)
- PARTIAL: DKIM passed (SPF unverifiable or failed), confidence=MEDIUM
- PARTIAL: SPF verifiably passed (but DKIM failed), confidence=MEDIUM
- INCONCLUSIVE: Otherwise, confidence=LOW
3.6 Expired Signatures with ARC/Authentication-Results
When a valid ARC chain exists, expired DKIM signatures SHOULD NOT force a FAILED verdict. The ARC chain serves as a trusted record of authentication state at time of receipt.
This applies in two scenarios:
DKIM passed at origin: The ARC chain shows
dkim=passfor the original hop. The signature was valid when received; subsequent expiry is irrelevant.DKIM absent at origin but other auth passed: The ARC chain shows
dkim=nonebutspf=passat the original hop. The expired signatures are from intermediaries (forwarding servers), not the original sender. The message was legitimately authenticated via SPF.
In either case, the DKIM_SIGNATURE_EXPIRED
finding remains (severity HIGH) as evidence of the current
signature state, but verdict determination continues to the
next priority level.
Authentication-Results headers follow the same logic but are only trusted when no ARC chain exists, as they are more easily forged.
3.7 DMARC Failures with p=none Policy
When DMARC evaluation fails but the domain publishes
p=none, implementations SHOULD NOT assign a
FAILED verdict. The p=none policy is an
explicit declaration that the domain does not enforce DMARC
alignment.
Common legitimate scenarios for p=none:
- Consumer email providers (Gmail, Outlook.com) that expect forwarding breakage
- Domains in DMARC deployment monitoring phase
- Organizations with complex mail flows that cannot guarantee alignment
In this case, the verdict SHOULD be PARTIAL with code
DMARC_FAIL_POLICY_NONE. The
DMARC_FAIL and DMARC_POLICY_NONE
findings remain as evidence, but the message should not be
treated as dangerous solely due to DMARC failure.
3.8 Distinguishing Poor Sender Hygiene from Spoofing
When a message lacks DKIM signatures, implementations SHOULD check whether the sender domain has DKIM keys published. This distinguishes between two fundamentally different scenarios:
Scenario A: Sender has no DKIM configured (poor
email hygiene) - No DKIM signatures in message -
DNS lookup shows no DKIM keys at common selectors - Verdict:
PARTIAL with code SENDER_NO_DKIM -
The sender simply hasn’t configured email authentication
Scenario B: Sender has DKIM but message lacks
signatures (possible spoofing) - No DKIM signatures
in message - DNS lookup shows DKIM keys ARE published for
the domain - Verdict: FAILED with code
NO_AUTH_MECHANISMS - A legitimate sender would
sign; absence suggests spoofing
3.8.1 DKIM Key Discovery
Implementations SHOULD check these common selectors for DKIM keys:
- Microsoft 365:
selector1,selector2 - Google Workspace:
google - Generic:
default,dkim,mail,k1,s1,s2
The check queries
{selector}._domainkey.{domain} for TXT records
containing v=DKIM1 with a non-empty
p= tag.
3.8.2 Limitations
DKIM key discovery is not exhaustive. Custom selectors may exist that are not in the common list. However, the major email platforms use well-known selectors, and the check provides a strong signal for the majority of legitimate senders.
If DNS lookup fails or times out, implementations SHOULD
treat senderHasDkimKeys as undefined and fall
back to the NO_AUTH_MECHANISMS verdict (assume
possible spoofing as the safer default).
4. Score Object
4.1 Purpose
The score provides a single numeric
projection of believability for UI display and policy
thresholding.
The score is derived from verdict and
findings. It is not a replacement for the
richer EBI structure.
4.2 ScoreObject Schema
{
"value": 0,
"scale": { "min": 0, "max": 100 },
"band": "EXCELLENT" | "GOOD" | "CAUTION" | "DANGEROUS" | "UNKNOWN",
"method": "EBI_SCORE_V2",
"components": {
"base": 0,
"finding_penalty": 0,
"confidence_adjustment": 0
},
"notes": "string | null"
}4.3 Bands
| Band | Range | User Mental Model |
|---|---|---|
EXCELLENT |
90–100 | Trust it |
GOOD |
80–89 | Trust it |
CAUTION |
60–79 | Raised eyebrow (70-79) to arms crossed (60-69) |
DANGEROUS |
0–59 | Fail |
UNKNOWN |
Used only when score is null. |
4.4 Scoring Method:
EBI_SCORE_V3
4.4.1 Design Rationale
EBI scores are calibrated for humans doing financial business who need to quickly assess email believability:
- 80+: Trust it. Full authentication or minor hygiene issues only.
- 70-79: Raised eyebrow. Sender has poor email hygiene (missing DKIM, no DMARC, third-party signing without alignment).
- 60-69: Arms crossed. Suspicious configuration, partial authentication, or weak crypto.
- <60: Fail. Authentication failed, integrity compromised, or possible spoofing.
Most legitimate emails should score 75+. A score below 70 warrants investigation. A score below 60 is a red flag.
| verdict.status | base | Rationale |
|---|---|---|
AUTHENTIC |
90 | Full authentication passed, domain aligned. Trust zone. |
PARTIAL |
75 | DKIM passed but incomplete alignment. Eyebrow territory. |
INCONCLUSIVE |
55 | Cannot verify at all. Arms crossed. |
FAILED |
35 | Authentication failed. Fail zone. |
UNSAFE |
25 | Integrity compromised (l= tag). Hard fail regardless of other auth. |
4.4.2 Calculation Steps
Step A: Base score from
verdict.status
See table in §4.4.1.
Step B: Finding penalties
For each element in findings, subtract:
| severity | penalty |
|---|---|
CRITICAL |
-25 |
HIGH |
-12 |
MEDIUM |
-6 |
LOW |
-2 |
INFO |
0 |
Total finding penalty MUST be capped at -50.
Step C: Confidence adjustment
| verdict.confidence | adjustment |
|---|---|
HIGH |
+5 |
MEDIUM |
0 |
LOW |
-10 |
Step D: Clamp
score = clamp(base + finding_penalty + confidence_adjustment, 0, 100)
Step E: Band mapping
Band is determined from the ranges in §4.3.
4.4.3 Migration from EBI_SCORE_V2
Implementations upgrading from v0.8 SHOULD note:
- PARTIAL base raised from 55 to 75. Third-party ESP signing (Google Workspace, SendGrid, Marketo) is normal and should not push emails into fail zone.
- INCONCLUSIVE base raised from 40 to 55. Unverifiable emails need investigation but shouldn’t auto-fail.
- FAILED base raised from 15 to 35. Failed auth is bad but findings add detail.
- UNSAFE base lowered from 60 to 25. Integrity vulnerabilities (l= tag) are critical.
- New findings
DKIM_NO_AUTHOR_DOMAIN_SIGNATUREandDMARC_TEMPERRORare INFO severity (no penalty).
Score thresholds for policy decisions should be recalibrated: - Old “trust” threshold of 80 → remains 80 - Old “review” threshold of 55 → consider raising to 70 - Old “block” threshold of 40 → consider raising to 60
5. Findings
5.1 FindingObject Schema
{
"id": "string",
"severity": "CRITICAL" | "HIGH" | "MEDIUM" | "LOW" | "INFO",
"points": "number",
"title": "string",
"summary": "string",
"details": "string | null",
"evidence": {
"type": "HEADER" | "DNS" | "DERIVED" | "BODY" | "OTHER",
"key": "string | null",
"value": "string | null"
},
"recommendation": "string | null"
}| Field | Type | Description |
|---|---|---|
id |
string | Stable identifier for rule-based matching (see §14) |
severity |
enum | CRITICAL, HIGH, MEDIUM, LOW, or INFO |
points |
number | Score impact of this finding (negative for penalties, 0 for INFO) |
title |
string | Short human-readable title |
summary |
string | One-sentence explanation |
details |
string|null | Extended explanation, may include technical background |
evidence |
object | Source of the finding |
recommendation |
string|null | Suggested action |
Point Values by Severity: - CRITICAL: -25 - HIGH: -12 - MEDIUM: -6 - LOW: -2 - INFO: 0
5.2 Finding IDs
Finding IDs are stable strings suitable for rule-based policy. See §14 for the complete registry.
6. DKIM
6.1 DKIMObject Schema
{
"result": "PASS" | "FAIL" | "NONE" | "TEMPERROR" | "PERMERROR",
"from_domain_match": true,
"domain": "string | null",
"selector": "string | null",
"signatures": [
{
"domain": "string",
"selector": "string",
"result": "PASS" | "FAIL" | "TEMPERROR" | "PERMERROR",
"canonicalization": {
"header": "simple" | "relaxed" | "unknown",
"body": "simple" | "relaxed" | "unknown"
},
"body_length": {
"limited": false,
"value": null
},
"timestamp": "RFC3339 string | null",
"expiry": "RFC3339 string | null",
"hash_algo": "rsa-sha256" | "rsa-sha1" | "ed25519-sha256" | "unknown",
"key_size": "number | null",
"signed_headers": ["string"]
}
]
}6.2 Field Semantics
result
The effective DKIM result. May be derived from (in priority order):
- Direct verification: Cryptographic verification of DKIM-Signature headers in the message
- Authentication-Results header: If
direct verification fails but the message contains an
Authentication-Resultsheader showingdkim=pass(see §6.4) - ARC chain: If no direct DKIM signatures
but a valid ARC chain exists with
dkim=passat the origin (i=1)
When the result comes from Authentication-Results,
metadata.raw.evidence_refs SHOULD include
"Authentication-Results". When the result comes
from ARC, metadata.raw.evidence_refs SHOULD
include "ARC-Authentication-Results".
from_domain_match
Indicates whether the DKIM signing domain
(d=) matches or is a parent of the From header
domain. This is a DKIM-level observation independent of
DMARC evaluation.
true: Thed=domain matches the From domain (exact or subdomain)false: No match
body_length.limited
Indicates whether the DKIM signature uses the
l= tag to limit body signing.
limited: false: Entire body is signed. Safe.limited: true: Only first N bytes signed. Attacker can append content. This MUST generate aDKIM_PARTIAL_BODY_SIGNEDfinding with CRITICAL severity and result inUNSAFEverdict.
key_size
RSA key size in bits, when determinable from DNS lookup.
Implementations SHOULD generate
DKIM_WEAK_KEY_SIZE finding for keys smaller
than 2048 bits.
6.3 Guidance
Weak DKIM conditions SHOULD be expressed via
findings (e.g., relaxed body canonicalization,
partial body signing, sha1).
When body_length.limited is
true, the value field contains the
byte count from the l= tag.
6.4 Authentication-Results Fallback (RFC 8601)
When analyzing forwarded emails (e.g., emails forwarded as attachments), DKIM signatures may fail re-verification due to transit modifications:
- Header reordering or whitespace normalization
- Line ending changes
- Character encoding adjustments
The original receiving server verified DKIM successfully
at the time of receipt and recorded this in the
Authentication-Results header (RFC 8601).
Implementations SHOULD trust
Authentication-Results when ALL of the
following conditions are met:
- The message contains DKIM-Signature headers (signatures were present)
- Direct cryptographic verification fails or returns
neutral - An
Authentication-Resultsheader exists withdkim=pass - The passing DKIM domain in
Authentication-Resultsmatches a signature’sd=domain
When Authentication-Results is used: - A
finding with ID DKIM_VIA_AUTH_RESULTS (severity
INFO) SHOULD be added -
metadata.raw.evidence_refs SHOULD include
"Authentication-Results"
Security note: The
Authentication-Results header represents a
trusted assertion from the original receiving mail server.
This is comparable to trusting ARC chains.
6.5 DKIM Archive Fallback (OPTIONAL)
When DKIM verification fails because the signing key is no longer published in DNS (key rotation, domain policy changes), implementations MAY query the DKIM Archive maintained by the ZK Email project for historical keys.
The DKIM Archive is a public database of historical DKIM public keys with over 1.4 million archived keys. Each archived key includes timestamps indicating when the key was first and last observed in DNS.
6.5.1 Archive API
The archive provides a REST API:
GET https://archive.prove.email/api/key/domain?domain={domain}&selector={selector}
Response: Array of archived keys with value,
domain, selector,
firstSeenAt, lastSeenAt
fields.
Implementations MAY also contribute newly-verified domain/selector pairs:
POST https://archive.prove.email/api/dsp
Body: { "domain": "example.com", "selector": "s1" }
6.5.2 Archive Fallback Conditions
Implementations SHOULD query the archive when ALL of the following conditions are met:
- The message contains DKIM-Signature headers
- Direct cryptographic verification fails
- The failure is NOT due to body hash mismatch (message modification)
- The failure appears to be key-related (DNS lookup failure, key not found, key mismatch)
Implementations SHOULD NOT query the archive when:
- Body hash verification failed (indicates message was modified; archived key won’t help)
- Direct verification succeeded
- ARC chain or Authentication-Results already provide trusted DKIM pass
6.5.3 Archive Verification
When an archived key is found:
- Re-attempt DKIM verification using the archived key
- If verification passes, treat as DKIM pass with reduced trust
6.5.4 Trust Model for Archive-Verified DKIM
Archive-verified DKIM provides weaker assurance than DNS-verified DKIM because:
- The archived key was valid at some point, but we cannot prove it was valid at the exact moment the email was signed
- Key rotation may have occurred between signing and archival
Implementations MUST apply score penalties to reflect this reduced trust:
| Key Age | Severity | Typical Penalty | Rationale |
|---|---|---|---|
| < 1 year | LOW | -2 points | Recent key rotation; high confidence |
| ≥ 1 year | MEDIUM | -6 points | Stale key; reduced confidence |
Key age is determined by the lastSeenAt
timestamp from the archive.
6.5.5 Findings
When archive verification is used:
| Finding ID | Severity | When Generated |
|---|---|---|
DKIM_VERIFIED_VIA_ARCHIVE |
LOW | Archived key < 1 year old |
DKIM_VERIFIED_VIA_STALE_ARCHIVE |
MEDIUM | Archived key ≥ 1 year old |
6.5.6 Metadata
When archive verification is used:
metadata.raw.evidence_refsSHOULD include"DKIM-Archive:{domain}/{selector}"dmarc.explanationMAY note"Verified using historical key from DKIM Archive"
6.5.7 Contribution
Implementations are ENCOURAGED to contribute successfully DNS-verified domain/selector pairs to the archive. This creates a virtuous cycle: more contributions improve archive coverage for future verifications.
Contribution SHOULD be fire-and-forget (non-blocking) and SHOULD NOT affect response latency.
6.5.8 Privacy Considerations
The archive API receives only:
- Domain name
- Selector name
- Requesting server’s IP address (for rate limiting)
The archive does NOT receive email content, message headers, recipient addresses, or other message metadata. This exposure is equivalent to standard DNS queries.
7. SPF
7.1 SPFObject Schema
{
"result": "PASS" | "FAIL" | "SOFTFAIL" | "NEUTRAL" | "UNVERIFIABLE" | "TEMPERROR" | "PERMERROR",
"domain": "string | null",
"mail_from": "string | null",
"helo": "string | null",
"ip": "string | null",
"explanation": "string | null",
"dns_lookups": 0,
"verification_source": "DIRECT" | "ARC" | "NONE"
}7.2 Static .eml Limitations and Trust Model
CRITICAL: SPF cannot be verified
directly from static .eml files because the
SMTP envelope sender (MAIL FROM) and connecting IP address
are not preserved.
The Authentication-Results header is NOT a
trusted source for SPF verification. Unlike DKIM signatures
and ARC chains, Authentication-Results headers
are trivially forgeable—any mail server can add this header
with arbitrary claims. Trusting this header would allow
attackers to forge SPF pass results.
SPF Trust Model: - SPF can ONLY be
trusted via a valid ARC chain (cryptographically attested) -
If no valid ARC chain exists, result MUST be
"UNVERIFIABLE" -
Authentication-Results header claims about SPF
MUST NOT be trusted
In the UNVERIFIABLE case: - result MUST be
"UNVERIFIABLE" -
verification_source MUST be "NONE"
- A finding with ID SPF_NOT_VERIFIABLE
(severity INFO, 0 points) MUST be added - SPF MUST NOT count
for or against verdict determination - SPF MUST NOT generate
penalty findings (FAIL, SOFTFAIL, NEUTRAL)
7.3 SPF from Valid ARC Chain
If and only if a valid ARC chain exists (see §9) and the ARC-Authentication-Results at instance i=1 contains SPF results, those results MAY be trusted.
When SPF is derived from ARC: -
verification_source MUST be "ARC"
- The explanation field SHOULD note
"From ARC chain" - SPF MAY contribute to
verdict determination - SPF findings (FAIL, SOFTFAIL,
NEUTRAL) MAY be generated with point penalties
7.4 SPF in LIVE Mode
When metadata.analysis.mode is
LIVE, implementations have access to envelope
data: - verification_source MUST be
"DIRECT" - SPF verification proceeds normally
per RFC 7208 - All SPF results (PASS, FAIL, SOFTFAIL, etc.)
are authoritative
7.5 SPF Findings
| ID | Severity | Points | Condition |
|---|---|---|---|
SPF_FAIL |
HIGH | -12 | SPF returned fail (verified sources only) |
SPF_SOFTFAIL |
MEDIUM | -6 | SPF returned softfail (verified sources only) |
SPF_NEUTRAL |
LOW | -2 | SPF returned neutral (verified sources only) |
SPF_NOT_VERIFIABLE |
INFO | 0 | Static .eml lacks envelope context for SPF |
7.6 Guidance
DNS lookup count may matter for risk and operational debugging.
SPF neutrality or softfail SHOULD appear as findings when relevant to policy, but ONLY when SPF is verifiable via ARC or direct verification.
8. DMARC
8.1 DMARCObject Schema
{
"result": "PASS" | "FAIL" | "NONE" | "TEMPERROR" | "PERMERROR",
"policy": "none" | "quarantine" | "reject" | "unknown",
"pct": 100,
"alignment": {
"dkim": true,
"spf": true,
"mode": "relaxed" | "strict" | "unknown"
},
"domain": "string | null",
"subdomain_policy": "none" | "quarantine" | "reject" | "unknown | null",
"rua": ["string"],
"ruf": ["string"],
"explanation": "string | null"
}8.2 DMARC Result Derivation
The effective DMARC result may come from multiple sources (in priority order):
- Authentication-Results header: If
message contains
Authentication-Resultswithdmarc=result (see §6.4 for trust model) - ARC chain: If valid ARC chain exists
with
dmarc=passat origin (i=1) - Direct DMARC check: Via DNS lookup of
_dmarc.<from-domain> - Implicit pass: If DKIM alignment passes (signing domain matches From domain) and DKIM passes, DMARC is considered to pass even without explicit DMARC record
When the result comes from Authentication-Results, the
explanation field SHOULD note
"Determined from Authentication-Results header".
8.3 Alignment for Static .eml
For static .eml files: -
alignment.dkim: Can be determined (signing
domain vs From domain) - alignment.spf: SHOULD
be false or null (cannot verify
SPF alignment without envelope)
8.4 DMARC Policy Findings
| ID | Severity | Points | Condition |
|---|---|---|---|
DMARC_POLICY_NONE |
MEDIUM | -6 | DMARC policy is p=none (no
enforcement) |
DMARC_POLICY_PARTIAL |
MEDIUM | -6 | DMARC pct < 100 (partial
enforcement) |
DMARC_FAIL |
HIGH | -12 | DMARC evaluation failed |
Rationale for DMARC_POLICY_NONE at
MEDIUM: A domain publishing p=none
explicitly instructs receivers not to enforce authentication
failures. This is a deliberate policy choice that enables
spoofing attacks against the domain. While commonly used
during DMARC deployment testing, it provides no protection
in production.
Rationale for DMARC_POLICY_PARTIAL: When
pct < 100, only a percentage of failing
messages are subject to policy. An attacker can simply send
more emails to ensure some bypass enforcement.
9. ARC (Authenticated Received Chain)
9.1 Overview
ARC preserves authentication results across mail forwarding. When an email is forwarded, the original DKIM signature may break, but ARC headers record authentication at each hop.
EBI uses ARC to recover original authentication results when direct verification is not possible.
9.2 ARCObject Schema
{
"result": "PASS" | "FAIL" | "NONE" | "TEMPERROR" | "PERMERROR",
"chain_valid": true,
"instances": [
{
"i": 1,
"cv": "pass" | "fail" | "none" | "unknown",
"auth_results": "string | null",
"signing_domain": "string | null"
}
]
}9.3 Field Semantics
chain_valid
Indicates whether the ARC chain is trustworthy:
true: All instances havecvofpassornone(for i=1)false: Any instance hascv=fail
Authentication results from ARC MUST NOT be
trusted if chain_valid is
false.
instances
Array of ARC set instances, sorted by i
value (ascending). Each instance represents a hop in the
forwarding chain.
i: Instance number (1 = origin, 2 = first forwarder, etc.)cv: Chain validation status at this hopauth_results: Summary of authentication results (e.g.,"dkim=pass spf=pass dmarc=pass")signing_domain: Domain that signed this ARC set (d=from ARC-Seal)
9.4 Trusting ARC Results
When chain_valid is true, the
authentication results from instance i=1 (the origin) MAY be
used as effective results:
IF arc.chain_valid = true AND arc.instances[0].i = 1:
effectiveDkim = parseResult(arc.instances[0].auth_results, "dkim")
effectiveSpf = parseResult(arc.instances[0].auth_results, "spf")
effectiveDmarc = parseResult(arc.instances[0].auth_results, "dmarc")
9.5 ARC Parsing
ARC headers are parsed from:
- ARC-Seal: Contains
i=,cv=,d=(signing domain) - ARC-Authentication-Results: Contains original auth results at each hop
Example ARC-Authentication-Results:
ARC-Authentication-Results: i=1; mx.google.com;
dkim=pass header.d=example.com;
spf=pass smtp.mailfrom=example.com;
dmarc=pass header.from=example.com
10. Header Analysis
10.1 Overview
Authentication mechanisms verify domain ownership but not sender intent. An email can have perfect DKIM/SPF/DMARC and still be deceptive through:
- Display name spoofing:
From: "CEO John Smith" <attacker@legitimate.com> - Header injection attacks
- Misleading Reply-To addresses
The header_analysis object captures these
indicators.
10.2 HeaderAnalysisObject Schema
{
"display_name_analysis": {
"from_display_name": "string | null",
"from_email": "string",
"from_domain": "string",
"impersonation_indicators": [
{
"type": "BRAND_NAME" | "EXECUTIVE_TITLE" | "DOMAIN_IN_NAME" | "UNICODE_CONFUSABLE" | "OTHER",
"matched": "string",
"details": "string | null"
}
]
},
"reply_to_analysis": {
"reply_to_present": true,
"reply_to_email": "string | null",
"reply_to_domain": "string | null",
"domain_mismatch": true,
"signed": true
},
"header_anomalies": [
{
"header": "string",
"issue": "string",
"severity": "HIGH" | "MEDIUM" | "LOW"
}
]
}10.3 Display Name Impersonation
Implementations SHOULD check for:
| Type | Description | Example |
|---|---|---|
BRAND_NAME |
Known brand/company name in display name | "Microsoft Support" <random@attacker.com> |
EXECUTIVE_TITLE |
Executive titles suggesting authority | "CEO" <phish@example.com> |
DOMAIN_IN_NAME |
Email domain embedded in display name | "support@company.com" <fake@attacker.com> |
UNICODE_CONFUSABLE |
Unicode characters resembling ASCII | "Microsοft" (Greek omicron) |
10.4 Header Analysis Findings
| ID | Severity | Points | Condition |
|---|---|---|---|
DISPLAY_NAME_BRAND_IMPERSONATION |
HIGH | -12 | Known brand name in display name from unrelated domain |
DISPLAY_NAME_DOMAIN_MISMATCH |
MEDIUM | -6 | Display name contains email address different from actual From |
REPLY_TO_DOMAIN_MISMATCH |
MEDIUM | -6 | Reply-To domain differs from From domain |
UNICODE_CONFUSABLE_CHARS |
HIGH | -12 | Unicode characters that visually resemble ASCII in headers |
11. Domain Intelligence
11.1 Overview
Domain threat intelligence provides additional context about the reputation and security posture of domains involved in an email. This analysis queries external threat intelligence APIs to assess:
- Domain reputation scores
- Malware and phishing associations
- Blocklist presence
- Registration status and age
- SSL/TLS configuration
- Mail server infrastructure
Domain intelligence is optional. When the
THREATS_API_KEY environment variable is not
configured, domain_details will be
null.
11.2 DomainDetailsObject Schema
{
"from_domain": { /* DomainAnalysis | null */ },
"to_domains": [ /* DomainAnalysis[] */ ],
"lookup_errors": ["string"]
}11.3 DomainAnalysis Schema
{
"domain": "string",
"reputation": {
"score": 85,
"risk_level": "LOW" | "MEDIUM" | "HIGH" | "CRITICAL",
"warnings": [
{
"code": 4001,
"category": "string",
"description": "string"
}
],
"dns_issues": ["string"],
"mail_issues": ["string"],
"ssl_issues": ["string"],
"blocklist_flags": ["string"]
},
"malware": {
"safe_score": 100,
"is_dangerous": false,
"threats": ["string"]
},
"infrastructure": {
"web_servers": [{ "hostname": "string", "ip": "string", "country": "string | null", "region": "string | null", "network_name": "string | null" }],
"mail_servers": [{ /* same as web_servers */ }],
"name_servers": [{ /* same as web_servers */ }]
},
"whois": {
"registration_date": "2010-03-15T00:00:00Z" | null,
"expiration_date": "2025-03-15T00:00:00Z" | null,
"domain_age_days": 5000 | null,
"registrar": "GoDaddy" | null
}
}All fields in DomainAnalysis except
domain may be null if the
respective data source is unavailable.
11.4 WHOIS Data
Domain registration data is fetched via RDAP
(Registration Data Access Protocol), the modern replacement
for WHOIS. RDAP uses standard HTTPS on port 443, making it
compatible with serverless environments that block raw TCP
connections. Results are cached aggressively since domain
registration dates don’t change. The cache persists to disk
at the path specified by WHOIS_CACHE_FILE
(default: /tmp/ebi-whois-cache.json).
| Field | Type | Description |
|---|---|---|
registration_date |
string | null | ISO 8601 date when domain was registered |
expiration_date |
string | null | ISO 8601 date when domain registration expires |
domain_age_days |
number | null | Days since registration (calculated) |
registrar |
string | null | Domain registrar name |
Domain age is a critical phishing signal. Domains registered within the past 7 days are flagged as high-risk in the formatted output.
11.5 Risk Level Thresholds
| Risk Level | Reputation Score |
|---|---|
| CRITICAL | < 20 |
| HIGH | 20–39 |
| MEDIUM | 40–59 |
| LOW | ≥ 60 |
11.6 Warning Code Categories
| Range | Category |
|---|---|
| 1000–1026 | DNS/nameserver issues |
| 2001–2009 | WHOIS/registration issues |
| 3001–3008 | Website security issues |
| 4001–4005 | Blocklist presence |
| 5000–5020 | Mail server configuration |
| 6001–6022 | SSL/TLS issues |
11.7 Domain Intelligence Findings
Only findings for the from_domain affect the
EBI score. To-domain analysis is informational.
| ID | Severity | Points | Condition |
|---|---|---|---|
DOMAIN_MALWARE_DETECTED |
CRITICAL | -25 | malware.safe_score < 50 OR
malware.threats.length > 0 |
DOMAIN_BLOCKLISTED |
HIGH | -12 | Warning codes 4001–4005 present |
DOMAIN_POOR_REPUTATION |
HIGH | -12 | reputation.score < 40 |
DOMAIN_RECENTLY_REGISTERED |
MEDIUM | -6 | Warning code 2001 present |
DOMAIN_EXPIRED_REGISTRATION |
MEDIUM | -6 | Warning code 2002 present |
DOMAIN_SSL_ISSUES |
MEDIUM | -6 | Warning codes 6001–6022 present |
DOMAIN_NO_SPF |
LOW | -2 | Warning code 5015 present |
DOMAIN_NO_DMARC |
LOW | -2 | Warning code 5016 present |
DOMAIN_OFFSHORE |
INFO | 0 | Warning code 2008 present |
12. Metadata
11.1 MetadataObject Schema
{
"source": {
"system": "string",
"version": "string | null"
},
"analysis": {
"mode": "LIVE" | "FORENSIC",
"elapsed_ms": 0
},
"raw": {
"header_hash": "string | null",
"body_hash": "string | null",
"evidence_refs": ["string"]
}
}11.2 Guidance
header_hash and body_hash allow
correlation without storing raw content.
evidence_refs MAY contain internal URIs to
stored raw artifacts.
When authentication results come from ARC,
evidence_refs SHOULD include
"ARC-Authentication-Results".
11.3 Analysis Mode
| Mode | Description |
|---|---|
LIVE |
Real-time MTA integration with full envelope access |
FORENSIC |
Static .eml analysis, limited to headers and ARC |
The previous BATCH and TEST
modes are deprecated. Implementations SHOULD map: -
BATCH → FORENSIC -
TEST → FORENSIC
13. Implementation Requirements
13.1 Determinism
Given identical inputs, an implementation MUST produce
identical EBI outputs (except for timestamp and
request_id).
13.2 Evidence-first
Every non-trivial verdict decision SHOULD be supported by either:
- an explicit auth result (SPF/DKIM/DMARC/ARC)
- or a corresponding
findingwith evidence
13.3 ARC Chain Validation
Implementations MUST:
- Parse all ARC-Seal headers to extract
cv=values - Set
chain_valid = falseif ANY instance hascv=fail - Only trust ARC authentication results when
chain_valid = true
13.4 Backward Compatibility
Clients MUST treat unknown fields as ignorable.
Fields MAY be added in future versions.
13.5 Score Method Versioning
When score.method changes between spec
versions, implementations SHOULD:
- Log the method version for audit purposes
- Document score changes in migration guides
- Provide configuration option to use previous method during transition
14. Example Response
This example demonstrates a forwarded email where original DKIM verification happens via ARC chain.
{
"ebi_version": "0.8",
"request_id": "req_a1b2c3d4",
"timestamp": "2026-01-18T10:30:00Z",
"message_id": "<forwarded-msg@example.com>",
"subject": "Important Document",
"from": "sender@example.com",
"from_display_name": "Sender Name",
"to": ["recipient@company.com"],
"request_context": {
"forwarder_email": "analyst@company.com",
"received_at": "2026-01-18T10:29:45Z"
},
"verdict": {
"status": "AUTHENTIC",
"confidence": "HIGH",
"code": "ALL_PASS",
"summary": "All authentication mechanisms passed.",
"explanation": "Email appears genuine.",
"flags": []
},
"score": {
"value": 83,
"scale": {"min": 0, "max": 100},
"band": "GOOD",
"method": "EBI_SCORE_V2",
"components": {
"base": 90,
"finding_penalty": -12,
"confidence_adjustment": 5
},
"notes": "AUTHENTIC + 2 findings"
},
"dkim": {
"result": "PASS",
"from_domain_match": true,
"domain": "example.com",
"selector": null,
"signatures": []
},
"spf": {
"result": "PASS",
"domain": "example.com",
"mail_from": null,
"helo": null,
"ip": null,
"explanation": "From ARC chain",
"dns_lookups": 3,
"verification_source": "ARC"
},
"dmarc": {
"result": "PASS",
"policy": "quarantine",
"pct": 100,
"alignment": {"dkim": true, "spf": false, "mode": "relaxed"},
"domain": "example.com",
"subdomain_policy": null,
"rua": [],
"ruf": [],
"explanation": "Determined from ARC authentication chain"
},
"arc": {
"result": "PASS",
"chain_valid": true,
"instances": [
{
"i": 1,
"cv": "none",
"auth_results": "dkim=pass spf=pass dmarc=pass",
"signing_domain": "forwarder.com"
},
{
"i": 2,
"cv": "pass",
"auth_results": "dkim=none spf=none dmarc=none",
"signing_domain": "mx.company.com"
}
]
},
"header_analysis": {
"display_name_analysis": {
"from_display_name": "Sender Name",
"from_email": "sender@example.com",
"from_domain": "example.com",
"impersonation_indicators": []
},
"reply_to_analysis": {
"reply_to_present": false,
"reply_to_email": null,
"reply_to_domain": null,
"domain_mismatch": false,
"signed": true
},
"header_anomalies": []
},
"findings": [
{
"id": "DKIM_VIA_ARC",
"severity": "INFO",
"points": 0,
"title": "DKIM verified via ARC chain",
"summary": "Original DKIM signature was verified by forwarder.com.",
"details": "The email was forwarded and the original DKIM signature was replaced with an ARC chain.",
"evidence": {"type": "HEADER", "key": "ARC-Authentication-Results", "value": "dkim=pass"},
"recommendation": "ARC provides trustworthy authentication for forwarded emails."
},
{
"id": "DMARC_POLICY_NONE",
"severity": "MEDIUM",
"points": -6,
"title": "DMARC policy not enforced",
"summary": "Domain publishes p=none, which does not enforce authentication failures.",
"details": "An attacker could spoof this domain and receivers would not reject the message.",
"evidence": {"type": "DNS", "key": "_dmarc.example.com", "value": "v=DMARC1; p=none"},
"recommendation": "Upgrade to p=quarantine or p=reject after monitoring."
},
{
"id": "DKIM_MISSING_DATE_HEADER",
"severity": "LOW",
"points": -2,
"title": "Date header not signed",
"summary": "The Date header can be modified without invalidating the DKIM signature.",
"details": null,
"evidence": {"type": "DERIVED", "key": "dkim.signed_headers", "value": "from:to:subject:message-id"},
"recommendation": "Include Date in h= tag."
}
],
"metadata": {
"source": {"system": "ebi-service", "version": "0.3.0"},
"analysis": {"mode": "FORENSIC", "elapsed_ms": 156},
"raw": {
"header_hash": null,
"body_hash": null,
"evidence_refs": ["ARC-Authentication-Results"]
}
}
}15. Finding ID Registry
This section is normative. Implementations SHOULD use these exact IDs.
15.1 DKIM Findings
| ID | Severity | Points | Condition |
|---|---|---|---|
DKIM_FAIL |
HIGH | -12 | DKIM verification failed |
DKIM_PARTIAL_BODY_SIGNED |
CRITICAL | -25 | DKIM l= tag present; body can be
appended |
DKIM_WEAK_HASH_ALGO |
HIGH | -12 | DKIM uses rsa-sha1 |
DKIM_WEAK_KEY_SIZE |
HIGH | -12 | DKIM key size < 2048 bits |
DKIM_MISSING_FROM_HEADER |
HIGH | -12 | From header not in signed headers (h=) |
DKIM_MISSING_SUBJECT_HEADER |
MEDIUM | -6 | Subject header not in signed headers |
DKIM_MISSING_TO_HEADER |
MEDIUM | -6 | To header not in signed headers |
DKIM_MISSING_DATE_HEADER |
LOW | -2 | Date header not in signed headers |
DKIM_MISSING_MESSAGE_ID_HEADER |
LOW | -2 | Message-ID header not in signed headers |
DKIM_MISSING_REPLY_TO_HEADER |
HIGH | -12 | Reply-To header present but not signed (redirect attack vector) |
DKIM_THIRD_PARTY_SIGNATURE |
INFO | 0 | Signature domain differs from From domain |
DKIM_NO_AUTHOR_DOMAIN_SIGNATURE |
INFO | 0 | DKIM passed but no signature from From domain (common with ESPs) |
DKIM_SIGNATURE_EXPIRED |
HIGH | -12 | Signature x= timestamp has passed |
DKIM_VIA_ARC |
INFO | 0 | DKIM result inferred from valid ARC chain |
DKIM_VIA_AUTH_RESULTS |
INFO | 0 | DKIM result inferred from Authentication-Results header (RFC 8601) |
DKIM_VERIFIED_VIA_ARCHIVE |
LOW | -2 | DKIM verified using archived key < 1 year old (§6.5) |
DKIM_VERIFIED_VIA_STALE_ARCHIVE |
MEDIUM | -6 | DKIM verified using archived key ≥ 1 year old (§6.5) |
SENDER_NO_DKIM_KEYS |
INFO | 0 | Sender domain has no DKIM keys published at common selectors |
15.2 SPF Findings
| ID | Severity | Points | Condition |
|---|---|---|---|
SPF_FAIL |
HIGH | -12 | SPF returned fail (verified sources only) |
SPF_SOFTFAIL |
MEDIUM | -6 | SPF returned softfail (verified sources only) |
SPF_NEUTRAL |
LOW | -2 | SPF returned neutral (verified sources only) |
SPF_NOT_VERIFIABLE |
INFO | 0 | Static .eml lacks envelope context for SPF |
15.3 DMARC Findings
| ID | Severity | Points | Condition |
|---|---|---|---|
DMARC_POLICY_NONE |
MEDIUM | -6 | DMARC policy is p=none (no
enforcement) |
DMARC_POLICY_PARTIAL |
MEDIUM | -6 | DMARC pct < 100 (partial
enforcement) |
DMARC_FAIL |
HIGH | -12 | DMARC evaluation failed |
DMARC_TEMPERROR |
INFO | 0 | DMARC DNS lookup failed or no record published |
15.4 ARC Findings
| ID | Severity | Points | Condition |
|---|---|---|---|
ARC_CHAIN_FAIL |
HIGH | -12 | ARC chain validation failed (cv=fail) |
15.5 Header Analysis Findings
| ID | Severity | Points | Condition |
|---|---|---|---|
DISPLAY_NAME_BRAND_IMPERSONATION |
HIGH | -12 | Known brand name in display name from unrelated domain |
DISPLAY_NAME_DOMAIN_MISMATCH |
MEDIUM | -6 | Display name contains email address different from actual From |
REPLY_TO_DOMAIN_MISMATCH |
MEDIUM | -6 | Reply-To domain differs from From domain |
UNICODE_CONFUSABLE_CHARS |
HIGH | -12 | Unicode characters that visually resemble ASCII in headers |
15.6 Domain Intelligence Findings
| ID | Severity | Points | Condition |
|---|---|---|---|
DOMAIN_MALWARE_DETECTED |
CRITICAL | -25 | Domain flagged for malware or phishing |
DOMAIN_BLOCKLISTED |
HIGH | -12 | Domain on security blocklists (codes 4001–4005) |
DOMAIN_POOR_REPUTATION |
HIGH | -12 | Reputation score below 40 |
DOMAIN_RECENTLY_REGISTERED |
MEDIUM | -6 | Domain recently registered (code 2001) |
DOMAIN_EXPIRED_REGISTRATION |
MEDIUM | -6 | Domain registration expired (code 2002) |
DOMAIN_SSL_ISSUES |
MEDIUM | -6 | SSL/TLS configuration problems (codes 6001–6022) |
DOMAIN_NO_SPF |
LOW | -2 | No SPF record published (code 5015) |
DOMAIN_NO_DMARC |
LOW | -2 | No DMARC record published (code 5016) |
DOMAIN_OFFSHORE |
INFO | 0 | Registered in offshore jurisdiction (code 2008) |
16. Changelog
v0.11.0
NEW: Domain Age via RDAP (§11.4)
DomainAnalysis now includes a whois field
with actual registration dates:
"whois": {
"registration_date": "2010-03-15T00:00:00Z",
"expiration_date": "2025-03-15T00:00:00Z",
"domain_age_days": 5000,
"registrar": "GoDaddy"
}Registration data is fetched via RDAP (Registration Data Access Protocol), the modern HTTPS-based replacement for WHOIS. This works in serverless environments where raw WHOIS (port 43) is blocked. Results are cached to disk for 30 days.
Display improvements:
- Domain age shows actual dates: “Registered March 2010 (13 years ago)”
- Domains ≤7 days old flagged as high-risk phishing indicator
- Falls back to reputation warning codes when WHOIS unavailable
Removed:
WHOIS_API_KEYenvironment variable (paid API replaced with free queries)
v0.10.0
NEW: Domain Intelligence (§11)
This release adds optional domain threat intelligence
analysis. When configured with a
THREATS_API_KEY, EBI queries external APIs
for:
- Domain reputation scores
- Malware and phishing associations
- Blocklist presence
- Registration status (new, expired, offshore)
- SSL/TLS configuration issues
- Infrastructure geolocation
New response field:
domain_details— ContainsDomainDetailsObjectwith analysis offrom_domainandto_domains, ornullif threat intelligence is not configured.
New findings (§15.6):
| ID | Severity | Points |
|---|---|---|
DOMAIN_MALWARE_DETECTED |
CRITICAL | -25 |
DOMAIN_BLOCKLISTED |
HIGH | -12 |
DOMAIN_POOR_REPUTATION |
HIGH | -12 |
DOMAIN_RECENTLY_REGISTERED |
MEDIUM | -6 |
DOMAIN_EXPIRED_REGISTRATION |
MEDIUM | -6 |
DOMAIN_SSL_ISSUES |
MEDIUM | -6 |
DOMAIN_NO_SPF |
LOW | -2 |
DOMAIN_NO_DMARC |
LOW | -2 |
DOMAIN_OFFSHORE |
INFO | 0 |
Notes:
- Domain findings only apply to the
from_domain(sender). To-domain analysis is informational only. - Without
THREATS_API_KEY,domain_detailsisnulland no domain findings are generated. - API timeouts (5s per call, 10s total) degrade gracefully with partial results.
v0.9.0
BREAKING: Scoring method upgraded to EBI_SCORE_V3 (§4.4)
This release recalibrates scoring to match human intuition for business email assessment:
- 80+: Trust it
- 70-79: Raised eyebrow (poor sender hygiene)
- 60-69: Arms crossed (suspicious configuration)
- <60: Fail
Base score changes:
| Status | V2 | V3 | Rationale |
|---|---|---|---|
| AUTHENTIC | 90 | 90 | Unchanged |
| PARTIAL | 55 | 75 | Third-party ESP signing is normal, not suspicious |
| INCONCLUSIVE | 40 | 55 | Unverifiable needs review, not auto-fail |
| FAILED | 15 | 35 | Failed auth is bad, findings add detail |
| UNSAFE | 60 | 25 | Integrity vulnerabilities (l= tag) are critical |
New findings:
DKIM_NO_AUTHOR_DOMAIN_SIGNATURE(INFO) — DKIM passed but no signature from From domain. Common with ESPs (Google Workspace, SendGrid, Marketo). Informational only, no penalty.DMARC_TEMPERROR(INFO) — DMARC DNS lookup failed or no record published. Poor hygiene but not evidence of spoofing.DKIM_VERIFIED_VIA_ARCHIVE(LOW, -2 pts) — DKIM verified using historical key from DKIM Archive (key < 1 year old).DKIM_VERIFIED_VIA_STALE_ARCHIVE(MEDIUM, -6 pts) — DKIM verified using stale archived key (key ≥ 1 year old).
New optional feature: DKIM Archive Fallback (§6.5)
Implementations MAY query the ZK Email DKIM Archive when DKIM verification fails due to key rotation. The archive contains 1.4M+ historical DKIM keys. Archive-verified DKIM receives reduced trust scores: - Recent keys (< 1 year): LOW severity, -2 points - Stale keys (≥ 1 year): MEDIUM severity, -6 points
Finding severity changes:
SENDER_NO_DKIM_KEYS: MEDIUM → INFO. Missing DKIM keys indicate poor hygiene, not spoofing.ARC_CHAIN_FAIL: MEDIUM → HIGH. Broken ARC chain is serious when relying on ARC for auth.
Bug fixes:
- Findings are now deduplicated by ID before scoring. Multiple DKIM signatures no longer generate duplicate findings.
Migration notes:
- Policy thresholds should be recalibrated. Old “block at 40” should become “block at 60”.
- Most legitimate emails now score 75+ instead of 55+.
- Emails from third-party ESPs (Google Workspace, marketing platforms) no longer penalized for lacking first-party DKIM signature.
v0.8.2
- NEW: SENDER_NO_DKIM verdict code (§3.4,
§3.8) — Added distinction between messages lacking
authentication due to sender’s poor email hygiene vs
possible spoofing. When a message has no DKIM signatures and
the sender domain has no DKIM keys published at common
selectors, the verdict is now PARTIAL with code
SENDER_NO_DKIMinstead of FAILED with codeNO_AUTH_MECHANISMS. - NEW: DKIM key discovery (§3.8) — Implementations now check whether sender domains have DKIM keys published at common selectors (selector1, selector2, google, default, etc.) to distinguish between misconfigured senders and potential spoofing.
- NEW: SENDER_NO_DKIM_KEYS finding (§14.1) — MEDIUM severity finding indicating the sender domain has no DKIM keys published. This provides actionable guidance to contact the sender about enabling DKIM.
- CHANGED: NO_AUTH_MECHANISMS verdict (§3.4) — Now only assigned when sender domain HAS DKIM keys published but the message lacks signatures, which is a stronger indicator of spoofing.
- CHANGED: Verdict determination priority (§3.5) — Added rules 3 and 4 to handle SENDER_NO_DKIM vs NO_AUTH_MECHANISMS based on DKIM key discovery.
v0.8.1
- CHANGED: Expired signatures with valid ARC (§3.6) — Expanded the rule for bypassing FAILED verdict on expired signatures. When a valid ARC chain shows the original message was authenticated (even via SPF alone, not just DKIM), expired intermediary signatures no longer force FAILED. This prevents false positives on legitimately forwarded emails where the sender doesn’t use DKIM.
- NEW: DMARC_FAIL_POLICY_NONE verdict code (§3.4,
§3.7) — Added distinction between DMARC failures on
enforcing vs non-enforcing domains. When a domain publishes
p=none, DMARC failures now result in PARTIAL verdict instead of FAILED. This reflects the domain’s explicit policy choice and prevents false positives on consumer email providers. - NEW: ARC_VOUCHED verdict code (§3.4) — Added verdict code for messages vouched by valid ARC chain despite expired signatures.
- CHANGED: Verdict determination priority (§3.5) — Reordered to check DMARC policy before assigning FAILED verdict. Added rule for DMARC_FAIL_POLICY_NONE as PARTIAL.
v0.8
- NEW: Header Analysis (§10) — Added
header_analysisobject for display name spoofing detection, Reply-To analysis, and header anomaly detection. Authentication mechanisms verify domain ownership but not sender intent; header analysis fills this gap. - NEW: Analysis Modes (§Design Goals
addendum) — Explicitly documented
LIVEvsFORENSICmodes with different trust models. DeprecatedBATCHandTESTmodes. - NEW: SPF verification_source field
(§7.1) — Added
verification_sourceenum (DIRECT,ARC,NONE) to make SPF trust provenance explicit. - NEW: DKIM key_size field (§6.1) — Added
key_sizeto signature objects for weak key detection. - NEW: DMARC subdomain_policy field
(§8.1) — Added
subdomain_policyforsp=tag. - BREAKING: Scoring method upgraded to
EBI_SCORE_V2 (§4.4)
- Base scores recalibrated for conservative security posture
- AUTHENTIC: 95 → 90 (reserve 90-100 for zero-finding messages)
- UNSAFE: 75 → 60 (exploitable vulnerabilities warrant human review)
- PARTIAL: 65 → 55 (borderline CAUTION/DANGEROUS)
- INCONCLUSIVE: 50 → 40 (treat as suspicious by default)
- FAILED: 10 → 15 (slight increase for operational reasons)
- HIGH confidence now provides +5 bonus (was 0)
- Penalty cap reduced from -60 to -50
- Finding point values adjusted: CRITICAL -25, HIGH -12, MEDIUM -6, LOW -2
- Added design rationale documentation (§4.4.1)
- CHANGED: DMARC_POLICY_NONE severity raised
(§8.4) — LOW → MEDIUM (-6 pts). Publishing
p=noneexplicitly enables spoofing; this is not a minor configuration weakness. - NEW: DMARC_POLICY_PARTIAL finding
(§8.4) — MEDIUM severity for
pct< 100. Partial enforcement is exploitable. - NEW: DKIM_WEAK_KEY_SIZE finding (§14.1) — HIGH severity for RSA keys < 2048 bits.
- NEW: Header analysis findings (§14.5) —
DISPLAY_NAME_BRAND_IMPERSONATION,DISPLAY_NAME_DOMAIN_MISMATCH,REPLY_TO_DOMAIN_MISMATCH,UNICODE_CONFUSABLE_CHARS. - Added: from_display_name to top-level schema (§2.1) — Surfaces display name for analysis without parsing From header.
- Added: expiry field to DKIM signatures
(§6.1) — Explicit
x=tag value as RFC3339. - Clarified: Score method versioning requirements (§12.5) — Implementations should document score changes during upgrades.
- Updated: Example response (§13) — Reflects v0.8 schema with header_analysis, EBI_SCORE_V2, and updated finding points.
v0.7
- BREAKING: Removed incorrect canonicalization penalties
DKIM_RELAXED_BODY_CANONremoved —relaxed/relaxedis the industry-recommended settingDKIM_RELAXED_HEADER_CANONremoved — penalizing correct behavior was wrong- Emails using relaxed canonicalization now score +10 points higher
- Added header signing checks:
DKIM_MISSING_TO_HEADER(MEDIUM, -7 pts) — To header not signedDKIM_MISSING_DATE_HEADER(LOW, -3 pts) — Date header not signedDKIM_MISSING_MESSAGE_ID_HEADER(LOW, -3 pts) — Message-ID header not signedDKIM_MISSING_REPLY_TO_HEADER(HIGH, -15 pts) — Reply-To header present but not signed (invoice fraud vector)
v0.6
- BREAKING: SPF trust model overhauled (§7.2)
- SPF can ONLY be trusted via valid ARC chain
Authentication-Resultsheader is NOT trusted for SPF (trivially forgeable)- New SPF result:
"UNVERIFIABLE"when no valid ARC chain exists - SPF does not count for/against verdict when unverifiable
- Added
DKIM_FAILfinding (HIGH, -15 pts) for DKIM verification failures (§13.1) - Added
SPF_FAILfinding (HIGH, -15 pts) for ARC-verified SPF failures (§13.2) - Updated verdict determination to exclude unverifiable SPF (§3.5)
- Authentication Results section now shows point deductions inline (e.g., “FAIL (-15 pts)”)
- Clarified that point deductions in display MUST match findings generated
v0.5
- Added
pointsfield to FindingObject (§5.1) showing the numeric score impact of each finding - Verdict banner color now follows score band for visual consistency (red “Dangerous” score = red banner, regardless of verdict status)
- HTML and text reports now display point deductions inline with each finding (e.g., “HIGH (-15 pts)”)
- Added Kapwork copyright and keyoxide link to HTML email footer and spec documentation
v0.4
- Added
request_contextobject withforwarder_emailandreceived_atfields (§2.3) - Request context enables report correlation without exposing additional PII
forwarder_emailidentifies who submitted the email for analysisreceived_atrecords when the analysis request was received (distinct fromtimestampwhich is analysis completion time)- Updated example response to include
request_context
v0.3
- Added Authentication-Results (RFC 8601) as fallback source for DKIM/SPF/DMARC when direct verification fails (§6.4)
- Updated DKIM result derivation priority to include Authentication-Results (§6.2)
- Updated SPF and DMARC sections to reference Authentication-Results as a source (§7.3, §8.2)
- Added new finding:
DKIM_VIA_AUTH_RESULTS - Improved support for forwarded-as-attachment emails where DKIM signatures break in transit
- Expired signatures no longer force FAILED verdict when ARC or Authentication-Results vouches for DKIM (§3.6)
v0.2
- Added comprehensive ARC chain parsing and trust model (§9)
- Documented verdict determination priority order (§3.5)
- Added verdict codes table (§3.4)
- Documented static .eml limitations for SPF (§7.2)
- Added SPF/DMARC result derivation from valid ARC chains (§7.3, §8.2)
- Added new findings:
DKIM_VIA_ARC,SPF_NOT_VERIFIABLE - Clarified DKIM result can come from direct verification OR ARC (§6.2)
- Added
ARC_CHAIN_FAILfinding (§13.4) - Promoted Finding ID Registry to normative (§13)
- Improved example to show ARC-based authentication
v0.1
- Added
scoreobject and scoring method (EBI_SCORE_V1). - Added
message_idto top-level fields. - Renamed
dkim.alignedtodkim.from_domain_matchfor clarity. - Renamed
body_length.presenttobody_length.limitedwith inverted semantics. - Renamed
BODY_LENGTH_LIMITfinding toDKIM_PARTIAL_BODY_SIGNED. - Clarified that partial body signing (l= tag present) is the vulnerability, not its absence.
- Clarified verdict semantics for
UNSAFE. - Improved schema structure and implementation requirements.
- Added common finding IDs appendix with severity guidance.
v0.0
- Initial draft.
17. License and Copyright
© 2026 Kapwork