SQL injection, command injection, XSS, SSRF, and unsafe deserialization are not exotic. They are the everyday ways a working app hands an attacker the keys: read the whole database, run shell commands on your box, run scripts in your users' browsers, or make your server attack your own internal network. IOnclad reads your code as a real syntax tree, not a pile of text, and follows where user input actually goes.
OWASP groups the most common ways web apps get owned. IOnclad's web scanner maps its checks against that list, and these are the categories it digs into the hardest:
It parses real grammars for JavaScript, TypeScript, Python, Java, Go, Ruby, and PHP. When a file is in a language it does not have a grammar for, it falls back to a line-level regex pass so nothing goes completely unscanned.
Every one of these is the same story: untrusted input reaches a place it was never supposed to control. The damage just depends on the place.
When user input is glued into a SQL string, a shell command, or an eval(), the attacker stops being a user and starts being the author. A single ' OR 1=1 -- dumps the users table. A ; rm -rf in a filename runs on your server. IOnclad flags .query(), .execute(), exec(), os.system, Prisma's $queryRawUnsafe, Knex raw builders, and more when tainted input flows in.
An innerHTML assignment, a document.write(), or a React dangerouslySetInnerHTML built from user content lets an attacker run JavaScript in your visitors' sessions: steal tokens, hijack accounts, rewrite the page. IOnclad checks the left-hand side and the actual value, so it does not scream about a static JSON-LD block or a hardcoded constant.
If a request URL is built from user input, an attacker can point your server at 169.254.169.254 and read cloud metadata, or scan your private network from inside. IOnclad checks the first (URL) argument of fetch, axios, got, http.request, and Python requests for taint, and it knows a fixed URL with a user-controlled body is not SSRF.
Python pickle.loads, yaml.load without a safe loader, and Node node-serialize on untrusted bytes are straight-up remote code execution. The payload looks like data and behaves like a program. IOnclad only fires when the file actually imports a known-dangerous deserializer, so it does not flag every function named deserialize.
Most "free" scanners grep for strings like exec( and call it a finding. That fires on emailRegex.exec(input), which is not command execution, and misses the real bug when the dangerous call is nested inside a wrapper. The result is a wall of noise you learn to ignore, which is worse than no scan at all.
Injection almost always ships in code that looks completely normal:
# Looks fine. Tests pass. The query "works." const user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`); # A regex grep sees ".query(" + a backtick and either fires on EVERY # template literal (even static CREATE TABLE strings) or misses this # one because it's wrapped: await retry(() => db.query(...)). const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]); # Parameterized. The id is data now, never instruction. ✓
IOnclad's OWASP web scanner parses your code with tree-sitter and walks the resulting syntax tree. It runs 100% offline, so your code never leaves your machine:
db.execute(`CREATE TABLE ...`) with no interpolation does not fire, and a nested dangerous call inside a wrapper still does.req.body, request.args, PHP superglobals, Go r.URL.Query, Java request.getParameter, and more) reaches the dangerous sink. That is the difference between catching the bug and drowning you in false positives.res.redirect is matched but a custom myres.redirect is not, and got() is matched but handleForgot() is not..format() and percent-style SQL.IOnclad finds known-bad patterns and common misconfigurations. It is a fast pre-launch check, not a full penetration test, so treat a clean result as one strong signal, not a guarantee.
$1, ?, :param, %s with a params tuple). Never build SQL with string interpolation or concatenation, even into a separate variable first.execFile or spawn with an argument array, or subprocess.run([...], shell=False). Drop eval() entirely.textContent, and sanitize with DOMPurify when you genuinely need to render HTML. Validate URLs and reject javascript: and data: schemes.yaml.safe_load(), never raw pickle on untrusted data.Scan your code and full git history in under a minute. Free verdict, no signup, nothing leaves your machine.