- Fix email report URL: /{token}.html → /visitor_{token}.html
- Remove duplicate case 'generating' in simulation.html polling
- Add defensive guard against empty Subdomains in SelectAndScan
- Add Home link and Start New Scan button to visitor report
- Replace QR code injection with email-send form in consultant report
- Add scan timer animation (barberpole + elapsed counter) to frontend
- Move IsIP() from scanner/probe.go to pkg/netutil for reuse
- Add 44 unit tests across scanner, report, and netutil packages
- Create Makefile with build/vet/test/deploy targets
- Document SMTP environment variables in README and AGENT.md
85 lines
2.7 KiB
Go
85 lines
2.7 KiB
Go
package scanner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
gotestwafTimeout = 120 * time.Second
|
|
)
|
|
|
|
// RunGoTestWAF executes GoTestWAF scan against the given target domain.
|
|
// Tries HTTPS first; if the report file isn't created (target not reachable
|
|
// on port 443), retries with plain HTTP on port 80.
|
|
// The report is saved to reports/ directory with the given reportName.
|
|
// Returns the raw output for AI analysis.
|
|
func RunGoTestWAF(ctx context.Context, projectRoot string, reportName string, targetDomain string) (string, error) {
|
|
// Try HTTPS first, then HTTP as fallback
|
|
for _, protocol := range []string{"https", "http"} {
|
|
output, err := runWithProtocol(ctx, projectRoot, reportName, targetDomain, protocol)
|
|
// If the report file exists, the scan produced output — success
|
|
reportPath := filepath.Join(projectRoot, "reports", reportName+".html")
|
|
if _, statErr := os.Stat(reportPath); statErr == nil {
|
|
return output, nil
|
|
}
|
|
// No report file — try next protocol or return error
|
|
if err != nil {
|
|
fmt.Printf("gotestwaf: %s failed for %s: %v\n", protocol, targetDomain, err)
|
|
}
|
|
}
|
|
return "", fmt.Errorf("GoTestWAF: %s not reachable on HTTPS or HTTP", targetDomain)
|
|
}
|
|
|
|
// runWithProtocol executes GoTestWAF with the given protocol (https or http).
|
|
func runWithProtocol(ctx context.Context, projectRoot string, reportName string, targetDomain string, protocol string) (string, error) {
|
|
targetURL := fmt.Sprintf("%s://%s", protocol, targetDomain)
|
|
binaryPath := filepath.Join(projectRoot, "gotestwaf")
|
|
|
|
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
|
|
return "", fmt.Errorf("GoTestWAF binary not found at %s", binaryPath)
|
|
}
|
|
|
|
reportsDir := filepath.Join(projectRoot, "reports")
|
|
if err := os.MkdirAll(reportsDir, 0755); err != nil {
|
|
return "", fmt.Errorf("failed to create reports directory: %w", err)
|
|
}
|
|
|
|
scanCtx, cancel := context.WithTimeout(ctx, gotestwafTimeout)
|
|
defer cancel()
|
|
|
|
cmd := exec.CommandContext(scanCtx, binaryPath,
|
|
"--url", targetURL,
|
|
"--configPath", "gotestwaf-config.yaml",
|
|
"--testCasesPath", "testcases",
|
|
"--maxIdleConns", "2",
|
|
"--maxRedirects", "10",
|
|
"--reportPath", reportsDir,
|
|
"--reportName", reportName,
|
|
"--reportFormat", "html",
|
|
"--wafName", "generic",
|
|
"--skipWAFBlockCheck",
|
|
"--nonBlockedAsPassed",
|
|
"--tlsVerify=false",
|
|
"--noEmailReport",
|
|
"--quiet",
|
|
)
|
|
|
|
cmd.Dir = projectRoot
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
outputStr := string(output)
|
|
|
|
if err != nil {
|
|
if scanCtx.Err() == context.DeadlineExceeded {
|
|
return outputStr, fmt.Errorf("GoTestWAF scan timed out after %v", gotestwafTimeout)
|
|
}
|
|
return outputStr, fmt.Errorf("GoTestWAF scan failed: %w", err)
|
|
}
|
|
|
|
return outputStr, nil
|
|
}
|