Commit Graph
7 Commits
Author SHA1 Message Date
Yogesh OjhaandClaude Opus 5 b0a47ba087 feat(oast): an instance-owned callback server, and a tier that pays the wait once
Out-of-band testing is an instance setting — `off`, `self_hosted` or
`public` — with the server, the token (the INTERACTSH API key row) and the
wait resolved once in `shared/services/oast.py` and read on the stage
thread. A mode that cannot be used is off and says why: self-hosted with no
server, or public without the disclosure acknowledged. A fresh instance is
off. `POST /oast/test` asks a public address for `/poll` over HTTPS and
reports that the server answered, never that OAST works.

The engine keeps one switch. `FieldNeeds` puts the resource key on the
field and `GET /engines/catalog` fills `unavailable_reason`, so the builder
renders the instance reason beside a switch that stays settable.

Measured on nuclei v3.11.1: with `-no-interactsh` a check that needs a
callback still loads and the request is refused — 0 requests, exit 0. So
the batch reported COMPLETED and every origin in it read `scanned` for
checks that never touched the target. The selection now drops them when
nothing is listening, one coverage row reports the count and the reason,
and `Tier.OAST` is left out of the planned tiers.

Membership is what a check references, not what it is tagged: 615 of the
12,586 official rows reference an interactsh placeholder and 233 of those
carry no `oast` tag. `vuln_templates.needs_oast` is parsed at index time; a
library resync fills the column.

The wait is paid on out-of-band batches alone, which sweep for 30 minutes
so it is paid fewer times, and the tier names the server on its own rows.
A finding's callback is rendered on the sheet from one label map; the list
path is bared to the metadata.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 20:53:01 +05:30
Yogesh OjhaandClaude Sonnet 5 7a960f1de1 fix(net): a malformed URL port is rejected, not raised, everywhere .port is read
urllib.parse.SplitResult.port raises ValueError when the authority
carries a non-numeric port; nine call sites read it unguarded, so one
malformed URL (a stray quote, a client typo) turned into an unhandled
exception in the middle of a scan, a report, an interest rule or a
proxy dial. shared/utils/net.py:url_port makes the read itself the
failure boundary; unreadable_port names the bogus text so the naabu
and banner proxy paths can report what they refused rather than
retrying against a host string that dropped its port silently.

shared/definitions/ports.py:SCHEME_PORTS replaces four private copies
of the same http/https default-port map (endpoints.py, endpoint_judge,
scan_surface/normalize, nuclei's parser, dalfox's parser).

Also renames live_hosts to answered_hosts in the report source and the
interest context, and lifts the asset query's inline is:web predicate
into predicates.answered() — the existing distinction between is:live
(2xx) and is:web (answered at all) was already the rule; live_hosts
was the one name still claiming the stronger one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VckaDx6mBB4TsG76u61gvv
2026-09-16 08:04:06 +05:30
Yogesh OjhaandClaude Opus 5 d03b29f186 fix(ipv6): give tools an authority they can parse
Three places built a probe target as f"{ip}:{port}". ip_addresses holds
IPv6 — seed_resolution writes both a and aaaa records — so http_probe,
origin_probe and the vulnerability surface handed httpx and nuclei
2001:db8::1:443, which is neither a host nor a host and port. The httpx
parser then made it worse: rsplit(":", 1) turned a bare v6 address into
2001:db8: before it was stored as the asset's host.

The endpoint layer already got this right in two places with the same
inline expression. That expression is now bracketed() in shared/utils/net
alongside host_port() and split_host_port(), and all five sites use it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5roHYDYzSPRNaTkn8hSkJ
2026-09-13 11:34:01 +05:30
Yogesh OjhaandClaude Opus 5 f178a5952f fix(proxy): carry the scan's proxy and headers into the last three tools
Three tools took the scan's network config and dropped part of it.

The banner prober spoke SOCKS5 to whatever proxy it was given. A scan
context holding an http:// proxy made every handshake fail, and the stage
reported "no banner returned by N ports" rather than a problem. It now
speaks SOCKS5 or HTTP CONNECT by scheme (tools/banner/proxy.py, tested
against a stub proxy), and refuses a scheme it cannot carry before any
port is touched instead of dialling direct.

naabu was handed the resolved URL whole. Its -proxy is socks5 only and
takes no inline credentials, so http:// or any password quietly did not
apply. proxy_args() renders -proxy host:port with -proxy-auth beside it,
and names the reason when the proxy cannot be expressed, which port_scan
and host_discovery report as partial. -passive is an HTTPS call to
Shodan, so it goes through proxy_env instead, which passive_ports was not
passing at all.

wafw00f got the proxy but never the headers, so an authenticated surface
was fingerprinted unauthenticated. It takes a file rather than a value,
so the run writes one 0600 temp file and removes it after.

proxy_env moves to shared/services/proxy_resolve.py now that naabu wants
it too, and host_port() lands in shared/utils/net.py for the bracketed
IPv6 authority CONNECT needs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5roHYDYzSPRNaTkn8hSkJ
2026-09-13 11:25:48 +05:30
Yogesh OjhaandClaude Opus 5 e7a5359624 feat(web-assets): say when another target carries the same value
A row states that a favicon, page title, body hash, certificate, CNAME
target or address is also held by a host under a different target, and
the popover names the peers and pivots to the query that returns them.
Read against the estate, so a row on a single-scan page still reaches
the other targets.

The refusal is the feature. Measured across four unrelated estates, all
five cross-target values were a provider's and none is drawn. A carrier
on a SHARED_EDGE_HOSTS CNAME vouches for no identity. generic_page()
refuses a title that restates the status of the rows carrying it, the
soft-404 phrases and the curated edge interstitials, and it gates the
three page-derived kinds. cert_covers() requires a certificate to name
the host presenting it, so *.okta.com on a tenant is not a link. JARM
spanned all four estates and is not a cross link.

Each value is judged once before the row loop and _crossing() refuses to
fetch carriers for a value one target holds: a wildcard CNAME with 8,679
carriers cost 4.6s a page, now ~80ms.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxAonr95Uj7Bdu4CTMCwx9
2026-09-12 23:35:34 +05:30
Yogesh OjhaandClaude Opus 5 48c0ebeaf3 fix(whois): registry lookups failed for subdomains and private ranges
A registry only holds the registered name, but the lookup asked for the
target verbatim, so every subdomain target 400'd at the registry:
scanme.nmap.org went to the .org RDAP server, which has no such object.
Any target below the apex — api.example.com, www.bbc.co.uk — therefore
carried a permanent "WHOIS failed" badge. Lookups now ask for the
registrable domain, so bbc.co.uk is reached through the public-suffix
table rather than the last two labels.

Private and reserved address space has no registry record at all. That
was reported as a failure carrying the RDAP library's own wording
("IPv4Address 192.168.0.0/24 is private"), which put an error in the
activity feed and a red badge on the target for a fact about the
address. It is now recognised before any query is made and recorded as
not applicable, with a sentence that says why. The target page already
worded this correctly for a single private address; it now recognises a
private range too.

One lookup key is shared by the cache, the stored record and the API
read-back, so a domain and its subdomains resolve to one registry record
instead of re-querying on every miss.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6LcH8uUddjJbKrahf34ys
2026-09-06 21:46:46 +05:30
Yogesh Ojha 69239333bc Refactor validation and DNS tools, implementation of onboarding wizard
- Added `clean_name` function to `validation.py` for name validation with max length.
- Simplified docstrings in `dnsx` client, parser, and service files for clarity.
- Enhanced error handling in `ViewDNSService` to prevent disabling keys on rate limits.
- Improved WHOIS service to handle concurrent inserts gracefully with integrity checks.
- Updated `Settings` class in worker config to inherit from `BaseAppSettings`.
- Added new dependencies in `pyproject.toml` for `apprise` and `cryptography`.
2026-06-21 21:56:20 +05:30