Package score

Deterministic risk scoring: domain-age check (contextual tiers with stacking and shared-cert discount), certificate downgrade check (+40), and registrar mismatch (+30), mapped onto a four-tier verdict. Sources: internal/score/score.go, internal/score/date.go.


DomainData

type DomainData struct {
    Domain    string // queried domain
    Date      string // YYYY-MM-DD, or sentinel on failure
    Registrar string // "Unknown" when unavailable
    Issuer    string // "Unknown" when unavailable
    Class     string // EV, OV, IV, DV, or Unknown
    SANs      string // comma-joined, or "Unknown"
    AgeDays   int
    AgeKnown  bool
}

Collected triage fields for one domain; the scoring input.


Result

type Result struct {
    Score    int
    Findings []string
    Verdict  string
    AgeFlag  string // display suffix for young suspect domains
}

ClassLong

func ClassLong(c string) string

Human-readable validation name via lookup table: EV to "Extended Validation", OV to "Organization Validated", DV to "Domain Validated", IV to "Individual Validated", anything else to "Unknown".


VerifyDataQuality

func VerifyDataQuality(target, suspect DomainData) error

Fails closed: returns an error when either domain lacks a valid RDAP date, a computable age, or any TLS signal (issuer and class both unknown). The CLI aborts instead of printing a verdict built on missing data.


Score

func Score(target, suspect DomainData) Result

Applies the signals. Identical domains force score zero and LEGITIMATE while still attaching DNS NOTEs. Suspect age is contextual: the full tier (< 30d: 80, 30-89d: 40) needs company (registrar mismatch or cert downgrade), otherwise the next tier down; shared certificate infrastructure (either domain in the other's SANs, exactly or via parent wildcard) halves whatever remains. Target EV/OV versus suspect DV adds 40 as DOWNGRADE; a same-class issuer difference is a display-only NOTE (+0). Case-insensitive registrar mismatch adds 30, skipped when either side is unknown. Suspect-side DNS suppression and NS splits add 50; split-horizon shapes and anything target-side are display-only NOTEs (+0). Identical domains force score zero and LEGITIMATE while still attaching DNS NOTEs.

r := score.Score(target, suspect)
// r.Score, r.Findings, r.Verdict, r.AgeFlag

Verdict

func Verdict(score int) string

Maps the total via threshold table: 51+ phishing, 40+ suspicious, 15+ unrelated/misconfigured, otherwise legitimate.


ValidISODate

func ValidISODate(s string) bool

True only for clean, calendar-valid YYYY-MM-DD strings. Any sentinel or garbage value fails by design.


AgeDays

func AgeDays(ymd string, now time.Time) (days int, ok bool)

Whole days between ymd and now; ok is false when ymd is unparseable.


Licensed under the GNU General Public License v3 - Mark Robillard Jr (MARKMENTAL)