Authorized bug-bounty recon scanner. Fetches a target page + its linked
JavaScript (and optionally .map files), runs deterministic detection rules
for likely-sensitive artifacts (tokens, keys, JWTs, internal endpoints,
suspicious comments), and can optionally hand a masked summary to an
OpenAI model for triage.
Only use this on targets you are explicitly authorized to test (your own apps, in-scope bug bounty programs, lab environments, etc.). A scope allowlist is supported and recommended.
cd ~/Desktop/PageSourceAnalyzer
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # only needed if you want --ai# Single page, deterministic scan only.
python scanner.py https://example.com
# With AI triage (masked findings only are sent to the model).
python scanner.py https://example.com --ai
# AI triage is also saved as Markdown under reports/ai/ by default.
python scanner.py https://example.com --ai --ai-report-dir reports/ai
# Crawl the target same-origin, up to 25 pages, depth 2, 0.5s between requests.
python scanner.py https://target.example \
--crawl --max-pages 25 --max-depth 2 --delay 0.5
# Use a scope allowlist so you can't accidentally hit out-of-scope hosts.
python scanner.py https://target.example --scope scope/target.txt
# Persist findings to SQLite so re-runs print "new findings vs previous".
python scanner.py https://target.example --db reports/history.sqliteEvery run also writes a full JSON report to reports/<host>-<timestamp>.json.
When --ai is enabled, the AI triage is saved separately as
reports/ai/<host>-<timestamp>-ai-triage.md. Use --show-ai if you also
want the full AI triage printed in the terminal. Use --quiet to hide the
progress/loading output.
--scope path/to/file.txt enables a strict allowlist. Format:
# one host per line, comments allowed
target.example
api.target.example
# subdomains of listed hosts are also allowed
If the target URL or any linked JS host is not in scope, the scanner refuses to fetch it.
Deterministic rules (regex + entropy):
- Cloud / SaaS keys: AWS (
AKIA/ASIA), Google (AIza), Slack (xox*), GitHub (ghp_, etc), Stripe (sk_live_,pk_live_), SendGrid, Mailgun, Twilio account SIDs - JWTs,
Authorization: Bearer …,Basic …headers - PEM
-----BEGIN … PRIVATE KEY-----blocks apiKey = "…"/password: "…"/client_secret = "…"assignments- Emails, RFC1918 internal IPs, full URLs
- Interesting endpoint paths under
/api/,/admin/,/internal/,/debug/,/dev/,/staging/,/graphql/,/v1/, … sourceMappingURL=…references, and (with default settings) the actual.mapfiles plus their listed original source paths- Firebase realtime DB URLs, S3 bucket URLs
- Suspicious HTML comments:
TODO,FIXME,password,secret,do not commit,remove before prod, etc. - High-entropy strings (>=4.2 bits/char) inside quoted literals,
flagged as
mediumfor manual validation
Static review of inline and external JavaScript for the classic OWASP WSTG-CLNT-03 pattern: an untrusted source flowing into a dangerous sink. No payloads are sent, no scripts are executed.
Sinks detected:
element.innerHTML/outerHTMLassignments,insertAdjacentHTMLdocument.write/document.writelneval(...),new Function(...),setTimeout("...string...")/setInterval("...string...")- jQuery sinks:
.html(),.append(),.prepend(),.after(),.before(),.replaceWith(), and selectors built by string concatenation ($("div[id=" + t + "]")) - React
dangerouslySetInnerHTML - Vue
v-html, Angular[innerHTML], AngularbypassSecurityTrust* - Direct
location/location.hrefassignment
Sources detected:
location.hash,location.search,location.href,location.pathnamedocument.URL,document.documentURI,document.baseURI,document.referrer,document.cookiewindow.namelocalStorage.getItem/sessionStorage.getItemURLSearchParams/.searchParamswindow.addEventListener("message", ...)(postMessage receiver)
Severity:
high— a source appears within ~600 characters of a sink in the same file (likely tainted data flow worth manual review).medium— a sink is fed something dynamic (string concatenation or template literal) but no source is nearby.- Standalone sinks/sources are intentionally not reported, because
bundled framework code contains them everywhere and they drown the
signal. Disable with
--no-dom-xssif you want to skip the pass.
After fetching a page the scanner inspects the response headers for mitigations that matter for HTML injection / DOM-XSS:
missing_csp_header— noContent-Security-Policyheader setcsp_unsafe_inline—'unsafe-inline'inscript-src/default-srccsp_unsafe_eval—'unsafe-eval'anywhere in the policy
Disable with --no-header-checks.
- It does not try to use any discovered credential.
- It does not send raw secret values to the AI model — only masked previews (first 6 + last 4 chars) and short context snippets.
- It does not fuzz or send active exploitation payloads.
- Run the scanner against an in-scope target.
- Review
reports/<host>-<timestamp>.json. Sort byseverity_hint: high. - Manually validate before reporting:
- Is the value a placeholder (
YOUR_API_KEY_HERE,xxx,changeme)? - Is it a public/publishable key (e.g.
pk_live_…for Stripe) that is supposed to be client-side? - Is the endpoint actually reachable without auth?
- Is the source map exposing readable original source?
- Is the value a placeholder (
- File a responsible-disclosure report using the skeleton in this README (or the one the AI triage step produces).
# Exposed Sensitive Information in Client-Side Source
## Summary
Sensitive information appears to be exposed in client-side HTML/JavaScript.
## Affected Asset
https://target.example/static/app.js
## Evidence
Finding type: <jwt | api_key | source_map | internal_endpoint | …>
Location: external JavaScript
Masked value: eyJhbG...abcd
## Steps to Reproduce
1. Visit https://target.example
2. Open the linked JavaScript asset above
3. Search for `token`, `secret`, `apiKey`, or the masked value shown
## Impact
An attacker may discover internal API endpoints, credentials, or tokens
that should not be exposed client-side, enabling further enumeration or
unauthorized access.
## Recommendation
Remove secrets from client-side code, rotate any exposed credentials,
restrict access to source maps, and move sensitive operations server-side.When --db reports/history.sqlite is passed, each finding is keyed by
(target, source_url, location, finding_type, match). Re-running on the
same target prints how many findings are new since previous runs — useful
for monitoring a target over time without re-reading every report.
- Wayback Machine URL import
- SARIF output (GitHub Security tab)
- HTML report generator
- Slack/Discord webhook notifications
- GitHub dork export for related code disclosures