Wuhp commited on
Commit 路
0ae56fc
1
Parent(s): 698776e
Support 30-target authorized active batches and test validation
Browse files- app/index.html +2 -2
- app/main.py +2 -2
- tests/test_active_batch_size.py +21 -0
app/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
| 12 |
<section class="card"><h2>Scope assets</h2><p class="muted">Select multiple web assets below. Your selection is retained when you change filters. Up to 1,000 queued sequentially.</p><div class="controls"><div class="field"><label>Search</label><input id="search" placeholder="Hostname / description" oninput="render()"/></div><div class="field"><label>Minimum max. severity</label><select id="severity" onchange="render()"><option value="all">All</option><option value="medium">Medium+</option><option value="high">High+</option><option value="critical">Critical only</option></select></div><div class="field"><label>Maximum resolved reports</label><input id="reports" type="number" min="0" placeholder="Any" oninput="render()"/></div></div><div class="controls" style="margin-top:13px"><label><input id="bounty" type="checkbox" onchange="render()"/> Reward eligible only</label><label><input id="compatible" type="checkbox" onchange="render()"/> ZAP-compatible only</label></div><div class="controls" style="margin-top:14px"><button class="secondary" onclick="selectFiltered()">Choose all matching filters</button><button class="secondary" onclick="selectFirst40()">Choose first 1000 matching</button><button class="secondary" onclick="clearSelection()">Clear selection</button><span id="selectionCount" class="tag">0 selected</span></div><div id="selectedTargets" class="controls" style="margin-top:12px"></div><div class="stats" style="margin:15px 0"><div class="stat"><b id="total">0</b><small>Imported assets</small></div><div class="stat"><b id="filtered">0</b><small>Filtered assets</small></div></div><div class="scroll"><table><thead><tr><th>Choose</th><th>Asset / description</th><th>Type</th><th>Max. severity</th><th>Bounty</th><th>Resolved</th></tr></thead><tbody id="rows"></tbody></table></div></section>
|
| 13 |
</div></div><div class="pane" id="pane-settings" hidden><section class="card"><h2>Offline JWT review</h2><div class="field" style="margin-top:14px"><label>Offline JWT metadata inspection (never saved)</label><textarea id="jwt" rows="2" placeholder="Paste a test JWT here"></textarea><button class="secondary" onclick="inspectJwt()">Inspect JWT locally on backend</button><pre id="jwtResults" class="results"></pre></div></section>
|
| 14 |
<div class="grid"><section class="card"><h2>Account profiles (session only)</h2><div class="controls"><div class="field"><label>Account label</label><input id="aname" placeholder="Test account A"/></div><div class="field"><label>Exact host</label><input id="ahost" placeholder="api.example.org"/></div></div><div class="field" style="margin-top:10px"><label>Cookie header (not persisted)</label><input id="acookie" type="password" autocomplete="off" placeholder="session=..."/></div><button style="margin-top:12px" onclick="addAccount()">Add account</button><p class="muted">Do not use other users' credentials. Cookies stay in backend process memory, not disk.</p></section>
|
| 15 |
-
</div><section class="card"><h2>General settings</h2><p class="muted">Limits apply per scan job, not globally across all browser sessions.</p><div class="toolbar"><div class="field"><label>Pipeline order</label><input id="settingsOrder" value="gau, katana, httpx, zap-passive, nuclei, ffuf, arjun, dalfox, sqlmap"/></div><div class="field"><label>Discovery endpoint budget</label><input id="settingsLimit" type="number" min="1" max="2000" value="100"/></div><div class="field"><label>ZAP total request budget</label><input id="settingsBudget" type="number" min="1" max="2000" value="100"/></div></div><p class="muted">Settings are scoped to this browser tab. Modules execute sequentially in the specified order.</p><button class="secondary" onclick="applySettings()">Apply pipeline settings</button></section><section class="card"><h2>Advanced settings 路 Individual modules</h2><div class="toolbar"><div class="field"><label>HTTPX: URLs per job</label><input id="settingsHttpx" type="number" value="10" min="1" max="100"/></div><div class="field"><label>Katana: maximum depth</label><input id="settingsKatana" type="number" value="1" min="1" max="5"/></div><div class="field"><label>ZAP: max discovered URLs</label><input id="settingsZap" type="number" value="5" min="1" max="100"/></div></div><div class="grid" style="margin-top:14px"><div><h2>FFUF 路 Content discovery</h2><p class="muted">Bounded 10-path dictionary, one request per second, single-target consent required.</p></div><div><h2>Arjun 路 Parameter discovery</h2><p class="muted">Bounded per-target parameter discovery; bounded by a process timeout.</p></div><div><h2>Dalfox 路 XSS review</h2><p class="muted">Single-target XSS review; output requires manual verification.</p></div><div><h2>SQLmap 路 SQL injection review</h2><p class="muted">Single parameterized target, low-risk boolean tests; no database extraction.</p></div></div><div class="controls" style="margin-top:13px"><label><input id="modFfuf" type="checkbox"/> FFUF</label><label><input id="modArjun" type="checkbox"/> Arjun</label><label><input id="modDalfox" type="checkbox"/> Dalfox</label><label><input id="modSqlmap" type="checkbox"/> SQLmap</label></div><p><label><input id="activeConsent" type="checkbox"/> I have verified this program authorizes active tests against this selected asset.</label></p><p class="notice">These tools may run in an explicitly authorized batch of up to
|
| 16 |
<p id="message" class="muted" role="status" aria-live="polite"></p></main>
|
| 17 |
<script>
|
| 18 |
let assets=[],selectedIds=new Set(),visibleWebIds=[],scanJobs=[],findings=[],currentTab="scope";
|
|
@@ -52,7 +52,7 @@ async function scan(){
|
|
| 52 |
await refresh();const ids=Array.from(selectedIds).slice(0,1000);
|
| 53 |
if(!ids.length)return message('No valid selected assets remain. The Space may have restarted; re-import the CSV and select targets again.',true);
|
| 54 |
let modules;try{modules=enabledModules();validateNumericSettings()}catch(e){return message(e.message,true)}
|
| 55 |
-
if(!modules.length)return message('Choose at least one pipeline module.',true);const active=modules.some(m=>['ffuf','arjun','dalfox','sqlmap'].includes(m));if(active&&ids.length>
|
| 56 |
if(!el('policy').checked)return message('Confirm the program policy first.',true);
|
| 57 |
try{
|
| 58 |
let r;
|
|
|
|
| 12 |
<section class="card"><h2>Scope assets</h2><p class="muted">Select multiple web assets below. Your selection is retained when you change filters. Up to 1,000 queued sequentially.</p><div class="controls"><div class="field"><label>Search</label><input id="search" placeholder="Hostname / description" oninput="render()"/></div><div class="field"><label>Minimum max. severity</label><select id="severity" onchange="render()"><option value="all">All</option><option value="medium">Medium+</option><option value="high">High+</option><option value="critical">Critical only</option></select></div><div class="field"><label>Maximum resolved reports</label><input id="reports" type="number" min="0" placeholder="Any" oninput="render()"/></div></div><div class="controls" style="margin-top:13px"><label><input id="bounty" type="checkbox" onchange="render()"/> Reward eligible only</label><label><input id="compatible" type="checkbox" onchange="render()"/> ZAP-compatible only</label></div><div class="controls" style="margin-top:14px"><button class="secondary" onclick="selectFiltered()">Choose all matching filters</button><button class="secondary" onclick="selectFirst40()">Choose first 1000 matching</button><button class="secondary" onclick="clearSelection()">Clear selection</button><span id="selectionCount" class="tag">0 selected</span></div><div id="selectedTargets" class="controls" style="margin-top:12px"></div><div class="stats" style="margin:15px 0"><div class="stat"><b id="total">0</b><small>Imported assets</small></div><div class="stat"><b id="filtered">0</b><small>Filtered assets</small></div></div><div class="scroll"><table><thead><tr><th>Choose</th><th>Asset / description</th><th>Type</th><th>Max. severity</th><th>Bounty</th><th>Resolved</th></tr></thead><tbody id="rows"></tbody></table></div></section>
|
| 13 |
</div></div><div class="pane" id="pane-settings" hidden><section class="card"><h2>Offline JWT review</h2><div class="field" style="margin-top:14px"><label>Offline JWT metadata inspection (never saved)</label><textarea id="jwt" rows="2" placeholder="Paste a test JWT here"></textarea><button class="secondary" onclick="inspectJwt()">Inspect JWT locally on backend</button><pre id="jwtResults" class="results"></pre></div></section>
|
| 14 |
<div class="grid"><section class="card"><h2>Account profiles (session only)</h2><div class="controls"><div class="field"><label>Account label</label><input id="aname" placeholder="Test account A"/></div><div class="field"><label>Exact host</label><input id="ahost" placeholder="api.example.org"/></div></div><div class="field" style="margin-top:10px"><label>Cookie header (not persisted)</label><input id="acookie" type="password" autocomplete="off" placeholder="session=..."/></div><button style="margin-top:12px" onclick="addAccount()">Add account</button><p class="muted">Do not use other users' credentials. Cookies stay in backend process memory, not disk.</p></section>
|
| 15 |
+
</div><section class="card"><h2>General settings</h2><p class="muted">Limits apply per scan job, not globally across all browser sessions.</p><div class="toolbar"><div class="field"><label>Pipeline order</label><input id="settingsOrder" value="gau, katana, httpx, zap-passive, nuclei, ffuf, arjun, dalfox, sqlmap"/></div><div class="field"><label>Discovery endpoint budget</label><input id="settingsLimit" type="number" min="1" max="2000" value="100"/></div><div class="field"><label>ZAP total request budget</label><input id="settingsBudget" type="number" min="1" max="2000" value="100"/></div></div><p class="muted">Settings are scoped to this browser tab. Modules execute sequentially in the specified order.</p><button class="secondary" onclick="applySettings()">Apply pipeline settings</button></section><section class="card"><h2>Advanced settings 路 Individual modules</h2><div class="toolbar"><div class="field"><label>HTTPX: URLs per job</label><input id="settingsHttpx" type="number" value="10" min="1" max="100"/></div><div class="field"><label>Katana: maximum depth</label><input id="settingsKatana" type="number" value="1" min="1" max="5"/></div><div class="field"><label>ZAP: max discovered URLs</label><input id="settingsZap" type="number" value="5" min="1" max="100"/></div></div><div class="grid" style="margin-top:14px"><div><h2>FFUF 路 Content discovery</h2><p class="muted">Bounded 10-path dictionary, one request per second, single-target consent required.</p></div><div><h2>Arjun 路 Parameter discovery</h2><p class="muted">Bounded per-target parameter discovery; bounded by a process timeout.</p></div><div><h2>Dalfox 路 XSS review</h2><p class="muted">Single-target XSS review; output requires manual verification.</p></div><div><h2>SQLmap 路 SQL injection review</h2><p class="muted">Single parameterized target, low-risk boolean tests; no database extraction.</p></div></div><div class="controls" style="margin-top:13px"><label><input id="modFfuf" type="checkbox"/> FFUF</label><label><input id="modArjun" type="checkbox"/> Arjun</label><label><input id="modDalfox" type="checkbox"/> Dalfox</label><label><input id="modSqlmap" type="checkbox"/> SQLmap</label></div><p><label><input id="activeConsent" type="checkbox"/> I have verified this program authorizes active tests against this selected asset.</label></p><p class="notice">These tools may run in an explicitly authorized batch of up to 30 selected targets. They require separate active-testing confirmation. A public Space still lacks strong access control; only test targets and techniques you are authorized to assess.</p></section></div><div class="pane" id="pane-logs" hidden><section class="card"><div class="controls"><h2>Full scan logs</h2><button class="secondary" onclick="copyLogs()">Copy logs</button><button class="secondary" onclick="loadLogs()">Refresh</button></div><pre id="fullLogs" class="results">No logs yet.</pre></section></div><div class="pane" id="pane-findings" hidden><section class="card"><div class="controls" style="justify-content:space-between"><h2>Findings explorer</h2><div class="controls"><button class="secondary" onclick="loadScans()">Refresh detections</button><button class="secondary" onclick="clearFindings()">Clear detections</button><button class="secondary" onclick="fullReport()">Full report JSON</button><button class="secondary" onclick="exportFindings()">Export filtered JSON</button><button class="secondary" onclick="exportCSV()">Export filtered CSV</button></div></div><p class="muted">Heuristic scanner alerts, not confirmed vulnerabilities. Repeated detections on the same URL and rule are grouped.</p><div class="summary" id="findSummary"></div><div class="toolbar" style="margin-top:16px"><div class="field"><label>Search</label><input id="findSearch" placeholder="Rule, URL, details..." oninput="renderFindings()"/></div><div class="field"><label>Severity</label><select id="findSeverity" onchange="renderFindings()"><option value="all">All</option><option>Critical</option><option>High</option><option>Medium</option><option>Low</option><option>Informational</option></select></div><div class="field"><label>Confidence</label><select id="findConfidence" onchange="renderFindings()"><option value="all">All</option><option>High</option><option>Medium</option><option>Low</option></select></div><div class="field"><label>Source</label><select id="findSource" onchange="renderFindings()"><option value="all">All tools</option><option value="zap">ZAP</option><option value="nuclei">Nuclei</option></select></div><div class="field"><label>Host</label><select id="findHost" onchange="renderFindings()"><option value="all">All hosts</option></select></div><div class="field"><label>Sort</label><select id="findSort" onchange="renderFindings()"><option value="severity">Severity first</option><option value="confidence">Confidence first</option><option value="host">Host A-Z</option><option value="count">Most repeated</option></select></div></div><div id="findCount" class="subtle" style="margin-top:13px"></div><div id="findList"></div></section></div><div class="pane" id="pane-jobs" hidden><section class="card"><div class="controls" style="justify-content:space-between"><h2>Scan jobs</h2><button class="secondary" onclick="loadScans()">Refresh jobs</button></div><div class="summary" id="jobSummary"></div><div id="scans"></div></section></div>
|
| 16 |
<p id="message" class="muted" role="status" aria-live="polite"></p></main>
|
| 17 |
<script>
|
| 18 |
let assets=[],selectedIds=new Set(),visibleWebIds=[],scanJobs=[],findings=[],currentTab="scope";
|
|
|
|
| 52 |
await refresh();const ids=Array.from(selectedIds).slice(0,1000);
|
| 53 |
if(!ids.length)return message('No valid selected assets remain. The Space may have restarted; re-import the CSV and select targets again.',true);
|
| 54 |
let modules;try{modules=enabledModules();validateNumericSettings()}catch(e){return message(e.message,true)}
|
| 55 |
+
if(!modules.length)return message('Choose at least one pipeline module.',true);const active=modules.some(m=>['ffuf','arjun','dalfox','sqlmap'].includes(m));if(active&&ids.length>30)return message('Active review allows up to 30 approved targets per batch.',true);if(active&&!el('activeConsent').checked)return message('Confirm active review authorization under Settings.',true);
|
| 56 |
if(!el('policy').checked)return message('Confirm the program policy first.',true);
|
| 57 |
try{
|
| 58 |
let r;
|
app/main.py
CHANGED
|
@@ -201,8 +201,8 @@ class BatchRequest(BaseModel):
|
|
| 201 |
|
| 202 |
@app.post("/api/scans/batch", dependencies=[Depends(authorize)])
|
| 203 |
async def batch_scans(payload: BatchRequest, request: Request):
|
| 204 |
-
if ACTIVE_MODULES.intersection(payload.modules) and (len(payload.asset_ids) >
|
| 205 |
-
raise HTTPException(422, "Active review requires authorization and at most
|
| 206 |
if not payload.policy_confirmed:
|
| 207 |
raise HTTPException(422, "Program policy confirmation is required")
|
| 208 |
if len(set(payload.asset_ids)) != len(payload.asset_ids) or queue.qsize() + len(payload.asset_ids) > 1000:
|
|
|
|
| 201 |
|
| 202 |
@app.post("/api/scans/batch", dependencies=[Depends(authorize)])
|
| 203 |
async def batch_scans(payload: BatchRequest, request: Request):
|
| 204 |
+
if ACTIVE_MODULES.intersection(payload.modules) and (len(payload.asset_ids) > 30 or not payload.active_confirmed):
|
| 205 |
+
raise HTTPException(422, "Active review requires authorization and at most 30 targets")
|
| 206 |
if not payload.policy_confirmed:
|
| 207 |
raise HTTPException(422, "Program policy confirmation is required")
|
| 208 |
if len(set(payload.asset_ids)) != len(payload.asset_ids) or queue.qsize() + len(payload.asset_ids) > 1000:
|
tests/test_active_batch_size.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi.testclient import TestClient
|
| 2 |
+
from app import main
|
| 3 |
+
|
| 4 |
+
def test_active_batch_of_30_reaches_asset_validation():
|
| 5 |
+
main.assets.clear()
|
| 6 |
+
with TestClient(main.app) as client:
|
| 7 |
+
payload={"asset_ids":[f"missing-{i}" for i in range(30)],
|
| 8 |
+
"policy_confirmed":True,"active_confirmed":True,
|
| 9 |
+
"modules":["ffuf"]}
|
| 10 |
+
result=client.post("/api/scans/batch",json=payload)
|
| 11 |
+
assert result.status_code == 409
|
| 12 |
+
assert "scope" in result.json()["detail"].lower()
|
| 13 |
+
|
| 14 |
+
def test_active_batch_above_30_rejected():
|
| 15 |
+
with TestClient(main.app) as client:
|
| 16 |
+
payload={"asset_ids":[f"missing-{i}" for i in range(31)],
|
| 17 |
+
"policy_confirmed":True,"active_confirmed":True,
|
| 18 |
+
"modules":["ffuf"]}
|
| 19 |
+
response=client.post("/api/scans/batch",json=payload)
|
| 20 |
+
assert response.status_code == 422
|
| 21 |
+
assert "30" in response.json()["detail"]
|