Input Validation That Sticks:Don't Become an Exploit Gadget
Most security bugs in small tools are not exotic crypto failures. They are trust in strings: paths, URLs, hostnames, and shell fragments that an attacker influences. Validation that sticks happens at every trust boundary, not once as a regex at the front door.
Name the Untrusted Inputs
List them:
- CLI arguments and flags
- Environment variables
- Config files
- Files whose paths come from users
- HTTP responses and webhooks
- Plugin or extension names
If it crossed a boundary, it is untrusted until proven otherwise.
Paths: Contain Them
Path traversal (../../etc/passwd) still works when you naively join user strings to a base directory.
- Resolve to a canonical path
- Ensure the result stays under an allowed root
- Refuse unexpected symlinks when that is your threat model
- Do not shell out with user paths unquoted
URLs and Network Targets
User-supplied URLs invite SSRF: your tool fetches internal metadata endpoints or localhost admin ports.
- Allow-list schemes (
httpsonly when possible) - Block link-local and metadata ranges when fetching on behalf of users
- Cap redirects
- Set timeouts
"Just curl whatever they gave us" is a feature request from attackers.
Commands and Shells
If you must run subprocesses:
- Prefer argument arrays over shell strings
- Never interpolate untrusted input into
shell=True - Allow-list executables when the command is selectable
Command injection is almost always a design smell, not bad luck.
Validate Types Early
Parse ints, enums, hostnames, and PEM blocks with strict parsers. Reject unknown fields in configs. Fail with a clear error pointing at the bad value.
Silent coercion hides attacks and bugs alike.
Test the Nasty Cases
Add fixtures for ../, weird unicode dots, newlines in arguments, oversized inputs, and file:// URLs. Security tests are just tests that remember adversaries.
Closing Thoughts
Input validation that sticks treats every boundary as hostile until proven kind. Contain paths, tame URLs, avoid shells, parse strictly. Do that and your CLI stays a tool instead of becoming someone else's gadget.