perimeter-scan
Passive external attack-surface scanner. Find your exposed subdomains, expiring certs, missing security headers, and dangling DNS that could be hijacked — before an attacker (or a prospect's security team) does.
One command, no agent, no signup:
$ perimeter-scan example.com
It's passive: it reads public Certificate Transparency logs and DNS, and makes standard web requests to the hosts it finds. No exploitation, no port scanning, no authenticated probing. Only scan domains you own or are authorized to assess.
What it checks
| Check | How |
|---|---|
| Subdomains | enumerated from public Certificate Transparency logs (crt.sh) |
| TLS hygiene | expired, expiring-soon, and self-signed certificates |
| Security headers | missing HSTS / CSP / anti-clickjacking / MIME-sniffing headers |
| Subdomain takeover | dangling CNAMEs pointing at unclaimed third-party services |
Install
$ pipx install perimeter-scan # recommended (isolated)
# or
$ pip install perimeter-scan
Requires Python 3.11+.
Usage
$ perimeter-scan example.com # human-readable report
$ perimeter-scan example.com --json # machine-readable JSON
$ perimeter-scan example.com --min-severity high # only high+ findings
$ perimeter-scan example.com --max-subdomains 100 # scan more hosts
$ perimeter-scan example.com --fail-on high # exit 1 if any high+ finding (for CI)
In CI — fail the build on a new exposure
# .github/workflows/attack-surface.yml
- run: pipx install perimeter-scan
- run: perimeter-scan yourcompany.com --fail-on high --json
As a library
import asyncio
from perimeter_scan import passive_scan, ScanConfig
result = asyncio.run(passive_scan("example.com", config=ScanConfig(max_subdomains=50)))
print(result.risk_score, result.counts_by_severity())
for f in result.findings:
print(f.severity.value, f.asset, f.title)
A one-time scan is a snapshot. Your attack surface changes every deploy.
perimeter-scan is the open-source scanner behind Perimeter.
The CLI tells you what's exposed right now. Perimeter runs it continuously, remembers
what it saw last time, and alerts you the moment something new appears — a fresh
subdomain, a cert about to lapse, a dangling record someone could hijack — so you find
it before anyone else does.
→ Monitor your domain continuously
License
MIT. Contributions welcome.
Metadata
Release files for perimeter-scan 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| perimeter_scan-0.1.0.tar.gz | 17.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| perimeter_scan-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.7 kB
Release files / perimeter_scan-0.1.0.tar.gz
| Download URL | perimeter_scan-0.1.0.tar.gz |
|---|---|
| Size | 17.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
faa68e964396f54c5b76bb39f113e8d8f8722672cc8422fe2bd17ab148a83a84
|
|
BLAKE2b-256 checksum How to use checksums |
656454105e926a531de02d81726d9790cf7b6fb87f99135fa31e8a38b053e489
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / perimeter_scan-0.1.0-py3-none-any.whl
| Download URL | perimeter_scan-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
06cf1f9ecff15a80d88efcf2f1e5a4f7bddc731299209ce1299a17b3ef8534ce
|
|
BLAKE2b-256 checksum How to use checksums |
1f63caa5c3f490ced9c15054769211d6f339b0a14f66e8b4c7748e18208131b5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|