Wuhp commited on
Commit
0ae56fc
路
1 Parent(s): 698776e

Support 30-target authorized active batches and test validation

Browse files
Files changed (3) hide show
  1. app/index.html +2 -2
  2. app/main.py +2 -2
  3. 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 five 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,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>5)return message('Active review allows up to 5 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;
 
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) > 5 or not payload.active_confirmed):
205
- raise HTTPException(422, "Active review requires authorization and at most five 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:
 
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"]