@intentsolutionsio/penetration-tester 2.0.0 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.claude-plugin/plugin.json +8 -3
  2. package/README.md +8 -0
  3. package/commands/pentest.md +5 -0
  4. package/package.json +8 -3
  5. package/skills/analyzing-tls-config/SKILL.md +221 -0
  6. package/skills/analyzing-tls-config/references/AUTHORIZATION.md +133 -0
  7. package/skills/analyzing-tls-config/references/PLAYBOOK.md +267 -0
  8. package/skills/analyzing-tls-config/references/THEORY.md +128 -0
  9. package/skills/analyzing-tls-config/scripts/analyze_tls.py +415 -0
  10. package/skills/auditing-cors-policy/SKILL.md +186 -0
  11. package/skills/auditing-cors-policy/references/PLAYBOOK.md +220 -0
  12. package/skills/auditing-cors-policy/references/THEORY.md +142 -0
  13. package/skills/auditing-cors-policy/scripts/audit_cors.py +350 -0
  14. package/skills/auditing-npm-dependencies/SKILL.md +254 -0
  15. package/skills/auditing-npm-dependencies/references/PLAYBOOK.md +175 -0
  16. package/skills/auditing-npm-dependencies/references/THEORY.md +122 -0
  17. package/skills/auditing-npm-dependencies/scripts/audit_npm.py +408 -0
  18. package/skills/auditing-python-dependencies/SKILL.md +251 -0
  19. package/skills/auditing-python-dependencies/references/PLAYBOOK.md +193 -0
  20. package/skills/auditing-python-dependencies/references/THEORY.md +122 -0
  21. package/skills/auditing-python-dependencies/scripts/audit_python.py +459 -0
  22. package/skills/checking-http-security-headers/SKILL.md +176 -0
  23. package/skills/checking-http-security-headers/references/PLAYBOOK.md +212 -0
  24. package/skills/checking-http-security-headers/references/THEORY.md +137 -0
  25. package/skills/checking-http-security-headers/scripts/check_headers.py +362 -0
  26. package/skills/checking-license-compliance/SKILL.md +225 -0
  27. package/skills/checking-license-compliance/references/PLAYBOOK.md +161 -0
  28. package/skills/checking-license-compliance/references/THEORY.md +152 -0
  29. package/skills/checking-license-compliance/scripts/check_licenses.py +461 -0
  30. package/skills/composing-vulnerability-report/SKILL.md +212 -0
  31. package/skills/composing-vulnerability-report/references/PLAYBOOK.md +180 -0
  32. package/skills/composing-vulnerability-report/references/THEORY.md +178 -0
  33. package/skills/composing-vulnerability-report/scripts/compose_report.py +396 -0
  34. package/skills/confirming-pentest-authorization/SKILL.md +247 -0
  35. package/skills/confirming-pentest-authorization/references/PLAYBOOK.md +189 -0
  36. package/skills/confirming-pentest-authorization/references/THEORY.md +167 -0
  37. package/skills/confirming-pentest-authorization/scripts/check_authorization.py +457 -0
  38. package/skills/defining-pentest-scope/SKILL.md +227 -0
  39. package/skills/defining-pentest-scope/references/PLAYBOOK.md +238 -0
  40. package/skills/defining-pentest-scope/references/THEORY.md +170 -0
  41. package/skills/defining-pentest-scope/scripts/define_scope.py +472 -0
  42. package/skills/detecting-command-injection-patterns/SKILL.md +144 -0
  43. package/skills/detecting-command-injection-patterns/references/PLAYBOOK.md +302 -0
  44. package/skills/detecting-command-injection-patterns/references/THEORY.md +206 -0
  45. package/skills/detecting-command-injection-patterns/scripts/scan_cmdi.py +290 -0
  46. package/skills/detecting-debug-endpoints/SKILL.md +207 -0
  47. package/skills/detecting-debug-endpoints/references/PLAYBOOK.md +402 -0
  48. package/skills/detecting-debug-endpoints/references/THEORY.md +218 -0
  49. package/skills/detecting-debug-endpoints/scripts/probe_debug.py +518 -0
  50. package/skills/detecting-directory-listing/SKILL.md +206 -0
  51. package/skills/detecting-directory-listing/references/PLAYBOOK.md +277 -0
  52. package/skills/detecting-directory-listing/references/THEORY.md +203 -0
  53. package/skills/detecting-directory-listing/scripts/probe_directory_listing.py +180 -0
  54. package/skills/detecting-eval-exec-usage/SKILL.md +128 -0
  55. package/skills/detecting-eval-exec-usage/references/PLAYBOOK.md +306 -0
  56. package/skills/detecting-eval-exec-usage/references/THEORY.md +159 -0
  57. package/skills/detecting-eval-exec-usage/scripts/scan_eval.py +223 -0
  58. package/skills/detecting-exposed-secrets-files/SKILL.md +179 -0
  59. package/skills/detecting-exposed-secrets-files/references/PLAYBOOK.md +274 -0
  60. package/skills/detecting-exposed-secrets-files/references/THEORY.md +174 -0
  61. package/skills/detecting-exposed-secrets-files/scripts/probe_secrets.py +207 -0
  62. package/skills/detecting-insecure-deserialization/SKILL.md +148 -0
  63. package/skills/detecting-insecure-deserialization/references/PLAYBOOK.md +333 -0
  64. package/skills/detecting-insecure-deserialization/references/THEORY.md +199 -0
  65. package/skills/detecting-insecure-deserialization/scripts/scan_deserialization.py +250 -0
  66. package/skills/detecting-sql-injection-patterns/SKILL.md +161 -0
  67. package/skills/detecting-sql-injection-patterns/references/PLAYBOOK.md +317 -0
  68. package/skills/detecting-sql-injection-patterns/references/THEORY.md +261 -0
  69. package/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py +354 -0
  70. package/skills/detecting-ssl-cert-issues/SKILL.md +182 -0
  71. package/skills/detecting-ssl-cert-issues/references/PLAYBOOK.md +203 -0
  72. package/skills/detecting-ssl-cert-issues/references/THEORY.md +133 -0
  73. package/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py +481 -0
  74. package/skills/detecting-weak-cryptography/SKILL.md +147 -0
  75. package/skills/detecting-weak-cryptography/references/PLAYBOOK.md +466 -0
  76. package/skills/detecting-weak-cryptography/references/THEORY.md +194 -0
  77. package/skills/detecting-weak-cryptography/scripts/scan_weak_crypto.py +417 -0
  78. package/skills/fingerprinting-server-software/SKILL.md +191 -0
  79. package/skills/fingerprinting-server-software/references/PLAYBOOK.md +337 -0
  80. package/skills/fingerprinting-server-software/references/THEORY.md +183 -0
  81. package/skills/fingerprinting-server-software/scripts/fingerprint_server.py +347 -0
  82. package/skills/generating-executive-summary/SKILL.md +261 -0
  83. package/skills/generating-executive-summary/references/PLAYBOOK.md +201 -0
  84. package/skills/generating-executive-summary/references/THEORY.md +195 -0
  85. package/skills/generating-executive-summary/scripts/exec_summary.py +538 -0
  86. package/skills/mapping-findings-to-owasp-top10/SKILL.md +235 -0
  87. package/skills/mapping-findings-to-owasp-top10/references/PLAYBOOK.md +193 -0
  88. package/skills/mapping-findings-to-owasp-top10/references/THEORY.md +160 -0
  89. package/skills/mapping-findings-to-owasp-top10/scripts/map_owasp.py +540 -0
  90. package/skills/performing-penetration-testing/SKILL.md +282 -190
  91. package/skills/performing-penetration-testing/references/OWASP_TOP_10.md +22 -0
  92. package/skills/performing-penetration-testing/references/REMEDIATION_PLAYBOOK.md +46 -0
  93. package/skills/performing-penetration-testing/references/SECURITY_HEADERS.md +41 -0
  94. package/skills/performing-penetration-testing/scripts/code_security_scanner.py +144 -79
  95. package/skills/performing-penetration-testing/scripts/dependency_auditor.py +116 -93
  96. package/skills/performing-penetration-testing/scripts/security_scanner.py +574 -446
  97. package/skills/probing-dangerous-http-methods/SKILL.md +182 -0
  98. package/skills/probing-dangerous-http-methods/references/PLAYBOOK.md +234 -0
  99. package/skills/probing-dangerous-http-methods/references/THEORY.md +145 -0
  100. package/skills/probing-dangerous-http-methods/scripts/probe_methods.py +263 -0
  101. package/skills/recording-pentest-engagement/SKILL.md +253 -0
  102. package/skills/recording-pentest-engagement/references/PLAYBOOK.md +203 -0
  103. package/skills/recording-pentest-engagement/references/THEORY.md +195 -0
  104. package/skills/recording-pentest-engagement/scripts/record_engagement.py +461 -0
  105. package/skills/scanning-for-hardcoded-secrets/SKILL.md +215 -0
  106. package/skills/scanning-for-hardcoded-secrets/references/PLAYBOOK.md +325 -0
  107. package/skills/scanning-for-hardcoded-secrets/references/THEORY.md +175 -0
  108. package/skills/scanning-for-hardcoded-secrets/scripts/scan_secrets.py +395 -0
  109. package/skills/tracing-transitive-vulnerabilities/SKILL.md +235 -0
  110. package/skills/tracing-transitive-vulnerabilities/references/PLAYBOOK.md +233 -0
  111. package/skills/tracing-transitive-vulnerabilities/references/THEORY.md +138 -0
  112. package/skills/tracing-transitive-vulnerabilities/scripts/trace_vulns.py +484 -0
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env python3
2
+ """Static-analysis scan for insecure-deserialization API usage.
3
+
4
+ References:
5
+ CWE-502 Deserialization of Untrusted Data
6
+ OWASP A08:2021 Software and Data Integrity Failures
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import re
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ _PLUGIN_ROOT = Path(__file__).resolve().parents[3]
17
+ if str(_PLUGIN_ROOT) not in sys.path:
18
+ sys.path.insert(0, str(_PLUGIN_ROOT))
19
+
20
+ from lib.finding import Finding, Severity # noqa: E402
21
+ from lib.report import emit, exit_code # noqa: E402
22
+
23
+ SKILL_ID = "detecting-insecure-deserialization"
24
+
25
+
26
+ PY_PATTERNS = [
27
+ ("Python pickle.loads", Severity.CRITICAL, r"\b(?:cPickle|pickle)\.loads?\s*\(", "python"),
28
+ ("Python dill.loads", Severity.CRITICAL, r"\bdill\.loads?\s*\(", "python"),
29
+ (
30
+ "Python yaml.load() without Loader=Safe",
31
+ Severity.CRITICAL,
32
+ r"\byaml\.load\s*\((?![^)]*Loader\s*=\s*[Yy]aml\.(?:Safe|safe_load))",
33
+ "python",
34
+ ),
35
+ ("Python yaml.unsafe_load (explicit unsafe)", Severity.CRITICAL, r"\byaml\.unsafe_load\s*\(", "python"),
36
+ ("Python yaml.full_load", Severity.HIGH, r"\byaml\.full_load\s*\(", "python"),
37
+ ("Python shelve.open (pickle-backed)", Severity.HIGH, r"\bshelve\.open\s*\(", "python"),
38
+ ("Python marshal.loads (legacy, unsafe)", Severity.CRITICAL, r"\bmarshal\.loads?\s*\(", "python"),
39
+ ]
40
+ JS_PATTERNS = [
41
+ (
42
+ "Node node-serialize.unserialize (known-vulnerable lib)",
43
+ Severity.CRITICAL,
44
+ r"require\s*\(\s*['\"]node-serialize['\"]\s*\)|from\s+['\"]node-serialize['\"]",
45
+ "javascript",
46
+ ),
47
+ ("JSON.parse with reviver function", Severity.MEDIUM, r"JSON\.parse\s*\([^,)]+,\s*function\s*\(", "javascript"),
48
+ ]
49
+ RUBY_PATTERNS = [
50
+ ("Ruby Marshal.load on non-literal", Severity.CRITICAL, r"\bMarshal\.load\s*\(\s*(?!['\"])", "ruby"),
51
+ ("Ruby YAML.load without permitted_classes", Severity.HIGH, r"\bYAML\.load\s*\((?![^)]*permitted_classes)", "ruby"),
52
+ ]
53
+ JAVA_PATTERNS = [
54
+ ("Java ObjectInputStream.readObject", Severity.CRITICAL, r"\bObjectInputStream\b[^;]*\.readObject\s*\(", "java"),
55
+ (
56
+ "Java XMLDecoder.readObject (unsafe XML deserialization)",
57
+ Severity.CRITICAL,
58
+ r"\bXMLDecoder\b[^;]*\.readObject\s*\(",
59
+ "java",
60
+ ),
61
+ ("Java SnakeYAML new Yaml() default constructor (unsafe)", Severity.HIGH, r"new\s+Yaml\s*\(\s*\)", "java"),
62
+ ]
63
+ PHP_PATTERNS = [
64
+ ("PHP unserialize on non-literal", Severity.CRITICAL, r"\bunserialize\s*\(\s*(?!['\"])", "php"),
65
+ ]
66
+ CSHARP_PATTERNS = [
67
+ (
68
+ "C# BinaryFormatter.Deserialize (deprecated unsafe)",
69
+ Severity.CRITICAL,
70
+ r"\bBinaryFormatter\s*\(?\s*\)?\s*\.\s*Deserialize\s*\(",
71
+ "csharp",
72
+ ),
73
+ (
74
+ "C# NetDataContractSerializer.ReadObject",
75
+ Severity.CRITICAL,
76
+ r"\bNetDataContractSerializer\b[^;]*\.(?:Read|Deserialize)",
77
+ "csharp",
78
+ ),
79
+ (
80
+ "C# LosFormatter.Deserialize (ViewState)",
81
+ Severity.CRITICAL,
82
+ r"\bLosFormatter\b[^;]*\.Deserialize\s*\(",
83
+ "csharp",
84
+ ),
85
+ (
86
+ "C# ObjectStateFormatter.Deserialize (ViewState)",
87
+ Severity.CRITICAL,
88
+ r"\bObjectStateFormatter\b[^;]*\.Deserialize\s*\(",
89
+ "csharp",
90
+ ),
91
+ (
92
+ "C# JavaScriptSerializer with SimpleTypeResolver",
93
+ Severity.HIGH,
94
+ r"new\s+JavaScriptSerializer\s*\(\s*new\s+SimpleTypeResolver",
95
+ "csharp",
96
+ ),
97
+ ]
98
+
99
+
100
+ LANG_EXT_MAP = {
101
+ "python": {".py"},
102
+ "javascript": {".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"},
103
+ "ruby": {".rb"},
104
+ "java": {".java", ".kt", ".scala"},
105
+ "php": {".php"},
106
+ "csharp": {".cs"},
107
+ }
108
+ LANG_PATTERNS = {
109
+ "python": PY_PATTERNS,
110
+ "javascript": JS_PATTERNS,
111
+ "ruby": RUBY_PATTERNS,
112
+ "java": JAVA_PATTERNS,
113
+ "php": PHP_PATTERNS,
114
+ "csharp": CSHARP_PATTERNS,
115
+ }
116
+ SKIP_DIRS = {
117
+ "node_modules",
118
+ ".git",
119
+ "dist",
120
+ "build",
121
+ "target",
122
+ ".cache",
123
+ ".pnpm-store",
124
+ ".venv",
125
+ "venv",
126
+ "__pycache__",
127
+ ".astro",
128
+ ".next",
129
+ ".nuxt",
130
+ "vendor",
131
+ }
132
+ TEST_DIRS = {"tests", "test", "__tests__", "spec", "specs"}
133
+ MAX_FILE_SIZE = 5 * 1024 * 1024
134
+
135
+
136
+ def should_skip_path(path: Path, include_tests: bool) -> bool:
137
+ parts = set(path.parts)
138
+ if parts & SKIP_DIRS:
139
+ return True
140
+ if not include_tests and parts & TEST_DIRS:
141
+ return True
142
+ return False
143
+
144
+
145
+ def detect_language(path: Path, langs: set[str]) -> str | None:
146
+ suf = path.suffix.lower()
147
+ for lang in langs:
148
+ if suf in LANG_EXT_MAP[lang]:
149
+ return lang
150
+ return None
151
+
152
+
153
+ def scan_file(file_path: Path, repo_root: Path, langs: set[str]) -> list[Finding]:
154
+ findings = []
155
+ lang = detect_language(file_path, langs)
156
+ if lang is None:
157
+ return findings
158
+ try:
159
+ if file_path.stat().st_size > MAX_FILE_SIZE:
160
+ return findings
161
+ text = file_path.read_text(encoding="utf-8", errors="ignore")
162
+ except (OSError, ValueError):
163
+ return findings
164
+ try:
165
+ rel = str(file_path.relative_to(repo_root))
166
+ except ValueError:
167
+ rel = str(file_path)
168
+
169
+ for title, sev, pattern, _lang in LANG_PATTERNS[lang]:
170
+ for m in re.finditer(pattern, text, re.MULTILINE):
171
+ line_no = text[: m.start()].count("\n") + 1
172
+ snippet = text.splitlines()[line_no - 1].strip()[:160]
173
+ findings.append(
174
+ Finding(
175
+ skill_id=SKILL_ID,
176
+ title=f"{title} at {rel}:{line_no}",
177
+ severity=sev,
178
+ target=f"{rel}:{line_no}",
179
+ detail=(
180
+ f"File {rel} line {line_no} calls {title}: `{snippet}`. "
181
+ "If the input is attacker-controllable, this is an "
182
+ "arbitrary-code-execution vector via deserialization "
183
+ "gadget chains."
184
+ ),
185
+ remediation=(
186
+ "Migrate to a schema-validated format. Python: json + "
187
+ "Pydantic. Ruby: JSON with strict schema. Java: Jackson "
188
+ "with allow-list. PHP: json_decode. .NET: System.Text.Json. "
189
+ "If polymorphic types are required, use HMAC-signed "
190
+ "serialization with explicit type allow-list. See "
191
+ "references/PLAYBOOK.md."
192
+ ),
193
+ cwe_id="CWE-502",
194
+ affected_control="OWASP A08:2021",
195
+ evidence=(("file", rel), ("line", line_no), ("language", lang), ("snippet", snippet)),
196
+ )
197
+ )
198
+ return findings
199
+
200
+
201
+ def walk_repo(root: Path, include_tests: bool, langs: set[str]) -> list[Path]:
202
+ out = []
203
+ valid_exts = set()
204
+ for lang in langs:
205
+ valid_exts |= LANG_EXT_MAP[lang]
206
+ for p in root.rglob("*"):
207
+ if not p.is_file():
208
+ continue
209
+ if should_skip_path(p, include_tests):
210
+ continue
211
+ if p.suffix.lower() not in valid_exts:
212
+ continue
213
+ out.append(p)
214
+ return out
215
+
216
+
217
+ def main(argv: list[str] | None = None) -> int:
218
+ parser = argparse.ArgumentParser(description="Insecure-deserialization scanner")
219
+ parser.add_argument("path", type=Path)
220
+ parser.add_argument("--output", default=None)
221
+ parser.add_argument("--format", choices=("json", "jsonl", "markdown"), default="markdown")
222
+ parser.add_argument("--min-severity", choices=("critical", "high", "medium", "low", "info"), default="info")
223
+ parser.add_argument("--include-tests", action="store_true")
224
+ parser.add_argument("--languages", default="all")
225
+ args = parser.parse_args(argv)
226
+
227
+ if args.languages == "all":
228
+ langs = set(LANG_PATTERNS.keys())
229
+ else:
230
+ langs = {lang.strip() for lang in args.languages.split(",") if lang.strip() in LANG_PATTERNS}
231
+
232
+ root = args.path.resolve()
233
+ if not root.exists():
234
+ sys.stderr.write(f"ERROR: path does not exist: {root}\n")
235
+ return 2
236
+
237
+ files = walk_repo(root, args.include_tests, langs)
238
+ findings: list[Finding] = []
239
+ for f in files:
240
+ findings.extend(scan_file(f, root, langs))
241
+
242
+ floor = Severity(args.min_severity)
243
+ findings = [f for f in findings if f.severity.numeric >= floor.numeric]
244
+
245
+ emit(findings, args.output, args.format, str(root))
246
+ return exit_code(findings)
247
+
248
+
249
+ if __name__ == "__main__":
250
+ sys.exit(main())
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: detecting-sql-injection-patterns
3
+ description: |
4
+ Scan a source tree for SQL-injection vulnerable patterns: string
5
+ concatenation into queries, f-string interpolation in SQL,
6
+ string-format substitution into raw queries, deprecated cursor
7
+ methods (cursor.execute with % formatting), Knex / Sequelize raw()
8
+ with template interpolation, sequelize.query with replacements.
9
+ Use when: pre-commit code review, post-feature SQL-touching
10
+ release, inheriting a legacy codebase that predates ORMs, or
11
+ post-bug-report investigation.
12
+ Threshold: any source line where SQL keywords (SELECT / INSERT /
13
+ UPDATE / DELETE / FROM / WHERE) appear in a string that's being
14
+ built via concatenation, f-string, %-format, or .format() with
15
+ variable input.
16
+ Trigger with: "scan for sqli", "sql injection patterns",
17
+ "check raw queries", "audit cursor.execute".
18
+ allowed-tools:
19
+ - Read
20
+ - Bash(python3:*)
21
+ - Glob
22
+ - Grep
23
+ disallowed-tools:
24
+ - Bash(rm:*)
25
+ - Bash(curl:*)
26
+ version: 3.0.0-dev
27
+ author: Jeremy Longshore <jeremy@intentsolutions.io>
28
+ license: MIT
29
+ compatibility: Designed for Claude Code
30
+ tags:
31
+ - security
32
+ - static-analysis
33
+ - sql-injection
34
+ - pentest
35
+ ---
36
+
37
+ # Detecting SQL Injection Patterns
38
+
39
+ ## Overview
40
+
41
+ SQL injection (CWE-89, OWASP A03:2021) remains one of the highest-
42
+ impact and most-easily-introduced vulnerability classes. The fix is
43
+ near-universal: use parameterized queries. The cause when introduced:
44
+ an engineer concatenates user input into a SQL string because the
45
+ ORM's parameterization mechanism wasn't obvious, or because they
46
+ "just need to add a quick condition."
47
+
48
+ The scanner reads source files and grades each apparent SQL-string
49
+ construction against the threshold table.
50
+
51
+ ## When the skill produces findings
52
+
53
+ | Finding | Severity | Threshold | Affected control |
54
+ |---|---|---|---|
55
+ | f-string with SQL keywords + user input | **CRITICAL** | `f"SELECT * FROM users WHERE id = {user_id}"` | CWE-89 |
56
+ | String concat into SQL keyword string | **CRITICAL** | `"SELECT ... " + var + " ..."` | CWE-89 |
57
+ | %-format SQL string | **HIGH** | `"SELECT * FROM %s" % table_name` | CWE-89 |
58
+ | `.format()` into SQL string | **HIGH** | `"SELECT {} FROM users".format(col)` | CWE-89 |
59
+ | `cursor.execute(f"...")` | **CRITICAL** | f-string passed directly to cursor.execute | CWE-89 |
60
+ | `sequelize.query` with template literal | **HIGH** | `sequelize.query(\`SELECT * FROM ${table}\`)` | CWE-89 |
61
+ | Knex / sequelize raw() with interpolation | **HIGH** | `knex.raw('SELECT * FROM ' + table)` | CWE-89 |
62
+ | Django `.extra()` with raw SQL | **MEDIUM** | `Model.objects.extra(where=['col = ' + val])` | CWE-89 |
63
+ | `cursor.executemany` with string-built query | **CRITICAL** | Same risk as execute | CWE-89 |
64
+ | JDBC `Statement.execute` with concat | **HIGH** | Java pattern: not PreparedStatement | CWE-89 |
65
+ | Rails `where()` with string interpolation | **HIGH** | `User.where("name = '#{name}'")` | CWE-89 |
66
+ | Go `db.Query` with `fmt.Sprintf` | **HIGH** | `db.Query(fmt.Sprintf("...", arg))` | CWE-89 |
67
+
68
+ ## Prerequisites
69
+
70
+ - Python 3.9+
71
+ - Target source tree on local filesystem
72
+
73
+ ## Instructions
74
+
75
+ ### Step 1 — Run the scanner
76
+
77
+ ```bash
78
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py /path/to/repo
79
+ ```
80
+
81
+ Options:
82
+
83
+ ```
84
+ Usage: scan_sqli.py PATH [OPTIONS]
85
+
86
+ Options:
87
+ --output FILE Write findings to FILE
88
+ --format FMT json | jsonl | markdown (default: markdown)
89
+ --min-severity SEV (default: info)
90
+ --include-tests Include test directories (default: excluded)
91
+ --languages LIST Comma-separated: python,javascript,typescript,java,
92
+ ruby,go,php,csharp (default: all)
93
+ ```
94
+
95
+ ### Step 2 — Interpret findings
96
+
97
+ CRITICAL = direct user-input → query string construction. Fix the
98
+ specific query AND audit nearby code for the same pattern.
99
+
100
+ HIGH = pattern suggests interpolation but might be a fixed
101
+ identifier (table/column name). Verify by reading the code.
102
+
103
+ MEDIUM = framework-specific pattern that's safe ONLY with strict
104
+ input validation (Django `.extra()`, Rails string `where()`).
105
+
106
+ ### Step 3 — Remediation
107
+
108
+ For each finding, the fix is the same shape per language: use the
109
+ language/library's parameterized-query API. See
110
+ `references/PLAYBOOK.md` for per-language snippets.
111
+
112
+ ### Step 4 — Cross-skill chaining
113
+
114
+ Consider running `scanning-for-hardcoded-secrets` (#10) on the same
115
+ target — same audit, different class of finding.
116
+
117
+ ## Examples
118
+
119
+ ### Example 1 — Pre-merge code review
120
+
121
+ ```bash
122
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py \
123
+ --min-severity high $(git diff --name-only main...HEAD | tr '\n' ' ')
124
+ ```
125
+
126
+ Scans only files changed in the current branch — fast feedback for
127
+ PR review.
128
+
129
+ ### Example 2 — Legacy codebase audit
130
+
131
+ ```bash
132
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-sql-injection-patterns/scripts/scan_sqli.py \
133
+ /path/to/legacy-app --format markdown > sqli-audit.md
134
+ ```
135
+
136
+ Expect dozens to hundreds of findings on a pre-ORM Java/PHP
137
+ codebase. Prioritize by reachability: the queries reached from
138
+ public endpoints first.
139
+
140
+ ## Output
141
+
142
+ JSON / JSONL / Markdown. Exit codes: 0 clean, 1 high/critical, 2 error.
143
+
144
+ ## Error Handling
145
+
146
+ - **False positives on fixed-identifier interpolation** (e.g.,
147
+ `f"SELECT * FROM {tablename}"` where `tablename` is hardcoded) →
148
+ verify manually. The scanner can't reason about variable
149
+ provenance without a full AST + control-flow pass.
150
+ - **String-built dynamic-table queries** are sometimes legitimate
151
+ (multi-tenant routing). Flag and review; the fix is usually
152
+ allow-list validation + identifier quoting.
153
+
154
+ ## Resources
155
+
156
+ - `references/THEORY.md` — Per-language interpolation patterns,
157
+ ORM-specific safe vs unsafe APIs, why prepared statements work
158
+ - `references/PLAYBOOK.md` — Per-language parameterization snippets
159
+ (Python sqlite3 + psycopg + SQLAlchemy, Node mysql2 + pg + knex
160
+ - sequelize, Ruby ActiveRecord, Go database/sql, Java JDBC
161
+ PreparedStatement, PHP PDO)