Add STARTTLS support to testssl-inspector#197
Conversation
Code Review by Qodo
Context used✅ Compliance rules (platform):
17 rules 1. Node script in connector plugin
|
📝 WalkthroughWalkthroughSTARTTLS support is added to the ChangesSTARTTLS Support for testssl-inspector
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Summary by QodoAdd STARTTLS support to testssl-inspector scan wrapper WalkthroughsDescription• Add --starttls= to enable scanning STARTTLS services via testssl.sh. • Validate allowed STARTTLS protocols and default ports; preserve explicit target ports. • Document STARTTLS usage and examples in README and scan command docs. Diagramgraph TD
A["scan.js (CLI)"] --> B["parseArgs()"] --> C["STARTTLS ports map"] --> D["withDefaultStarttlsPort()"] --> E["resolveRunner()/testsslArgs()"] --> F["testssl.sh"]
High-Level AssessmentThe following are alternative approaches to this PR: 1. Pass through `--starttls` without protocol allowlist/port defaults
2. Use a host/port parser that handles IPv6 literals explicitly
Recommendation: Current approach (explicit allowlist + default-port inference + passthrough to testssl) is a good UX/robustness balance. Consider tightening target parsing for IPv6 (':' detection) if IPv6 targets are in scope, and remove/avoid unused parameters (e.g., File ChangesEnhancement (1)
Documentation (2)
|
Greptile SummaryThis PR wires
Confidence Score: 3/5Functional for the happy path, but the output document will contain a wrong URI scheme for every STARTTLS scan until the normalizer is made protocol-aware. The plugins/connectors/testssl-inspector/scripts/scan.js — specifically Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["CLI argv"] -->|"--starttls=smtp"| B["parseArgs()"]
B -->|"proto not in STARTTLS_PORTS"| C["fail EXIT.USAGE"]
B -->|"valid proto"| D["args.starttls = proto"]
D --> E["resolveRunner({ starttls })"]
E -->|"useDocker"| F["runner.argv = (t,m) => docker + testsslArgs(t,m,starttls,outDir)"]
E -->|"binary"| G["runner.argv = (t,m) => testsslArgs(t,m,starttls)"]
D --> H["withDefaultStarttlsPort(target, starttls)"]
H -->|"target has ':'"| I["preserve explicit port"]
H -->|"no ':' in target"| J["append STARTTLS_PORTS[proto]"]
I --> K["runTestssl(runner, finalTarget, mode, log)"]
J --> K
K --> L["runner.argv(target, mode)"]
L --> M["testsslArgs() includes '--starttls proto'"]
M --> N["testssl.sh invocation"]
N --> O["normalizeTargetFindings()"]
O -->|"⚠ always https://"| P["resource.uri = https://host:port/"]
|
| } | ||
|
|
||
| async function runTestssl(runner, target, mode, log) { | ||
| async function runTestssl(runner, target, mode, starttls, log) { |
There was a problem hiding this comment.
The
starttls parameter added to runTestssl is never read inside the function body. The --starttls flag is already baked into the runner.argv closure built by resolveRunner, so the argument has no effect here. The dead parameter may mislead future maintainers into thinking they can call runTestssl with a different starttls value and get a different result.
| async function runTestssl(runner, target, mode, starttls, log) { | |
| async function runTestssl(runner, target, mode, log) { |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| const STARTTLS_PORTS = Object.freeze({ | ||
| smtp: 25, | ||
| imap: 143, | ||
| pop3: 110, | ||
| ftp: 21, | ||
| ldap: 389, | ||
| postgres: 5432, | ||
| mysql: 3306, | ||
| smtps: 465, | ||
| }); | ||
|
|
||
| const SUPPORTED_STARTTLS = Object.keys(STARTTLS_PORTS).join(', '); |
There was a problem hiding this comment.
1. Node script in connector plugin 📘 Rule violation ⌂ Architecture
The modified plugins/connectors/testssl-inspector/scripts/scan.js is a Node.js entrypoint located under a non-allowed (non-persona) plugin directory. This violates the requirement that Node.js scripts only exist under the allowed persona plugins, creating compliance and runtime-governance risk.
Agent Prompt
## Issue description
`plugins/connectors/testssl-inspector/scripts/scan.js` is a Node.js runtime entrypoint, but it lives under `plugins/connectors/...`, which is not one of the allowed persona plugins.
## Issue Context
Compliance policy restricts Node.js scripts to persona plugins named exactly: `grc-engineer`, `grc-auditor`, `grc-internal`, or `grc-tprm`. This PR modifies a Node.js script in a connector plugin, which violates that restriction.
## Fix Focus Areas
- plugins/connectors/testssl-inspector/scripts/scan.js[35-46]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| const out = outDir || CACHE_DIR; | ||
| const jsonPath = path.join(out, `testssl-raw-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`); | ||
| const base = [ | ||
| '--quiet', '--warnings', 'off', '--color', '0', | ||
| '--jsonfile-pretty', jsonPath, | ||
| ]; | ||
| if (mode === 'fast') base.push('--fast'); | ||
| if (starttls) { | ||
| base.push('--starttls', starttls); | ||
| } | ||
| base.push(target); | ||
| base.__jsonPath = jsonPath; | ||
| return base; | ||
| } | ||
|
|
||
| async function runTestssl(runner, target, mode, log) { | ||
| async function runTestssl(runner, target, mode, starttls, log) { | ||
| const argv = runner.argv(target, mode); | ||
| const jsonPath = argv.__jsonPath; | ||
| log(`scanning ${target}`); |
There was a problem hiding this comment.
2. Docker json path mismatch 🐞 Bug ☼ Reliability
In Docker mode the JSON output path is generated as a container path (/tmp/scan-out/...), but runTestssl() later reads that same path on the host filesystem, so --docker scans will fail when trying to load the JSON results.
Agent Prompt
### Issue description
In `--docker` mode, `testsslArgs()` sets `argv.__jsonPath` to a container path (e.g. `/tmp/scan-out/...json`). After `docker run` completes, `runTestssl()` uses `fs.readFile(jsonPath)` on the host, which should instead read from `${CACHE_DIR}/...json` (the bind-mounted directory).
### Issue Context
Docker binds `${CACHE_DIR}` (host) to `/tmp/scan-out` (container). The JSON file is written inside the container to `/tmp/scan-out/<file>`, which appears on the host at `${CACHE_DIR}/<file>`.
### Fix Focus Areas
- plugins/connectors/testssl-inspector/scripts/scan.js[173-201]
- plugins/connectors/testssl-inspector/scripts/scan.js[237-263]
### Suggested approach
- Generate a filename once (e.g. `const fname = ...`), and set:
- `containerJsonPath = path.posix.join('/tmp/scan-out', fname)` for the `--jsonfile-pretty` argument.
- `hostJsonPath = path.join(CACHE_DIR, fname)` for `base.__jsonPath`.
- Alternatively, keep `--jsonfile-pretty` pointing at the container path but translate `argv.__jsonPath` to the host path in the docker runner (e.g., replace the `/tmp/scan-out/` prefix with `${CACHE_DIR}/` before reading).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/connectors/testssl-inspector/scripts/scan.js`:
- Around line 230-234: The withDefaultStarttlsPort function incorrectly detects
explicit ports by checking if target.includes(':'), which treats IPv6 addresses
as having explicit ports even when they don't (e.g., [2001:db8::1] has colons
but no port). Fix the port detection logic to properly handle IPv6 literals in
square brackets: if the target starts with '[', check whether there's a colon
after the closing bracket ']' to determine if a port is specified; otherwise,
check for the presence of a colon to determine if a port exists. This ensures
the default port is correctly appended only when no explicit port is provided.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 84983c7b-e419-4c9f-94bc-9b7adf3c4081
📒 Files selected for processing (3)
plugins/connectors/testssl-inspector/README.mdplugins/connectors/testssl-inspector/commands/scan.mdplugins/connectors/testssl-inspector/scripts/scan.js
| function withDefaultStarttlsPort(target, starttls) { | ||
| if (!starttls) return target; | ||
| if (target.includes(':')) return target; | ||
|
|
||
| return `${target}:${STARTTLS_PORTS[starttls]}`; |
There was a problem hiding this comment.
Fix explicit-port detection for IPv6 targets
target.includes(':') treats IPv6 host literals as “has explicit port”, so --starttls=<proto> won’t append the default port for targets like [2001:db8::1]. That breaks the “append default port when port is omitted” behavior.
Suggested patch
function withDefaultStarttlsPort(target, starttls) {
if (!starttls) return target;
- if (target.includes(':')) return target;
+ const hasExplicitPort = target.startsWith('[')
+ ? /^\[[^\]]+\]:\d+$/.test(target) // bracketed IPv6 with :port
+ : /:\d+$/.test(target); // hostname/IPv4 with :port
+ if (hasExplicitPort) return target;
return `${target}:${STARTTLS_PORTS[starttls]}`;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/connectors/testssl-inspector/scripts/scan.js` around lines 230 - 234,
The withDefaultStarttlsPort function incorrectly detects explicit ports by
checking if target.includes(':'), which treats IPv6 addresses as having explicit
ports even when they don't (e.g., [2001:db8::1] has colons but no port). Fix the
port detection logic to properly handle IPv6 literals in square brackets: if the
target starts with '[', check whether there's a colon after the closing bracket
']' to determine if a port is specified; otherwise, check for the presence of a
colon to determine if a port exists. This ensures the default port is correctly
appended only when no explicit port is provided.
Summary
Adds
--starttls=<proto>support to the testssl-inspector scan wrapper so users can scan STARTTLS services such as SMTP, IMAP, POP3, FTP, LDAP, Postgres, MySQL, and SMTPS.Changes
--starttls=<proto>inscan.jsEXIT.USAGEmail.example.com:587--starttls <proto>through totestssl.shcommands/scan.mdandREADME.mdVerification
Syntax check:
Result:
Contract fixture validation:
Result:
Happy-path STARTTLS argument parsing:
Result:
This confirms
--starttls=smtpis no longer rejected as an unknown flag. The command reaches runner/tool resolution, buttestssl.shis not installed locally.Explicit port preservation smoke test:
Result:
This confirms an explicit-port STARTTLS command is accepted and reaches runner/tool resolution.
Unknown protocol path:
Result:
Closes #165
Summary by CodeRabbit
New Features
Documentation