@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,354 @@
1
+ #!/usr/bin/env python3
2
+ """Static-analysis scan for SQL-injection vulnerable patterns.
3
+
4
+ References:
5
+ CWE-89 Improper Neutralization of Special Elements used in an SQL Command
6
+ OWASP A03:2021 Injection
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-sql-injection-patterns"
24
+
25
+ # Build the SQL-keyword detector once
26
+ SQL_KEYWORDS = r"(?:SELECT|INSERT\s+INTO|UPDATE\s+\w|DELETE\s+FROM|REPLACE\s+INTO|MERGE\s+INTO|UPSERT|DROP\s+\w|TRUNCATE|ALTER\s+\w|CREATE\s+\w|FROM\s+\w|WHERE|JOIN|GROUP\s+BY|ORDER\s+BY)"
27
+
28
+
29
+ def py_patterns():
30
+ """Python SQL-injection patterns."""
31
+ return [
32
+ (
33
+ "Python f-string with SQL keywords",
34
+ Severity.CRITICAL,
35
+ rf"""f["']\s*{SQL_KEYWORDS}\s+[^"']*\{{[^}}]+\}}""",
36
+ "python",
37
+ ),
38
+ (
39
+ "Python cursor.execute with f-string argument",
40
+ Severity.CRITICAL,
41
+ r"""\b(?:cursor|conn|db)\s*\.\s*execute(?:many)?\s*\(\s*f["']""",
42
+ "python",
43
+ ),
44
+ (
45
+ "Python string concat into SQL keyword string",
46
+ Severity.CRITICAL,
47
+ rf"""["']\s*{SQL_KEYWORDS}[^"']*["']\s*\+\s*\w""",
48
+ "python",
49
+ ),
50
+ (
51
+ "Python %-format on SQL string",
52
+ Severity.HIGH,
53
+ rf"""["']\s*{SQL_KEYWORDS}[^"']*%\s*[sdif]?["']\s*%\s*""",
54
+ "python",
55
+ ),
56
+ (
57
+ "Python .format() on SQL string",
58
+ Severity.HIGH,
59
+ rf"""["']\s*{SQL_KEYWORDS}[^"']*\{{[^}}]*\}}[^"']*["']\s*\.\s*format\b""",
60
+ "python",
61
+ ),
62
+ ("Django .extra() with raw SQL", Severity.MEDIUM, r"\.\s*extra\s*\(\s*(?:where|select|tables)\s*=", "python"),
63
+ ]
64
+
65
+
66
+ def js_patterns():
67
+ """JavaScript / TypeScript SQL-injection patterns."""
68
+ return [
69
+ (
70
+ "JS template literal with SQL keywords + interpolation",
71
+ Severity.CRITICAL,
72
+ rf"""`\s*{SQL_KEYWORDS}[^`]*\$\{{[^}}]+\}}""",
73
+ "javascript",
74
+ ),
75
+ (
76
+ "JS sequelize.query with template literal interpolation",
77
+ Severity.HIGH,
78
+ r"""sequelize\s*\.\s*query\s*\(\s*`[^`]*\$\{""",
79
+ "javascript",
80
+ ),
81
+ (
82
+ "JS knex.raw with template literal interpolation",
83
+ Severity.HIGH,
84
+ r"""(?:knex|db)\s*\.\s*raw\s*\(\s*`[^`]*\$\{""",
85
+ "javascript",
86
+ ),
87
+ (
88
+ "JS knex.raw with string concat",
89
+ Severity.HIGH,
90
+ r"""(?:knex|db)\s*\.\s*raw\s*\(\s*['"][^'"]*['"]\s*\+\s*\w""",
91
+ "javascript",
92
+ ),
93
+ (
94
+ "JS mysql/pg .query with concat",
95
+ Severity.HIGH,
96
+ rf"""\.\s*query\s*\(\s*["']\s*{SQL_KEYWORDS}[^"']*["']\s*\+\s*\w""",
97
+ "javascript",
98
+ ),
99
+ ]
100
+
101
+
102
+ def ruby_patterns():
103
+ """Ruby SQL-injection patterns."""
104
+ return [
105
+ ("Rails .where with string interpolation", Severity.HIGH, r"""\.\s*where\s*\(\s*["'][^"']*\#\{""", "ruby"),
106
+ (
107
+ "Rails find_by_sql with string interpolation",
108
+ Severity.HIGH,
109
+ r"""\.\s*find_by_sql\s*\(\s*["'][^"']*\#\{""",
110
+ "ruby",
111
+ ),
112
+ (
113
+ "Rails connection.execute with interpolation",
114
+ Severity.CRITICAL,
115
+ r"""ActiveRecord::Base\.connection\s*\.\s*execute\s*\(\s*["'][^"']*\#\{""",
116
+ "ruby",
117
+ ),
118
+ ]
119
+
120
+
121
+ def go_patterns():
122
+ """Go SQL-injection patterns."""
123
+ return [
124
+ (
125
+ "Go fmt.Sprintf into db.Query/QueryRow/Exec",
126
+ Severity.HIGH,
127
+ r"""(?:Query|QueryRow|Exec)(?:Context)?\s*\(\s*fmt\.Sprintf\s*\(""",
128
+ "go",
129
+ ),
130
+ (
131
+ "Go string concat into db.Query",
132
+ Severity.CRITICAL,
133
+ rf"""(?:Query|QueryRow|Exec)(?:Context)?\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\+""",
134
+ "go",
135
+ ),
136
+ ]
137
+
138
+
139
+ def java_patterns():
140
+ """Java SQL-injection patterns."""
141
+ return [
142
+ (
143
+ "Java Statement.execute with concat",
144
+ Severity.HIGH,
145
+ rf"""(?:Statement|stmt)\.\s*execute(?:Query|Update)?\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\+""",
146
+ "java",
147
+ ),
148
+ (
149
+ "Java String.format on SQL string",
150
+ Severity.HIGH,
151
+ rf"""String\.format\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*%[sd]""",
152
+ "java",
153
+ ),
154
+ ]
155
+
156
+
157
+ def php_patterns():
158
+ """PHP SQL-injection patterns."""
159
+ return [
160
+ (
161
+ "PHP mysqli_query with interpolated string",
162
+ Severity.CRITICAL,
163
+ rf"""mysqli_query\s*\(\s*\$[a-z_]+\s*,\s*"[^"]*{SQL_KEYWORDS}[^"]*\$""",
164
+ "php",
165
+ ),
166
+ (
167
+ "PHP mysql_query with concat",
168
+ Severity.CRITICAL,
169
+ rf"""mysql_query\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\.\s*\$""",
170
+ "php",
171
+ ),
172
+ (
173
+ "PHP PDO->query with concat (should be prepare)",
174
+ Severity.HIGH,
175
+ rf"""->query\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\.\s*\$""",
176
+ "php",
177
+ ),
178
+ ]
179
+
180
+
181
+ def csharp_patterns():
182
+ """C# SQL-injection patterns."""
183
+ return [
184
+ (
185
+ "C# SqlCommand with string interpolation",
186
+ Severity.CRITICAL,
187
+ rf"""new SqlCommand\s*\(\s*\$"[^"]*{SQL_KEYWORDS}[^"]*\{{""",
188
+ "csharp",
189
+ ),
190
+ (
191
+ "C# SqlCommand with string concat",
192
+ Severity.CRITICAL,
193
+ rf"""new SqlCommand\s*\(\s*"[^"]*{SQL_KEYWORDS}[^"]*"\s*\+""",
194
+ "csharp",
195
+ ),
196
+ ]
197
+
198
+
199
+ LANG_EXT_MAP = {
200
+ "python": {".py"},
201
+ "javascript": {".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"},
202
+ "ruby": {".rb"},
203
+ "go": {".go"},
204
+ "java": {".java", ".kt", ".scala"},
205
+ "php": {".php"},
206
+ "csharp": {".cs"},
207
+ }
208
+ LANG_PATTERNS = {
209
+ "python": py_patterns,
210
+ "javascript": js_patterns,
211
+ "ruby": ruby_patterns,
212
+ "go": go_patterns,
213
+ "java": java_patterns,
214
+ "php": php_patterns,
215
+ "csharp": csharp_patterns,
216
+ }
217
+ SKIP_DIRS = {
218
+ "node_modules",
219
+ ".git",
220
+ "dist",
221
+ "build",
222
+ "target",
223
+ ".cache",
224
+ ".pnpm-store",
225
+ ".venv",
226
+ "venv",
227
+ "__pycache__",
228
+ ".astro",
229
+ ".next",
230
+ ".nuxt",
231
+ "vendor",
232
+ }
233
+ TEST_DIRS = {"tests", "test", "__tests__", "spec", "specs"}
234
+ MAX_FILE_SIZE = 5 * 1024 * 1024
235
+
236
+
237
+ def should_skip_path(path: Path, include_tests: bool) -> bool:
238
+ parts = set(path.parts)
239
+ if parts & SKIP_DIRS:
240
+ return True
241
+ if not include_tests and parts & TEST_DIRS:
242
+ return True
243
+ return False
244
+
245
+
246
+ def detect_language(path: Path, langs: set[str]) -> str | None:
247
+ suf = path.suffix.lower()
248
+ for lang in langs:
249
+ if suf in LANG_EXT_MAP[lang]:
250
+ return lang
251
+ return None
252
+
253
+
254
+ def scan_file(file_path: Path, repo_root: Path, langs: set[str]) -> list[Finding]:
255
+ findings = []
256
+ lang = detect_language(file_path, langs)
257
+ if lang is None:
258
+ return findings
259
+ try:
260
+ if file_path.stat().st_size > MAX_FILE_SIZE:
261
+ return findings
262
+ text = file_path.read_text(encoding="utf-8", errors="ignore")
263
+ except (OSError, ValueError):
264
+ return findings
265
+ try:
266
+ rel = str(file_path.relative_to(repo_root))
267
+ except ValueError:
268
+ rel = str(file_path)
269
+
270
+ for title, sev, pattern, _lang in LANG_PATTERNS[lang]():
271
+ for m in re.finditer(pattern, text, re.IGNORECASE | re.MULTILINE):
272
+ line_no = text[: m.start()].count("\n") + 1
273
+ snippet = text.splitlines()[line_no - 1].strip()[:160]
274
+ findings.append(
275
+ Finding(
276
+ skill_id=SKILL_ID,
277
+ title=f"{title} at {rel}:{line_no}",
278
+ severity=sev,
279
+ target=f"{rel}:{line_no}",
280
+ detail=(
281
+ f"File {rel} line {line_no} matches the {title} "
282
+ f"pattern: `{snippet}`. If the interpolated value can "
283
+ "be influenced by external input, this is a "
284
+ "SQL-injection vector."
285
+ ),
286
+ remediation=(
287
+ "Replace the string-built query with a parameterized "
288
+ "query API. Per language: Python sqlite3/psycopg uses "
289
+ "`?` or `%s` placeholders; Node mysql2/pg uses `?` or "
290
+ "`$1`; Ruby uses `where(...)` with hash args; Go uses "
291
+ '`db.Query("SELECT ... WHERE x = ?", arg)`; Java '
292
+ "PreparedStatement with `setString(1, val)`. See "
293
+ "references/PLAYBOOK.md for per-language patterns."
294
+ ),
295
+ cwe_id="CWE-89",
296
+ affected_control="OWASP A03:2021",
297
+ evidence=(("file", rel), ("line", line_no), ("language", lang), ("snippet", snippet)),
298
+ )
299
+ )
300
+ return findings
301
+
302
+
303
+ def walk_repo(root: Path, include_tests: bool, langs: set[str]) -> list[Path]:
304
+ out = []
305
+ valid_exts = set()
306
+ for lang in langs:
307
+ valid_exts |= LANG_EXT_MAP[lang]
308
+ for p in root.rglob("*"):
309
+ if not p.is_file():
310
+ continue
311
+ if should_skip_path(p, include_tests):
312
+ continue
313
+ if p.suffix.lower() not in valid_exts:
314
+ continue
315
+ out.append(p)
316
+ return out
317
+
318
+
319
+ def main(argv: list[str] | None = None) -> int:
320
+ parser = argparse.ArgumentParser(description="SQL-injection pattern scanner")
321
+ parser.add_argument("path", type=Path)
322
+ parser.add_argument("--output", default=None)
323
+ parser.add_argument("--format", choices=("json", "jsonl", "markdown"), default="markdown")
324
+ parser.add_argument("--min-severity", choices=("critical", "high", "medium", "low", "info"), default="info")
325
+ parser.add_argument("--include-tests", action="store_true")
326
+ parser.add_argument(
327
+ "--languages", default="all", help="Comma-separated: python,javascript,ruby,go,java,php,csharp (default: all)"
328
+ )
329
+ args = parser.parse_args(argv)
330
+
331
+ if args.languages == "all":
332
+ langs = set(LANG_PATTERNS.keys())
333
+ else:
334
+ langs = {lang.strip() for lang in args.languages.split(",") if lang.strip() in LANG_PATTERNS}
335
+
336
+ root = args.path.resolve()
337
+ if not root.exists():
338
+ sys.stderr.write(f"ERROR: path does not exist: {root}\n")
339
+ return 2
340
+
341
+ files = walk_repo(root, args.include_tests, langs)
342
+ findings: list[Finding] = []
343
+ for f in files:
344
+ findings.extend(scan_file(f, root, langs))
345
+
346
+ floor = Severity(args.min_severity)
347
+ findings = [f for f in findings if f.severity.numeric >= floor.numeric]
348
+
349
+ emit(findings, args.output, args.format, str(root))
350
+ return exit_code(findings)
351
+
352
+
353
+ if __name__ == "__main__":
354
+ sys.exit(main())
@@ -0,0 +1,182 @@
1
+ ---
2
+ name: detecting-ssl-cert-issues
3
+ description: |
4
+ Audit a target's TLS certificate beyond protocol/expiry — chain ordering,
5
+ OCSP stapling, revocation status, Certificate Transparency presence,
6
+ key-usage flags, and over-broad wildcards.
7
+ Use when: TLS handshake already passes (skill #1 analyzing-tls-config
8
+ cleared) but you suspect the cert posture is fragile. Auditors flag this
9
+ during SOC2 readiness when a renewal slipped or an intermediate was
10
+ rotated.
11
+ Threshold: missing OCSP stapling on production, fewer than 2 SCTs in
12
+ the cert, intermediate served out of order, key usage missing
13
+ digitalSignature/keyEncipherment, revoked cert presented, or wildcard
14
+ scope of 2-level (e.g., *.com is rejection; *.api.example.com is fine).
15
+ Trigger with: "check cert revocation", "audit ocsp", "ct log check",
16
+ "cert chain audit".
17
+ allowed-tools:
18
+ - Read
19
+ - Bash(python3:*)
20
+ - Bash(openssl:*)
21
+ disallowed-tools:
22
+ - Bash(rm:*)
23
+ - Edit(/etc/*)
24
+ - Write(/etc/*)
25
+ version: 3.0.0-dev
26
+ author: Jeremy Longshore <jeremy@intentsolutions.io>
27
+ license: MIT
28
+ compatibility: Designed for Claude Code
29
+ tags:
30
+ - security
31
+ - tls
32
+ - ocsp
33
+ - certificate-transparency
34
+ - pentest
35
+ ---
36
+
37
+ # Detecting SSL Certificate Issues
38
+
39
+ ## Overview
40
+
41
+ This skill is the second-level cert audit, run after `analyzing-tls-config`
42
+ clears the protocol+cipher+expiry+hostname basics. It surfaces issues
43
+ that don't break the handshake today but make the cert fragile or open
44
+ to soft-bypass attacks: missing OCSP stapling forces clients to phone
45
+ home to the CA (privacy + latency hit), missing Certificate Transparency
46
+ SCTs are rejected by Chrome since 2018, an out-of-order chain confuses
47
+ older clients, and over-broad wildcards expand the blast radius of any
48
+ future key compromise.
49
+
50
+ ## When the skill produces findings
51
+
52
+ | Finding | Severity | Threshold | Affected control |
53
+ |---|---|---|---|
54
+ | Revoked certificate presented | **CRITICAL** | OCSP responder says "revoked" | RFC 6960 |
55
+ | Missing or invalid OCSP staple | **HIGH** | No status_request response on production | RFC 6066, CA/B BR |
56
+ | Fewer than 2 SCTs embedded | **HIGH** | CT-policy violation (Chrome enforces) | RFC 6962, CA/B Baseline Reqs |
57
+ | Intermediate served out of RFC 5246 order | **MEDIUM** | Server sends root before leaf | RFC 5246 §7.4.2 |
58
+ | AIA extension missing | **MEDIUM** | No CA Issuers / OCSP URL in cert | RFC 5280 §4.2.2.1 |
59
+ | Over-broad wildcard | **HIGH** | Scope of 2-level or wider (e.g., *.com) | CA/B Baseline Reqs §3.2.2 |
60
+ | Wildcard at apex SAN | **LOW** | *.example.com without example.com | RFC 6125 §6.4.3 |
61
+ | Key Usage missing digitalSignature | **MEDIUM** | KU bit absent for TLS server cert | RFC 5280 §4.2.1.3 |
62
+ | Cert chain longer than 4 | **LOW** | Performance + trust expansion | CA/B Baseline Reqs |
63
+
64
+ ## Prerequisites
65
+
66
+ - Python 3.9+ with `cryptography` library
67
+ - `openssl` CLI 1.1.1+ (for OCSP query + chain enumeration)
68
+ - Authorization for non-local targets (see `references/AUTHORIZATION.md`
69
+ in skill #1 `analyzing-tls-config` for the canonical pattern)
70
+
71
+ ## Instructions
72
+
73
+ ### Step 1 — Confirm Authorization
74
+
75
+ Active scan; ask the user verbatim:
76
+
77
+ > "Do you have authorization to perform TLS testing on this target?
78
+ > I need confirmation before proceeding."
79
+
80
+ ### Step 2 — Run the scanner
81
+
82
+ ```bash
83
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py \
84
+ https://target.example.com \
85
+ --authorized
86
+ ```
87
+
88
+ Options:
89
+
90
+ ```
91
+ Usage: check_cert_chain.py URL [OPTIONS]
92
+
93
+ Options:
94
+ --authorized Attest authorization for non-local targets (required)
95
+ --port PORT Target port (default: 443)
96
+ --output FILE Write findings to FILE (default: stdout)
97
+ --format FMT json | jsonl | markdown (default: markdown)
98
+ --min-severity SEV critical|high|medium|low|info (default: info)
99
+ --timeout SECS Per-probe timeout (default: 10)
100
+ --skip-ocsp Skip OCSP responder query (offline mode)
101
+ ```
102
+
103
+ ### Step 3 — Interpret findings
104
+
105
+ CRITICAL/HIGH map to immediate action items; MEDIUM/LOW to backlog
106
+ hardening. Cross-reference `references/PLAYBOOK.md` for OCSP stapling
107
+ config snippets per server type.
108
+
109
+ ### Step 4 — Cross-skill chaining
110
+
111
+ - After this skill, suggest `checking-http-security-headers` (#4) to
112
+ verify HSTS preload status — HSTS preload depends on a clean cert
113
+ chain to be effective.
114
+ - For CI integration patterns, see `references/PLAYBOOK.md` § CI
115
+ posture-monitoring.
116
+
117
+ ## Examples
118
+
119
+ ### Example 1 — OCSP stapling audit before adopting must-staple
120
+
121
+ User: "We're considering Must-Staple — what's our OCSP stapling posture
122
+ look like across endpoints?"
123
+
124
+ ```bash
125
+ for ENDPOINT in https://api.example.com https://app.example.com https://admin.example.com; do
126
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py \
127
+ "$ENDPOINT" --authorized --min-severity medium
128
+ done
129
+ ```
130
+
131
+ If any endpoint reports "Missing OCSP staple" HIGH, adopting Must-Staple
132
+ on that cert breaks it on next renewal until OCSP-stapling config
133
+ lands. Pair with `references/PLAYBOOK.md` § OCSP stapling for nginx /
134
+ Caddy / Apache config.
135
+
136
+ ### Example 2 — CT-log compliance check before public launch
137
+
138
+ User: "Pre-launch — does our cert have enough SCTs for Chrome to trust it?"
139
+
140
+ ```bash
141
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py \
142
+ https://new-site.example.com --authorized
143
+ ```
144
+
145
+ The scanner extracts embedded SCTs from the cert's CT extension. <2
146
+ SCTs → HIGH finding; Chrome's CT enforcement policy rejects the
147
+ connection silently in HTTPS, leaving users with a generic error.
148
+
149
+ ### Example 3 — Wildcard scope audit
150
+
151
+ User: "An auditor flagged our wildcard cert as too broad."
152
+
153
+ ```bash
154
+ python3 ${CLAUDE_PLUGIN_ROOT}/skills/detecting-ssl-cert-issues/scripts/check_cert_chain.py \
155
+ https://example.com --authorized --format json | jq '.[] | select(.title | contains("wildcard"))'
156
+ ```
157
+
158
+ The JSON output captures the wildcard scope; pair with the auditor's
159
+ request to either narrow the SAN list or move to per-service certs.
160
+
161
+ ## Output
162
+
163
+ JSON / JSONL / Markdown per `lib/report.py`. Exit codes: 0 clean, 1
164
+ high/critical, 2 error.
165
+
166
+ ## Error Handling
167
+
168
+ - **OCSP responder timeout** → emitted as MEDIUM finding (not an error
169
+ exit) with note to investigate responder availability.
170
+ - **CT log lookup unavailable** → falls back to embedded-SCT parsing
171
+ only; emits INFO note.
172
+ - **Untrusted cert** → out of scope (skill #1 handles); this skill assumes
173
+ the chain validates and looks at deeper posture.
174
+
175
+ ## Resources
176
+
177
+ - `references/THEORY.md` — OCSP, CT, AIA, chain ordering, wildcard
178
+ scope reasoning with RFC anchors
179
+ - `references/PLAYBOOK.md` — OCSP stapling config per server type +
180
+ CT-log compliance + AIA extension correctness
181
+ - `../analyzing-tls-config/references/AUTHORIZATION.md` — canonical ROE
182
+ template + 2-step gate (shared across all active-scan skills)