Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Triage web application vulnerability findings from DAST/SAST scanners using OWASP risk rating methodology to separate true positives from false positives and prioritize remediation.
.claude/skills/performing-web-application-vulnerability-triage/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-17 | ✗→✓ | ▲ Improved | — | — |
Web application vulnerability triage is the process of reviewing findings from DAST (Dynamic Application Security Testing) and SAST (Static Application Security Testing) tools to validate true positives, dismiss false positives, assign risk ratings using the OWASP Risk Rating Methodology, and prioritize remediation. Effective triage reduces alert fatigue and focuses development teams on the vulnerabilities that matter most.
requests, beautifulsoup4Risk = Likelihood x Impact| Factor Group | Factor | Description | |-------------|--------|------------| | Threat Agent | Skill Level | How technically skilled is the attacker? | | Threat Agent | Motive | How motivated is the attacker? | | Threat Agent | Opportunity | What resources/access are needed? | | Threat Agent | Size | How large is the potential threat agent group? | | Vulnerability | Ease of Discovery | How easy is it to find the vulnerability? | | Vulnerability | Ease of Exploit | How easy is it to exploit? | | Vulnerability | Awareness | How well known is the vulnerability? | | Vulnerability | Intrusion Detection | How likely is exploitation to be detected? |
| Factor Group | Factor | Description | |-------------|--------|------------| | Technical | Confidentiality | How much data could be disclosed? | | Technical | Integrity | How much data could be corrupted? | | Technical | Availability | How much service could be lost? | | Technical | Accountability | Can actions be traced to attacker? | | Business | Financial Damage | Revenue loss, regulatory fines | | Business | Reputation Damage | Brand trust erosion | | Business | Non-compliance | Regulatory violation exposure | | Business | Privacy Violation | PII/PHI exposure volume |
| | Low Impact (0-3) | Medium Impact (3-6) | High Impact (6-9) | |---|---|---|---| | High Likelihood (6-9) | Medium | High | Critical | | Medium Likelihood (3-6) | Low | Medium | High | | Low Likelihood (0-3) | Note | Low | Medium |
pythonOWASP_TOP_10_2021 = { "A01": "Broken Access Control", "A02": "Cryptographic Failures", "A03": "Injection", "A04": "Insecure Design", "A05": "Security Misconfiguration", "A06": "Vulnerable and Outdated Components", "A07": "Identification and Authentication Failures", "A08": "Software and Data Integrity Failures", "A09": "Security Logging and Monitoring Failures", "A10": "Server-Side Request Forgery", } CWE_TO_OWASP = { "CWE-79": "A03", # XSS -> Injection "CWE-89": "A03", # SQL Injection "CWE-78": "A03", # OS Command Injection "CWE-352": "A01", # CSRF -> Access Control "CWE-22": "A01", # Path Traversal "CWE-200": "A02", # Information Exposure "CWE-327": "A02", # Weak Cryptography "CWE-287": "A07", # Authentication Issues "CWE-918": "A10", # SSRF "CWE-502": "A08", # Deserialization "CWE-611": "A05", # XXE -> Misconfiguration }
pythondef triage_finding(finding): """Classify finding as true_positive, false_positive, or needs_review.""" fp_indicators = [ "Content-Security-Policy header not set", # Often informational "X-Content-Type-Options header missing", # Low severity header "Cookie without SameSite attribute", # Context dependent ] for indicator in fp_indicators: if indicator.lower() in finding.get("title", "").lower(): if finding.get("severity", "").lower() in ("info", "low"): return "false_positive", "Common informational finding" # Check for confirmed exploitation evidence if finding.get("evidence") and finding.get("confidence", "").lower() == "certain": return "true_positive", "Scanner confirmed exploitation" # SAST findings need manual code review if finding.get("source") == "sast": if finding.get("cwe") in ["CWE-89", "CWE-78", "CWE-79"]: return "needs_review", "Injection finding requires manual code review" return "needs_review", "Requires manual validation"
pythondef calculate_risk_score(finding, app_context): """Calculate OWASP risk rating for a web application finding.""" # Likelihood factors likelihood = { "skill_level": 6 if finding["cwe"] in ["CWE-89", "CWE-79"] else 4, "motive": 7, # Financial gain "opportunity": 7 if finding.get("authenticated") == False else 4, "size": 9 if finding.get("internet_facing") else 4, "ease_of_discovery": 8 if finding.get("scanner_detected") else 5, "ease_of_exploit": 7 if finding.get("exploit_available") else 4, "awareness": 6, "intrusion_detection": 3 if app_context.get("waf_enabled") else 8, } # Impact factors impact = { "confidentiality": 9 if "data_exposure" in finding.get("tags", []) else 5, "integrity": 9 if finding["cwe"] in ["CWE-89", "CWE-78"] else 4, "availability": 7 if "dos" in finding.get("tags", []) else 2, "accountability": 3 if app_context.get("logging_enabled") else 7, "financial": 7 if app_context.get("processes_payments") else 3, "reputation": 6 if app_context.get("customer_facing") else 2, "compliance": 8 if app_context.get("pci_scope") else 3, "privacy": 9 if app_context.get("handles_pii") else 2, } likelihood_score = sum(likelihood.values()) / len(likelihood) impact_score = sum(impact.values()) / len(impact) risk_score = likelihood_score * impact_score if risk_score >= 42: risk_level = "Critical" elif risk_score >= 24: risk_level = "High" elif risk_score >= 12: risk_level = "Medium" elif risk_score >= 3: risk_level = "Low" else: risk_level = "Note" return { "likelihood_score": round(likelihood_score, 1), "impact_score": round(impact_score, 1), "risk_score": round(risk_score, 1), "risk_level": risk_level, }
bash# Process DAST/SAST results through triage pipeline python3 scripts/process.py \ --input zap_results.json \ --format zap \ --app-context app_config.json \ --output triage_report.json
# Test parameter with single quote
GET /search?q=test' HTTP/1.1
# Test with boolean-based payload
GET /search?q=test' AND 1=1-- HTTP/1.1
GET /search?q=test' AND 1=2-- HTTP/1.1
# Time-based verification
GET /search?q=test'; WAITFOR DELAY '0:0:5'-- HTTP/1.1# Reflected XSS test
GET /search?q=<script>alert(document.domain)</script> HTTP/1.1
# Check if output is encoded
GET /search?q="><img src=x onerror=alert(1)> HTTP/1.1
# DOM-based XSS
GET /page#<img src=x onerror=alert(1)> HTTP/1.1| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +26 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.