Bump up to version 1.02

This commit is contained in:
markmental 2026-09-09 12:50:34 -04:00
commit 1fe0ac49a5
6 changed files with 129 additions and 38 deletions

View file

@ -1,4 +1,9 @@
# phisherslapper [v1.01]
# phisherslapper [v1.02]
> **Note:** `isthislegit` has been renamed to `phisherslapper`. We do not
> condone physical violence against phishers — but you are warmly encouraged
> to metaphorically slap them with this tool's output in your reply the next
> time they try to scam you.
Domain Impersonation & OSINT Triage Tool.
@ -128,7 +133,7 @@ resort for devices without stored CA certificates.
## Example output
```text
phisherslapper [v1.01]
phisherslapper [v1.02]
Domain Impersonation & OSINT Triage Tool
[*] Comparing: example.com (Target) <---> example.org (Suspect)

84
STYLE.md Normal file
View file

@ -0,0 +1,84 @@
# phisherslapper Code Conventions
This file is the style contract for this repo. Follow it for every change;
reviewers should cite it when requesting revisions.
## 1. No `else` — map lookups for values
Boolean or small-domain forks become table lookups, not branches. Fork
targets stay visible as data, and diffs add rows instead of nesting.
```go
// Good: the two outcomes sit side by side as data.
hostsLine := map[bool]string{
true: fmt.Sprintf(" hosts override: STATIC MAPPING %s -> %s\n\n", ip, domain),
false: " hosts override: none\n\n",
}[hit]
// Bad: an else branch hiding the same two outcomes.
if hit {
fmt.Fprintf(&b, " hosts override: STATIC MAPPING %s -> %s\n\n", ip, domain)
} else {
b.WriteString(" hosts override: none\n\n")
}
```
Ordered or priority mappings use `[]struct{…}` slices scanned in order —
see `verdictScale` (`internal/score/score.go`), `oidClassPriority`
(`internal/cert/cert.go`), and `platformAliases` / `archAliases`
(`compile.go`). Flat `map[K]V` lookups cover the rest — see
`classLongNames`.
## 2. Dispatch tables for goroutines
When a condition selects *behavior* rather than a value — e.g. which
probes to launch — dispatch through `map[bool]func()` instead of an
if/else launch block:
```go
// Good: one dispatch, same concurrency shape either way.
probePaths := map[bool]func(){
true: func() { g.Go(probeShared) },
false: func() { g.Go(probeSys); g.Go(probePub) },
}[sameEndpoint]
probePaths()
```
The table lives next to the errgroup it feeds (`cmd/phisherslapper/main.go`).
## 3. Guard clauses over nesting
Handle the degenerate case first with an early `return` or `continue`;
the happy path stays at the top indent level. Never nest to avoid an
else — flatten instead.
```go
// Good: incomplete input exits before the real work.
if sys == nil || pub == nil || !sys.HasCert || !pub.HasCert {
return false, false, ""
}
```
## 4. Pure functions at the core, I/O at the edges
Parsing, comparison, and scoring take values and return values:
`ParseHostsFile`, `ParseCymruTXT`, `cert.ComparePaths`, `detectSplit`,
`score.Score`. Network access, the clock, and the filesystem live at the
edges (`main.go` orchestration, `LookupASN`, `CheckHostsOverride`).
## 5. Fail-open probes, fail-closed verdicts
Lookup helpers return `(T, bool)` and never surface errors for expected
absence — an unanswered Team Cymru query or a missing hosts file simply
yields `false`. Only `score.VerifyDataQuality` may abort a run, and it
must name the underlying cause(s).
## 6. Tests and hygiene
- Every pure function gets a table-driven unit test (`cases := map[string]…`,
see `internal/dns/dns_test.go`). Live-network behavior is asserted
through fail-open semantics, not flaky integration tests.
- `gofmt -l .` must print nothing; `go vet ./...` must pass.
- Stdlib only, plus `golang.org/x/sync` for the fetch pipeline.
- Every exported symbol carries a doc comment.
- New scoring signals update `--scale`, the README scale table, and `docs/` together — a signal without docs is unfinished.

View file

@ -28,7 +28,7 @@ import (
"phisherslapper/internal/score"
)
const version = "1.01"
const version = "1.02"
const defaultPerCall = 5 * time.Second
const globalTimeout = 15 * time.Second
@ -100,25 +100,30 @@ func fetchDomain(ctx context.Context, client *http.Client, domain string, now ti
pubIP = dc.PublicAddrs[0]
}
if sysIP != nil && pubIP != nil {
if sysIP.Equal(pubIP) {
// Same endpoint both paths: one dial attests for both.
g.Go(func() error {
c, _ := cert.InspectAddr(ctx, sysIP, domain, perCall)
res.sysCert, res.pubCert = c, c
return nil
})
} else {
g.Go(func() error {
c, _ := cert.InspectAddr(ctx, sysIP, domain, perCall)
res.sysCert = c
return nil
})
g.Go(func() error {
c, _ := cert.InspectAddr(ctx, pubIP, domain, perCall)
res.pubCert = c
return nil
})
}
// Shared vs split endpoints dispatch which per-path probes run:
// one dial attests for both when the paths converge.
probePaths := map[bool]func(){
true: func() {
g.Go(func() error {
c, _ := cert.InspectAddr(ctx, sysIP, domain, perCall)
res.sysCert, res.pubCert = c, c
return nil
})
},
false: func() {
g.Go(func() error {
c, _ := cert.InspectAddr(ctx, sysIP, domain, perCall)
res.sysCert = c
return nil
})
g.Go(func() error {
c, _ := cert.InspectAddr(ctx, pubIP, domain, perCall)
res.pubCert = c
return nil
})
},
}[sysIP.Equal(pubIP)]
probePaths()
g.Go(func() error {
a, ok := dns.LookupASN(ctx, sysIP, perCall)
res.sysASN, res.sysASNOK = a, ok

View file

@ -54,7 +54,7 @@ plus <a href="report.html#Verbose">report.Verbose</a> when verbose.</li>
</ol>
<font color="#859900"><h2>Version</h2></font>
<p>Current version string is <tt>1.01</tt>, printed in the banner, JSON
<p>Current version string is <tt>1.02</tt>, printed in the banner, JSON
output, and <tt>--scale</tt> header. Build target:
<tt>CGO_ENABLED=0 go build -ldflags="-s -w" -o phisherslapper
./cmd/phisherslapper</tt>.</p>

View file

@ -4,7 +4,7 @@
<title>phisherslapper Developer Docs - Overview</title>
</head>
<body bgcolor="#002b36" text="#a3b8b9" link="#859900" vlink="#b58900">
<font color="#b58900"><h1>phisherslapper [v1.01]</h1></font>
<font color="#b58900"><h1>phisherslapper [v1.02]</h1></font>
<p>Domain Impersonation and OSINT Triage Tool. Compares a known-legitimate
domain (target) against a suspect domain using DNS resolution, X.509 TLS
certificates, and RDAP registration data, then produces a deterministic risk

View file

@ -287,20 +287,17 @@ func writePaths(b *strings.Builder, label, domain string, d score.DomainData) {
case d.PathChecked:
b.WriteString(" per-path TLS: inconclusive (one endpoint refused port 443)\n")
}
if d.ASNChecked {
status := "agree"
if !d.ASNMatch {
status = "SPLIT"
}
fmt.Fprintf(b, " origin AS: local %s vs public %s [%s]\n", asnOrUnknown(d.SysASN), asnOrUnknown(d.PubASN), status)
} else {
b.WriteString(" origin AS: unattributed (Team Cymru query unanswered)\n")
}
if d.HostsHit {
fmt.Fprintf(b, " hosts override: STATIC MAPPING %s -> %s\n\n", d.HostsIP, domain)
} else {
b.WriteString(" hosts override: none\n\n")
}
asnVerdict := map[bool]string{true: "agree", false: "SPLIT"}[d.ASNMatch]
asnLine := map[bool]string{
true: fmt.Sprintf(" origin AS: local %s vs public %s [%s]\n", asnOrUnknown(d.SysASN), asnOrUnknown(d.PubASN), asnVerdict),
false: " origin AS: unattributed (Team Cymru query unanswered)\n",
}[d.ASNChecked]
b.WriteString(asnLine)
hostsLine := map[bool]string{
true: fmt.Sprintf(" hosts override: STATIC MAPPING %s -> %s\n\n", d.HostsIP, domain),
false: " hosts override: none\n\n",
}[d.HostsHit]
b.WriteString(hostsLine)
}
func asnOrUnknown(s string) string {