> ## Documentation Index
> Fetch the complete documentation index at: https://docs.iriscode.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Smell Detection: Nine Patterns Across Nine Languages

> Iris Code detects nine security anti-patterns in every supported language. Each finding reduces the health score, surfaces as a sidebar blocker, and can emit a squiggle via inline diagnostics.

None of these nine patterns is a defect on its own. Each one is a way a vulnerability gets introduced later.

`eval` with a literal string is safe until the string becomes dynamic. A query built by concatenation is safe until one of its values comes from a request. Because the risk is real but latent, security smells count as **Blockers**, alongside hardcoded secrets, rather than as structural warnings like file length.

All nine are detected across TypeScript, JavaScript, Go, Python, Ruby, C#, Java, and Rust wherever they apply, and `enableSecuritySmells` enables or disables the group. Not every pattern exists in every language: Java has no dynamic code evaluation in the sense the eval rule means, so its process-execution check is a separate rule, and Rust has neither.

## Patterns

### eval() / exec() - `evalUsage`

Calls to `eval()` in JavaScript and TypeScript, and `exec()` in Python and Go. These execute whatever string they are given as code, so if any part of that string ever becomes user-controlled, it is a direct code-injection route.

| Language                | Detected form       |
| ----------------------- | ------------------- |
| TypeScript / JavaScript | `eval(...)`         |
| Python                  | `exec(...)`         |
| Go                      | `exec.Command(...)` |

<Warning>
  A literal argument today is still a risk, because the next change to that code may make it dynamic without anyone reconsidering the call. A dispatch table or a purpose-built expression parser removes the category entirely.
</Warning>

***

### SQL concatenation - `sqlConcatenation`

SQL assembled by string concatenation or interpolation, which is the root cause of almost every SQL injection vulnerability.

| Language                | Detected form                          |
| ----------------------- | -------------------------------------- |
| TypeScript / JavaScript | `"SELECT … " + variable`               |
| TypeScript / JavaScript | `` `SELECT … ${variable}` ``           |
| Go                      | `"SELECT … " + variable`               |
| Go                      | `fmt.Sprintf("SELECT … %s", variable)` |
| Python                  | `"SELECT … " + variable`               |
| Python                  | `f"SELECT … {variable}"`               |

<Note>
  Only uppercase SQL keywords are matched (`SELECT`, `INSERT`, `UPDATE`, `DELETE`, `DROP`), so an error message containing the word "delete" is not flagged. Parameterised queries, or a query builder that handles escaping, remove the risk entirely.
</Note>

***

### Insecure RNG - `insecureRandom`

A non-cryptographic random number generator used where cryptographic randomness is expected: session tokens, CSRF nonces, password reset links. These generators are predictable enough to be guessed by an attacker who tries.

| Language                | Detected form                            |
| ----------------------- | ---------------------------------------- |
| TypeScript / JavaScript | `Math.random()`                          |
| Go                      | `rand.Intn(...)`, `rand.Float64()`       |
| Python                  | `random.random()`, `random.randint(...)` |

Use `crypto.randomBytes` in Node, `crypto/rand` in Go, or `secrets` in Python for anything security-sensitive.

***

### ReDoS-risk regex - `unsafeRegex`

Regular expressions with nested quantifiers (`(a+)+`, `(a|aa)+`), which can backtrack catastrophically on certain inputs. A short crafted string can occupy a CPU core for minutes, so any regex applied to untrusted input becomes a denial-of-service route.

Detected in all four languages. Iris Code flags the pattern itself; the fix is to rewrite it without the nesting, or to use a regex engine that does not backtrack.

***

### Hardcoded localhost URL - `hardcodedLocalhost`

`http://localhost` or `http://127.0.0.1` committed into production code. In production the call either fails, or reaches whatever else happens to be listening on that port on the server.

<Note>
  Test files are skipped. A `localhost` URL in `*.test.ts` or `*_test.go` is usually intentional.
</Note>

***

### TLS verification disabled - `disabledTlsVerification`

Configuration that tells an HTTPS client to accept any certificate, including expired and self-signed ones. It is usually added to work around a certificate problem in development and then ships by accident. Against a man-in-the-middle attacker, the connection offers no more protection than plain HTTP.

| Language                | Detected form               |
| ----------------------- | --------------------------- |
| TypeScript / JavaScript | `rejectUnauthorized: false` |
| Go                      | `InsecureSkipVerify: true`  |
| Python                  | `verify=False`              |

***

### Debug flag enabled - `debugFlagsEnabled`

`debug: true` in a configuration object committed to production code. Debug modes typically increase log verbosity, relax security controls, and expose internal state through error messages users can see.

<Note>
  Test files are skipped. `debug: true` in a test setup file is intentional.
</Note>

***

### Weak hashing - `weakHashing`

Use of MD5 or SHA-1 in hashing calls. Both are cryptographically broken, with collisions cheap enough to generate that neither belongs in password storage, signatures, or integrity verification.

| Language                | Detected form                                    |
| ----------------------- | ------------------------------------------------ |
| TypeScript / JavaScript | `crypto.createHash('md5')`, `createHash('sha1')` |
| Go                      | `md5.New()`, `sha1.New()`                        |
| Python                  | `hashlib.md5(...)`, `hashlib.sha1(...)`          |

Use SHA-256 or stronger for integrity checks, and `bcrypt` or `argon2` for password storage. A plain hash of any algorithm is unsuitable for passwords.

***

### Open redirect - `openRedirect`

A redirect that forwards to a URL taken from user input without validation. An attacker can then distribute a link on your own domain, which your users have reason to trust, that sends them to a phishing site.

Flagged when a redirect call (`res.redirect(...)` in Express, `http.Redirect(...)` in Go) receives a value directly from a request parameter. Validate the destination against a list of paths you control.

***

## CLI scanning

`iris security` is free and needs no account:

```bash theme={null}
iris security
iris security ./src --format json
```

<Tip>
  Worth adding to CI even without a Pro licence. It checks all nine patterns on every push and runs independently of a full `iris check`.
</Tip>

## Configuration

On by default. To switch the group off:

```json theme={null}
{
  "enableSecuritySmells": false
}
```

To suppress squiggles and Problems panel entries while keeping the sidebar findings and scoring:

```json theme={null}
{
  "enableInlineDiagnostics": true,
  "inlineDiagnostics": {
    "securitySmells": false
  }
}
```

## Scoring

Each security smell type has its own configurable weight under `healthScoreWeights` (Pro). Defaults:

| Pattern             | Weight key                | Default deduction |
| ------------------- | ------------------------- | ----------------- |
| eval() / exec()     | `evalUsage`               | −5 each           |
| SQL concatenation   | `sqlConcatenation`        | −5 each           |
| Insecure RNG        | `insecureRandom`          | −2 each           |
| ReDoS regex         | `unsafeRegex`             | −3 each           |
| Hardcoded localhost | `hardcodedLocalhost`      | −1 each           |
| TLS disabled        | `disabledTlsVerification` | −7 each           |
| Debug flag          | `debugFlagsEnabled`       | −2 each           |
| Weak hashing        | `weakHashing`             | −5 each           |
| Open redirect       | `openRedirect`            | −7 each           |

<Note>
  These are the global defaults. The `security` preset raises several of them - `evalUsage` and `sqlConcatenation` to `8`, `disabledTlsVerification` and `openRedirect` to `12`, `weakHashing` to `8`, `debugFlagsEnabled` to `4`, `insecureRandom` to `3`, and `hardcodedLocalhost` to `2` - see [Scoring Weights](/configuration/scoring-weights) for the full picture.
</Note>

Security smells count as **Blockers** rather than Warnings. When evaluating gate readiness, a file containing one is treated the same as a file containing a leaked credential.

## Failing a build on them (Pro)

`gateMaxSecuritySmells` caps the total across the workspace, enforced by the CLI, the pre-push hook and the build hook, the same way `gateMaxSecrets` caps secrets.

```json theme={null}
{
  "gateMaxSecuritySmells": 0
}
```

<Tip>
  Set it to `0` to require a clean workspace. Leave it unset while working through an existing backlog, since a gate that fails on every run tends to get disabled. See [Gate limits](/configuration/irisconfig#gate-thresholds).
</Tip>

## Inline diagnostics

With `enableInlineDiagnostics` enabled and `inlineDiagnostics.securitySmells` left at `true`, every finding gets a warning-severity squiggle and a Problems panel entry naming the pattern and the risk.

<Tip>
  If your team treats these as hard blockers, promote all nine to errors at once with `"severityOverrides": { "security-smell": "error" }`. The override applies to the whole category; there is currently no way to raise one pattern independently of the other eight.
</Tip>
