Skip to content

Commit 8f812e4

Browse files
committed
Merge testing: resolve 8 project audit findings (security + correctness)
2 parents d54d8a9 + 05e583f commit 8f812e4

8 files changed

Lines changed: 47 additions & 21 deletions

File tree

internal/api/scan_logbus.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,16 @@ func (b *logBus) Subscribe(scanID string) (history []string, ch chan string) {
7474
history = make([]string, len(stored))
7575
copy(history, stored)
7676

77-
ch = make(chan string, logBusChanSize)
78-
if len(b.subs[scanID]) < logBusMaxSubs {
79-
b.subs[scanID] = append(b.subs[scanID], ch)
77+
if len(b.subs[scanID]) >= logBusMaxSubs {
78+
// At the subscriber cap: hand back an already-closed channel so the SSE
79+
// loop receives ok=false and tears down cleanly, instead of an orphaned
80+
// open channel that Close() can never close (relying on client disconnect).
81+
ch = make(chan string)
82+
close(ch)
83+
return history, ch
8084
}
85+
ch = make(chan string, logBusChanSize)
86+
b.subs[scanID] = append(b.subs[scanID], ch)
8187
return history, ch
8288
}
8389

internal/api/scan_results_api.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2056,18 +2056,17 @@ func apiGetScanReport(c *gin.Context) {
20562056
templateName := strings.TrimSpace(c.DefaultQuery("template", "default"))
20572057
format := strings.ToLower(strings.TrimSpace(c.DefaultQuery("format", "markdown")))
20582058

2059-
scanRec, _ := db.GetScan(scanID)
2060-
target := ""
2061-
scanType := ""
2062-
status := "unknown"
2059+
scanRec, err := db.GetScan(scanID)
2060+
if err != nil {
2061+
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
2062+
return
2063+
}
2064+
target := scanRec.Target
2065+
scanType := scanRec.ScanType
2066+
status := scanRec.Status
20632067
dateStr := time.Now().UTC().Format("2006-01-02")
2064-
if scanRec != nil {
2065-
target = scanRec.Target
2066-
scanType = scanRec.ScanType
2067-
status = scanRec.Status
2068-
if !scanRec.CompletedAt.IsZero() {
2069-
dateStr = scanRec.CompletedAt.UTC().Format("2006-01-02")
2070-
}
2068+
if scanRec.CompletedAt != nil && !scanRec.CompletedAt.IsZero() {
2069+
dateStr = scanRec.CompletedAt.UTC().Format("2006-01-02")
20712070
}
20722071

20732072
// Build a simple Markdown findings table from parsed results (top 50 rows).

internal/api/scan_runner.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ func RunScanInProcess(scanID, scanType, target string, fn func() error) {
5252
// Without a DB record the scan would be invisible to the UI — abort rather
5353
// than run an orphaned scan whose results can never be retrieved.
5454
log.Printf("[runner] ABORT: failed to create DB record for %s (%s): %v", scanID, scanType, err)
55-
<-scanSemaphore // release slot acquired above
55+
// The deferred release above already frees the acquired slot; releasing
56+
// again here would unbalance the semaphore (steal another scan's slot).
5657
return
5758
}
5859

internal/api/ui/app.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,9 @@ function targetsLaunchScan(domain) {
733733
async function targetsCopyAll() {
734734
return callPageMethod('TargetsPage', 'targetsCopyAll');
735735
}
736+
async function targetsCopyOne(value) {
737+
return callPageMethod('TargetsPage', 'targetsCopyOne', [value]);
738+
}
736739

737740
// ── Keyhacks ─────────────────────────────────────────────────────────────────
738741

internal/api/ui/pages/settings.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@
177177
<div class="settings-hint">Where monitor change alerts are sent. Discord webhook URLs work out of the box.</div>
178178
</div>
179179
<div class="settings-control">
180-
<input type="text" id="monitor-webhook-input" value="${escValue(cfg.monitor_webhook || '')}" placeholder="https://discord.com/api/webhooks/..." class="form-control premium-input">
180+
<input type="text" id="monitor-webhook-input" value="" placeholder="${cfg.monitor_webhook_set ? 'Configured — enter a new URL to replace it' : 'https://discord.com/api/webhooks/...'}" class="form-control premium-input">
181181
<button class="btn btn-primary" onclick="window.SettingsPage.saveWebhookSettings()">Save</button>
182182
</div>
183183
</div>
@@ -362,6 +362,12 @@
362362
const input = document.getElementById('monitor-webhook-input');
363363
if (!input) return;
364364
const webhook = input.value.trim();
365+
// The raw webhook is no longer returned by /api/config (it's a secret), so the
366+
// field renders empty; an empty submit means "no change" rather than clearing it.
367+
if (!webhook) {
368+
window.showToast('info', 'No change', 'Enter a webhook URL to set or replace the current one.');
369+
return;
370+
}
365371
try {
366372
const headers = await window.buildAuthHeaders({ 'Content-Type': 'application/json' });
367373
const res = await fetch('/api/settings', {

internal/api/ui/pages/targets.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@
1818
};
1919

2020
function escapeSafe(s) {
21-
return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
21+
// Escapes &, ", <, > AND ' — the single quote matters because these values are
22+
// interpolated into single-quoted onclick="fn('...')" string literals; leaving
23+
// it unescaped lets a crafted scope target break out and execute JS (XSS).
24+
return String(s)
25+
.replace(/&/g, '&amp;')
26+
.replace(/"/g, '&quot;')
27+
.replace(/</g, '&lt;')
28+
.replace(/>/g, '&gt;')
29+
.replace(/'/g, '&#39;');
2230
}
2331

2432
function renderPlatformCredFields(p, colors) {
@@ -330,5 +338,6 @@
330338
targetsAddAllDomains,
331339
targetsLaunchScan,
332340
targetsCopyAll,
341+
targetsCopyOne,
333342
};
334343
})();

internal/api/ui_api.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ func apiConfigHandler(c *gin.Context) {
6363
"auth_provider": "local",
6464
"db_type": utils.GetEnv("DB_TYPE", "postgresql"),
6565
"mode": utils.GetEnv("AUTOAR_MODE", "api"),
66-
"monitor_webhook": os.Getenv("MONITOR_WEBHOOK_URL"),
66+
// Secret webhook URL is never returned on this public endpoint — only
67+
// whether one is configured (the raw value carries a Discord/Slack token).
68+
"monitor_webhook_set": strings.TrimSpace(os.Getenv("MONITOR_WEBHOOK_URL")) != "",
6769
"monitor_ai_available": strings.TrimSpace(os.Getenv("OPENROUTER_API_KEY")) != "" ||
6870
strings.TrimSpace(os.Getenv("OPENCODE_API_KEY")) != "" ||
6971
strings.TrimSpace(os.Getenv("GEMINI_API_KEY")) != "",

internal/scanner/ffuf/ffuf.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ func RunFFufDomainMode(opts Options) (*Result, error) {
942942

943943
// Setup output directory
944944
resultsDir := utils.GetResultsDir()
945-
outputDir := filepath.Join(resultsDir, opts.Domain, "ffuf")
945+
outputDir := filepath.Join(resultsDir, utils.SanitizeTargetSegment(opts.Domain), "ffuf")
946946
if err := utils.EnsureDir(outputDir); err != nil {
947947
return nil, fmt.Errorf("failed to create output directory: %w", err)
948948
}
@@ -1194,10 +1194,10 @@ func runFFufSingleTarget(opts Options) (*Result, error) {
11941194
// Otherwise use the legacy single-target layout:
11951195
// {resultsDir}/{subdomain}/ffuf/
11961196
resultsDir := utils.GetResultsDir()
1197-
subdomainName := extractDomain(opts.Target)
1197+
subdomainName := utils.SanitizeTargetSegment(extractDomain(opts.Target))
11981198
var outputDir string
11991199
if opts.ParentDomain != "" {
1200-
outputDir = filepath.Join(resultsDir, opts.ParentDomain, "ffuf", subdomainName)
1200+
outputDir = filepath.Join(resultsDir, utils.SanitizeTargetSegment(opts.ParentDomain), "ffuf", subdomainName)
12011201
} else {
12021202
outputDir = filepath.Join(resultsDir, subdomainName, "ffuf")
12031203
}

0 commit comments

Comments
 (0)