Response modules analyze every match that passes the filters (status, length, content-type, etc.). For each match, all enabled modules run; their results are attached to that result and appear in the report.
- CLI:
-modules fingerprint,cors,ai(comma-separated; names are case-insensitive) - Config file:
"modules": "fingerprint,cors,ai"
Unknown names are ignored; duplicates are only run once.
| Module | Description | Output (example) |
|---|---|---|
| fingerprint | Detects technologies from response headers and body (e.g. nginx, PHP, WordPress, React). | technologies: list of detected technologies |
| cors | Reads CORS headers from the response (no separate request with Origin). |
access_control_allow_origin, access_control_allow_credentials, potentially_permissive, etc. |
| ai | Sends status, URL, and (truncated) body to an AI backend; returns a short security assessment. Provider: openai (default) | ollama | gemini. Keys: OPENAI_API_KEY, or GEMINI_API_KEY/GOOGLE_API_KEY, or none for Ollama. |
verdict: response text |
| urlextract | Parses URLs from the response body and Location header; deduplicates and normalizes. Stored per result in the report. | urls: list of URLs (strings) |
| links | Extracts links from HTML (href, action, src), resolves to absolute URLs, deduplicates. Use with -enqueue-module-urls links to enqueue discovered URLs. |
urls: list of URLs (strings) |
| headers | Evaluates security-related response headers: Content-Security-Policy, X-Frame-Options, Strict-Transport-Security, X-Content-Type-Options; Set-Cookie (Secure, HttpOnly). Flags missing or weak values. | missing_headers, weak_headers, issues, cookie_has_secure, cookie_has_httponly, etc. |
| secrets | Scans response body (and Authorization header) for common secret patterns: AWS keys, JWTs, GitHub/Slack tokens, password= in response, generic api_key/secret. Reports finding types only; no secret values stored. |
findings: list of types (e.g. aws_access_key, jwt, password_in_response), count |
| auth | Detects auth-related responses: login form (form + password field + login/signin), logout link, 401, 302 to login, "session expired" / "please log in" text, Set-Cookie with session/auth-like name. Helps prioritize auth flows. | hints: list (e.g. login_form, logout_present, status_401, redirect_to_login, session_or_login_required, auth_cookie_set), count |
- Providers:
-ai-provider openai | ollama | gemini. Default: openai. Ollama uses local server (defaulthttp://localhost:11434); no API key. OpenAI needsOPENAI_API_KEY; Gemini needsGEMINI_API_KEYorGOOGLE_API_KEY. Override URL with-ai-endpoint, model with-ai-model, token limit with-ai-max-tokens(0 = default 150). - Error handling: If the API key is missing or the API call fails, the module does not crash: it returns structured data in
module_data.ai:"skipped": "reason"(e.g. OPENAI_API_KEY not set) or"error": "api", "message": "...". So you can see in the JSON/HTML report why a verdict is missing. - Prompt: Default prompt (security-focused) or custom text via
-ai-promptor configaiPrompt. - Placeholders:
{{status}},{{method}},{{url}},{{body}}are replaced. - Body: Truncated to 3000 characters to limit API cost and latency.
Example custom prompt:
-ai-prompt "Security expert: status {{status}}, {{url}}. Anything unusual? One sentence.\n\n{{body}}"- TXT, CSV, HTML: Each row shows a short summary of module data (e.g.
fingerprint: technologies=nginx,php | cors: potentially_permissive). - JSON, NDJSON, compat JSON: Each result includes the full
module_data(object per module with all fields). Ideal for evaluation or filtering by technologies, CORS, or AI verdict.
Recommendation: Use -of json (or ndjson) when you need full module data; then all module output is available per URL in a structured form.
With -enqueue-module-urls (or config enqueueModuleUrls) you can feed URLs discovered by certain modules back into the scan queue. Only modules that output a urls field (list of strings) are supported, e.g. urlextract and links.
- urlextract: URLs from body (regex) and
Locationheader. - links: URLs from HTML attributes
href,action,src(resolved against the request URL).
Example: discover directories by following links from each page:
./psfuzz -u https://example.com/ -w wordlist.txt -modules links -enqueue-module-urls links -D 2 -of json -o scanEnqueued URLs get the same scope/visited handling as recursion (depth +1, same method/headers).
-
Analyze only certain status codes
With-mc 200(or other status codes), modules run only for those matches—less work and, for AI, fewer API calls. -
Combine with filters
Filters (status, length, content-type, block-words) reduce the number of matches; fewer matches mean fewer module runs. -
Use AI sparingly
AI per request consumes API calls. Consider running it only for 200 responses (-mc 200 -modules ai) or, after an initial run withfingerprint,cors, a second run withaionly on interesting paths. -
Full module data
For scripts or post-processing:-of json -o scan→ inscan.json, each entry has full data undermodule_data(e.g.module_data.fingerprint.technologies,module_data.ai.verdict).
# Technology detection + CORS for all matches, output with full module_data
./psfuzz -u https://example.com/FUZZ -w wordlist.txt -modules fingerprint,cors -of json -o scan
# Only 200 responses with fingerprint and AI (fewer API costs)
./psfuzz -u https://example.com/FUZZ -w wordlist.txt -mc 200 -modules fingerprint,ai -of json -o scan
# Custom AI prompt from config
# In config.json: "modules": "ai", "aiPrompt": "Brief check: {{url}} Status {{status}}. Anything notable?\n\n{{body}}"
./psfuzz -u https://example.com/FUZZ -w wordlist.txt -cf config.json -of json -o scanTo add a new response-analysis module:
-
Implement the
Analyzerinterface ininternal/modules/:Name() string– lowercase, short name (e.g."urlextract").Analyze(ctx context.Context, in Input) (Output, error)– usein.URL,in.Body,in.Headers, etc.; returnOutput{Data: map[string]any{...}}. ReturnOutput{Data: nil}when there is nothing to report (no error). Respectctx.Done()for cancellation.
-
Register the module in your new file with
init()– no need to editregistry.go:-
Call
Register("modulename", func(c *Config) Analyzer { return YourAnalyzer{} })infunc init(). If your module needs config (e.g. a prompt), usefunc(c *Config) Analyzer { return YourAnalyzer{Option: c.SomeField} }. -
If your module needs a CLI flag or config file option (e.g. custom prompt, API key): add the field to
internal/config(incliConfigand inapplyCLIConfigso it is applied tocfg.ModuleConfigor the main config), and add it tomodules.Configininternal/modules/config.go. Then pass it in the factory, e.g.return YourAnalyzer{Option: c.YourOption}. Example: theaimodule usesAIPromptfrom config. -
If your module needs to call an LLM (OpenAI, Ollama, Gemini): use the shared
internal/llmpackage. It providesllm.Provider,llm.Config,llm.Message,llm.GetAPIKey(provider), andllm.Call(ctx, cfg, apiKey, messages). Add your config (provider, endpoint, model, max tokens) tomodules.Configand wire flags ininternal/config. Seeinternal/modules/ai.gofor a full example. New modules can reuse the same layer without duplicating API logic.
-
-
Output format: Put only JSON-serializable values in
Data(e.g.string,[]string,bool,float64). This data appears in the report per result and in all output formats (TXT summary, JSON/NDJSON/compat JSON in full). -
Best practices:
- Keep the module stateless or use only config passed at creation (e.g.
AIAnalyzer{Prompt: cfg.AIPrompt}). - Limit work per response (e.g. cap body size, timeouts for external calls).
- On error, return the error; the runner skips that module for that result but continues others.
- Add a test in
registry_test.goor a dedicated*_test.go(e.g.TestYourAnalyzer_...).
- Keep the module stateless or use only config passed at creation (e.g.
-
Document: Add the module to the "Available modules" table above and, if needed, to README and CHEATSHEET.
Minimal example: See internal/modules/urlextract.go (URL parsing from body/headers) or internal/modules/cors.go (header-only).
- Modules run after the filter, once per match.
- Input: URL, Method, StatusCode, Headers, Body, ContentType, Length, Words, Lines.
- Output: per module a
map[string]any; one module failing does not block the others. - Data is stored in the engine report per
ResultinModuleDataand used by all output formats (TXT/CSV/HTML as summary, JSON/NDJSON/compat JSON in full).