saase is a passive-first SaaS exposure intelligence CLI for authorized bug bounty, red-team, and attack-surface research. Give it an organization domain and it correlates DNS verification records, mail and nameserver infrastructure, provider CNAMEs, and optional tenant probes into evidence-backed findings.
The default scan does not contact target SaaS login endpoints. Active HTTP checks require --active or an active profile.
- 266 provider identities backed by 196 TXT rules and 73 DNS infrastructure rules
- TXT, SPF, CNAME, MX, NS, and SRV signals
- Deterministic
low,medium,high, andconfirmedconfidence - Evidence and errors instead of boolean-only guesses
- Explicit candidates for verification-record review and dangling CNAME review
- Bounded concurrency, per-provider rate limits, retries, cancellation, and verified TLS
- Bulk domains, stdin, provider filtering, proxy support, and tenant slug overrides
- Text, JSON, JSONL, and CSV designed for both humans and pipelines
- Live operator-console text UI that prints accepted findings while scans are still running
- Modern TTY-aware color, elapsed-time markers, confidence upgrades, and evidence trees
- Optional SQLite history, versioned evidence cache, and scan diffs
- No telemetry and no target uploads other than the passive sources selected by the user
Requires the Go version declared in go.mod.
go install github.com/codejavu-llc/saase/v2@latestFrom a checkout:
go build -trimpath -o saase .
./saase versionRelease archives and the container image are produced for tagged releases.
# Passive discovery (default)
saase scan -d example.com
# Scan a specific subdomain (including nested subdomains)
saase scan -d subdomains.domain.com
# JSONL for jq or another recon tool
saase scan -d example.com --format jsonl | jq -r '.provider_id'
# Multiple domains from a file or stdin
saase scan -l scope.txt --format json
cat scope.txt | saase scan --stdin --silent
# Domain-based HTTP checks, with no tenant slug probes
saase scan -d example.com --active
# Include tenant slugs derived from the domain
saase scan -d example.com --active --include-slugs
# One provider, with tenant slug probing explicitly enabled
saase scan -d example.com -s slack --active --include-slugs -v
# Supply an extra tenant slug and enable slug probes alongside domain checks
saase scan -d example.com --slug acme --active
# Tenant slug only: skip DNS and services that require a domain
saase scan --slug acme --active
saase scan --slug acme --active -s slack,okta
# Multiple independent tenant slugs (also accepts repeated --slug flags)
saase scan --slug acme,other-team --active --format json
# Save history and reuse evidence for 24 hours
saase scan -d example.com --store recon.db
saase diff --db recon.db --list
saase diff --db recon.dbFor domain lists (-l / --list or stdin), use --resolvers-file to select a random DNS server for each target. All DNS checks for that target, including provider-specific labels, use the selected server. The file accepts one IP address per line, optionally with a port (1.1.1.1:53 or [2606:4700:4700::1111]:53); the default port is 53. Blank lines and lines starting with # are ignored. The supplied resolvers.txt contains example servers.
# Custom resolvers, direct DNS
saase scan -l domains.txt --resolvers-file resolvers.txt --format json -o results.json
# Send DNS over TCP through a SOCKS5 proxy
saase scan -l domains.txt --resolvers-file resolvers.txt --proxy socks5://127.0.0.1:1080
# Read a list from stdin
cat domains.txt | saase scan --stdin --resolvers-file resolvers.txtWithout --proxy, custom resolvers use ordinary DNS (normally UDP, with TCP fallback). With --proxy, DNS uses TCP through SOCKS5/SOCKS5H or HTTP/HTTPS CONNECT; the proxy must allow connections to the resolver's port. Proxy credentials in the URL are supported. Failed proxy connections never fall back to direct DNS. The custom resolver pool and proxy configuration are included in the cache identity. Without --resolvers-file, DNS continues to use the system resolver. The flag requires list input and is rejected for standalone -d or slug-only scans. It affects DNS discovery; HTTP probes retain their existing proxy behavior.
Targets retain their full hostname: domain.com, subdomains.domain.com, and other.domain.com are scanned and cached separately. URLs are accepted and normalized to their hostname. DNS checks and domain-based SSO probes use that hostname; provider-specific DNS labels are checked beneath it. Domain scans skip slug-based HTTP services by default, including with --active, --profile standard, or --profile deep. Add --include-slugs to check tenant names derived from the registrable parent domain, or --slug NAME to enable slug probes and add explicit candidates. HTTP probes still require active mode. -s / --provider only filters providers and does not enable slug probes. Cached results distinguish domain-only scans from scans that include slugs.
With --slug and no domain input, only slug-based tenant/SSO HTTP services are checked. DNS discovery and services requiring a domain (such as Microsoft 365 and Google Workspace) are skipped, even when selected with -s. Use --active, --profile standard, or --profile deep; passive-only slug scans return an error. Each unique slug is reported and cached separately, with no extra candidates generated. Slugs are lowercased and must contain 1–63 ASCII letters, digits, or internal hyphens. When domains are supplied, --slug continues to add tenant candidates to each domain scan. With explicit slugs, stdin is read only when --stdin is supplied.
Tenant existence checks for Freshdesk, Freshworks, Monday, OneLogin, BambooHR, and TalentLMS reject their known generic or missing-tenant redirects. BambooHR is checked at /home/. Okta rejects HTTP 404 and responses containing all three x-rate-limit-limit, x-rate-limit-remaining, and x-rate-limit-reset headers. Accepted tenant responses from these services have medium confidence; negative matches produce no finding. HTTP errors and timeouts do not produce positive findings. Sumo Logic slug detection is temporarily disabled.
The old root-level flags still work during the v2 compatibility window:
saase -d example.com -s slack -v -x http://127.0.0.1:8080-c remains a deprecated alias for --providers-file. Unlike v1, unknown providers are errors rather than silently ignored.
The default text view streams each accepted finding as soon as it is observed, then closes with the aggregated scan summary. It uses a Unicode operator-console layout and enables ANSI color only on an interactive terminal. --no-color keeps the layout without escape sequences. --silent also streams unique tab-separated matches immediately, while JSON, JSONL, CSV, and files written with -o remain deterministic final reports without terminal decoration.
Select a single DNS discovery record type with --record-type:
# TXT only (verification records and SPF), one lookup per uncached domain
saase scan -l domains.txt --record-type txt --resolvers-file resolvers.txt --low-memory --progress --format json -o results.json
# Mail infrastructure only
saase scan -d example.com --record-type mxValues are all (default), txt, mx, ns, cname, and srv, case-insensitive. The filter changes which DNS checks run, the progress plan, and cache identity; JSON metadata includes record_type when filtered. TXT mode makes one lookup per uncached target, although retries or truncated responses can require extra wire queries. NS and CNAME modes also check provider-specific labels, and SRV mode checks five service names. CNAME resolution may issue A/AAAA queries and retains address checks for discovered aliases. This filters DNS discovery; explicitly enabled active profiles or --active still run HTTP probes.
Control DNS concurrency separately from domain workers:
saase scan -l domains.txt --resolvers-file resolvers.txt --concurrency 20 --dns-concurrency 5--dns-concurrency limits DNS lookup operations across all domains and resolvers, including TXT, MX, NS, CNAME, SRV, and address checks. Values are 1–500, or 0 (the default) to inherit --concurrency, preserving the default of 20 simultaneous lookups. It applies to both system and custom resolvers, with or without a proxy. A lookup can generate multiple wire queries, so this is not a packet limit or a queries-per-second limit. --rate-limit applies only to HTTP probes.
Use --progress to see DNS work remaining while scanning, including when saving JSON:
saase scan -l domains.txt --resolvers-file resolvers.txt --dns-concurrency 5 --progress --format json -o results.jsonProgress goes to stderr at most once per second as lookups finish, plus initial and final updates:
[DNS scanning] 100 completed | 215 remaining | 0 skipped (cache) | 315 planned lookups
These are DNS lookup operations, not individual packets or HTTP requests. Remaining includes queued and running lookups across all targets. Extra address checks discovered through CNAME results increase the plan; cached targets are counted as skipped. Failed lookups count as completed attempts, and interrupted scans are labeled accordingly. The option does not change JSON, JSONL, CSV, or saved report contents.
For large domain lists, --low-memory keeps completed findings and errors on disk instead of accumulating the whole report in RAM:
saase scan -l domains.txt --resolvers-file resolvers.txt --dns-concurrency 5 --low-memory --progress --format json -o results.jsonThis mode supports JSON and JSONL. It keeps the same report fields and aggregates evidence per target, but target results are ordered by completion rather than globally sorted. The final output is written after the scan finishes. Only active workers and a bounded result queue retain findings in memory; the input domain list and metadata still scale with the number of targets. Temporary files are allocated under ~/.saase/tmp/ (created with private permissions), independently of TMPDIR. This avoids /tmp tmpfs on systems with disk-backed home directories; the actual backing filesystem depends on where your home is mounted. Linux uses unnamed files (O_TMPFILE) where supported, so they consume disk space while open without appearing in directory listings. The OS reclaims them when their handles close, including on Ctrl+C, SIGTERM, or SIGKILL. Other Unix systems unlink open files immediately; Windows uses delete-on-close handles. No cleanup process deletes other scans’ files. Enough free storage for the results is required. A spool write failure stops the scan. Lower --concurrency to reduce the number of target results held at once; --dns-concurrency only limits DNS lookups.
--low-memory cannot be combined with --store, whose history writer still encodes a complete report in memory. Use -o to save JSON or JSONL. Ordinary JSON/JSONL output and -o alone do not enable bounded result storage.
| Profile | Network behavior |
|---|---|
passive |
DNS resolution only; no HTTP requests to discovery services or target SaaS endpoints |
standard |
Passive signals plus bounded, non-destructive tenant probes |
deep |
Currently equivalent to standard; reserved for reviewed multi-step SSO probes |
--active enables standard active probes without changing the profile name. Domain targets use domain-based probes by default; slug-based probes require --include-slugs or an explicit --slug. Slug-only targets continue to check slug-based services. Active probes never create accounts, claim tenants, send password resets, submit credentials, or attempt exploitation. A detected endpoint is not automatically a vulnerability.
TLS verification is always enabled unless --insecure is explicitly supplied. This choice is recorded in JSON metadata.
Input:
-d, --domain DOMAIN target domain; repeatable or comma-separated
-l, --list FILE target domains, one per line
--stdin read target domains from stdin
-s, --provider PROVIDER provider name or stable ID
--providers-file FILE provider names or IDs, one per line
--org NAME organization override for a single target
--slug SLUG tenant slug; repeatable or comma-separated
without domains: slug-only scan (needs --active)
with domains: enable slugs and add a candidate
Scanning:
--record-type TYPE DNS discovery: all, txt, mx, ns, cname, or srv
--resolvers-file FILE DNS servers for list/stdin scans; random per domain
--profile PROFILE passive, standard, or deep
--active enable bounded tenant HTTP probes
--include-slugs include domain-derived slug probes in active scans
--concurrency N maximum concurrent domain workers (default 20)
--dns-concurrency N simultaneous DNS lookups (0 inherits --concurrency)
--rate-limit N requests/second/provider (default 2)
--timeout DURATION per-operation timeout (default 10s)
--retries N transient retries (default 2)
-x, --proxy URL HTTP or SOCKS proxy
--insecure explicitly disable TLS verification
Output:
--format FORMAT text, json, jsonl, or csv
-o FILE mirror stdout results to a file
-v, --verbose include evidence and print probe errors to stderr
--low-memory spool JSON/JSONL results to temporary disk storage
--progress show completed/remaining DNS lookups on stderr
--silent print target, provider ID, and endpoint only
--show-sensitive-evidence
do not redact verification tokens
--store FILE persist reports and versioned cache in SQLite
--cache-ttl DURATION evidence cache lifetime (default 24h)
Run saase -h, saase providers list, or saase rules validate for live information.
Every structured finding includes a schema version, target, stable provider ID, category, confidence, detector, observation time, and one or more evidence records. Verification tokens are redacted by default.
high: an explicit verification record or strong provider-positive responsemedium: provider-specific DNS infrastructure or a validated tenant endpointconfirmed: at least two independent signal typeslow: reserved for ambiguous evidence; low-confidence guesses are not emitted by current built-in detectors
risk_lead is triage guidance, not a vulnerability verdict:
verification_record_review: confirm that the SaaS relationship and TXT record are still requiredpotential_dangling_tenant: a provider CNAME target did not resolve and needs manual validationpublic_tenant_endpoint: a bounded active check located a likely tenant URL
Exit codes are 0 for a usable completed scan (including zero findings), 1 for invalid input/configuration, and 2 when all primary discovery probes fail.
Built-in rules live in rules. providers.yml contains exact TXT/SPF patterns, dns.yml contains infrastructure suffixes, and metadata.yml describes active-only providers. Validate a modified catalog with:
saase rules validate --rules-dir ./rules
go test ./internal/catalogSimple fingerprints belong in YAML. Stateful flows require a reviewed Go detector, deterministic positive and negative fixtures, rate-limit behavior, and proof that the request cannot mutate vendor state. See CONTRIBUTING.md.
go test ./...
go test -race ./internal/...
go vet ./...
go test -run '^$' -bench . ./internal/engineThe CI workflow also runs rule validation, vulnerability scanning, cross-platform builds, and static analysis. Release artifacts include checksums, SBOMs, and signed checksum files.
Use saase only on assets you own or are explicitly authorized to assess. Respect program scope, provider terms, and rate limits. Do not treat discovery evidence as authorization to access a tenant or attempt a takeover. See SECURITY.md for private vulnerability reporting.
Copyright © CodejaVu. All rights reserved. Public source visibility does not grant permission to copy, modify, redistribute, or create derivatives. See COPYRIGHT.
The built-in rule catalog incorporates BSD-3-Clause material from mubix/saas-enum; required attribution is preserved in THIRD_PARTY_NOTICES.md.