You run this tool inside your network with your RADIUS shared secret, often on
a shared jump box. That only works if you can answer "what does this thing do
to my network and my secrets" without reading the code. This page is that
answer — the threat model first, then how to report a problem, then how we
keep the supply chain checkable.
- RADIUS traffic to the server you name, and nothing else. Access-Request
and Status-Server packets out (UDP/1812 for radius test, TLS on TCP/2083
for radsec test), replies in. Exactly what a switch or access point would
send — the probe is a NAS as far as the server can tell.
- No telemetry, no phone-home, no update check. The free tool never
contacts authhound.com or any other host. The only network peer of a run is
the RADIUS server on the command line. (connect, the paid tier, is
explicitly opt-in and prints what it would do; in this open-source tool it
does nothing else.)
- Bounded rate. A hard-coded rate ceiling in the runner caps how fast
packets leave, and --count is capped at 50 iterations with an enforced
interval floor. There is no flag, environment variable, or config file that
raises either — the probe cannot be turned into a load generator or used to
hammer someone else's server.
Nothing. The probe is a one-shot process:
- no config files written, no cache, no history, no state directory;
- no daemon or watch mode — when the run ends, the process and everything it
knew are gone;
- secrets read from a file (--secret-file / --password-file) stay on the
file you own; the probe only ever reads them.
- Never captures packets or opens raw sockets — it sends its own requests
and reads its own replies, nothing else on the wire.
- Never changes anything on the RADIUS server. Authentication requests
are read-only from the server's point of view; the probe has no
provisioning, CoA, or accounting-write capability.
- Never completes a second factor. If the server issues an MFA challenge,
the probe reports that boundary and stops — completing a push/OTP from an
unattended tool would mean holding a live MFA secret, which it refuses to do.
- Never proxies or forwards authentication for anything else.
- Never sends your secrets anywhere except inside the RADIUS/EAP exchange
they are for (see below).
How credentials are handled
- Inputs stay off the command line by default. The shared secret and any
password can come from a file (refused if group/world-readable on unix), an
environment variable, standard input, or a no-echo interactive prompt — the
inline --secret/user:pass forms work but print a warning on a terminal,
because they leak into shell history and ps. Precedence and details:
README — Where credentials come from.
- Secrets are used only to build protocol messages. The shared secret
signs/validates RADIUS packets; passwords are hidden per RFC 2865 (PAP) or
used inside the TLS tunnel (PEAP/TTLS) or as MSCHAPv2 responses — never
transmitted in the clear outside the protocol that carries them.
- Secrets never appear in output. Not in text output, --json, hints,
error messages, or panics. This is enforced by tests (including a dedicated
leak test in cmd/authhound-probe/leak_test.go) and asserted again by the
Docker smoke test, which greps every output surface for the secret.
- The EAP-TLS/RadSec private key is read from disk only to complete the
TLS handshake; it is never transmitted (TLS never sends private keys) or
logged.
- Environment variables are convenient but readable by other processes running
as the same user — for hostile multi-user boxes, prefer a chmod 600 file
or the prompt.
TLS and certificate posture — an honest note
The probe's TLS handshakes (PEAP/TTLS/EAP-TLS tunnels and RadSec) deliberately
run with certificate verification disabled at the TLS layer, because the probe
is a diagnostic: its job is to capture the certificate the server presents
and report on it — expiry, chain completeness, name — even (especially) when
that certificate is broken. A normal TLS client would abort on the broken cert
and tell you nothing.
What that means in practice:
- The dedicated certificate checks (server-cert, radsec-cert) do the
verification as reporting: expiry and chain problems FAIL/WARN, and the
certificate name is validated against --server-name — a mismatch is a
FAIL. If --server-name is omitted, name validation is skipped and the
probe says so with a WARN (name_validation: "skipped" in --json); it
never reports an unqualified "valid" for a name it didn't check.
- The credential-carrying checks (PEAP/TTLS) therefore complete their exchange
even against a server presenting an untrusted certificate. The inner
credential is protected by the method itself (MSCHAPv2 challenge-response
never sends the password; TTLS-PAP sends it only inside the tunnel), but a
sufficiently positioned attacker who can intercept RADIUS and knows your
shared secret could present their own tunnel endpoint. Use a dedicated
least-privilege test account — the README says this everywhere credentials
come up — and treat --server-name plus the server-cert check as your
impersonation tripwire.
Verify what you downloaded
Every release artifact is checksummed and keyless-signed with Sigstore cosign,
bound to this repo's tagged release workflow via OIDC and logged in the public
Rekor transparency log — you can prove a binary came from our CI and was not
tampered with, without trusting a long-lived key. Step-by-step commands
(including Windows): README — Verify your download.
Every archive also ships an SPDX SBOM, so when a CVE drops you can check your
exposure in seconds.
Reporting a vulnerability
- Use GitHub private vulnerability reporting — Security →
"Report a vulnerability"
on this repo. It reaches the maintainer privately and tracks the fix.
- Please don't open a public issue for anything you believe is exploitable.
You'll get an acknowledgement within 72 hours. Confirmed vulnerabilities get a
GitHub Security Advisory and a patch release; the SBOMs let you determine your
exposure without waiting on us. We won't take legal action against good-faith
research done against your own infrastructure.
Dependencies & supply chain
Minimal-dependency policy. The probe is stdlib-first Go. The dependency
tree is one direct module — golang.org/x/term, for no-echo secret prompts —
plus its golang.org/x/sys transitive, both maintained by the Go team. Every
dependency we don't have is a CVE stream we never have to watch; proposals
that add a dependency need to explain why ~50 lines of stdlib code can't do
the job. (MD4, needed for MSCHAPv2 interop, is vendored from the frozen
golang.org/x/crypto/md4 into internal/md4 with its license and RFC 1320
test vectors rather than pulling in the whole module.)
Detection.
- Every PR runs govulncheck in CI and
fails on findings that are reachable from our code — call-graph analysis,
not just version matching, so a red check always means something real.
- A daily scheduled scan runs the same check against main, catching CVEs
published against dependencies (or the Go standard library) we haven't
touched in months. Findings automatically open a tracking issue.
- PRs that change the dependency manifest additionally run GitHub's
dependency review, which flags newly introduced vulnerable versions and
license changes.
Updates. Dependabot opens PRs for Go modules and GitHub Actions weekly;
security updates arrive immediately. Nothing auto-merges — every bump gets a
human review, because real supply-chain attacks ride new versions and Go's
checksum database already prevents tampered re-publishes of existing ones.
The full policy is in RELEASING.md.
Build pipeline. Every GitHub Action in our workflows is pinned to a full
commit SHA (a hijacked version tag on a popular action is the likeliest
supply-chain hole in a small repo), and the workflow GITHUB_TOKEN is
read-only except in the release job. Releases are built by GoReleaser in CI —
no artifacts are ever uploaded by hand — and every release ships:
- keyless (Sigstore) cosign signatures, so you can verify artifacts came from
this repo's release workflow (see "Verify your download" in the README);
- an SPDX SBOM per archive, so when a CVE drops you can check in seconds
whether a release you deployed contains the affected module.
What we promise. Not zero CVEs — nobody can promise that honestly. What
we promise is fast, loud handling: if a vulnerability ships in a release, we
publish a GitHub Security Advisory and a patch release, and the SBOMs let you
determine your exposure without waiting on us.