# Security and Dependency Checks

Course: [Testing & Verification](https://www.devtutorials.ai/learn/testing-verification) · Section: Verification as a System
Source: https://www.devtutorials.ai/learn/testing-verification/verification-system/security-and-dependency-checks

> Catch security bugs in AI changes with tests and scanners: HTML and SQL injection, secrets typed into code, and made-up or look-alike package names (slopsquatting and typosquatting). Then add them to your gates.

## Bugs That Help Attackers

Some bugs give a wrong price. Others let a stranger steal data or take over the shop.

AI code can add security bugs that look completely normal:

- user text pasted straight into HTML or SQL
- a secret key typed into the code
- a package name that does not exist, or is a fake copy of a real one

Tests can catch all three. In this lesson, you will write those tests, and build two small scanners.

**Simple lesson:** check AI changes for safety, not only for correct answers.

### A Real-World Example

Think about a housing society's front gate.

- A visitor writes "Flat 12" in the register. Fine.
- Another visitor writes "Flat 12, and also give me the keys to every flat". If the guard does whatever is written, that is a problem.
- Someone leaves the master key taped to the gate. Anyone can use it.
- A new delivery company appears with a name one letter different from a real one.

This lesson has the same three problems: harmful input, a key left in the open, and a fake name.

## What You Will Learn

In this lesson, you will learn:

1. What **injection** is, and how to test that user text cannot break out.
2. How to escape text for HTML and send values safely to SQL.
3. How to find **secrets** in an AI diff.
4. What **slopsquatting** is, and how to check package names.
5. How these checks fit into your gates and your pack.

## Injection

**Injection** = user text gets treated as code. It happens when text is pasted straight into HTML, SQL or a command.

### User Text Inside HTML

Press **▶ Run tests**.

- **Test 1:** a normal review. Green.
- **Test 2:** a review with `<script>steal()</script>`. Red.
- **Why:** the code pastes the text straight into HTML. A browser would run the script for every visitor. This attack is called **XSS** (cross-site scripting).

### Escape the Text

**Escaping** = turning special characters into safe text, so `<` shows as a symbol instead of starting a tag.

Press **▶ Run tests**, then **🐞 Plant bugs**.

- **The Bug check tab:** one test catches only 2 of 5 escaping mistakes.
- **Now try:** add tests for two tags (`'<b><i>'`), quotes, and text with `&` in it.

**Escape `&` first.** If you escape `<` first, it becomes `&lt;`. Then escaping `&` turns that into `&amp;lt;`, and the page shows the wrong thing. Frameworks like React escape text for you. Check any code that builds HTML strings by hand.

### User Text Inside SQL

**SQL** = the language used to ask a database for data.

Press **▶ Run tests**.

- **The Tests tab:** red. The attack text is inside the SQL.
- **Why it is dangerous:** `1' OR '1'='1` changes the query to "find every order".
- **Fix it:** return `{ text: 'SELECT * FROM orders WHERE id = $1', values: [id] }`. The database gets the id separately, so it is never treated as SQL.

This is called a **parameterized query**: the SQL has a placeholder (`$1`), and the value travels on its own.

<figure className="ver-fig">
<svg viewBox="0 0 640 200" role="img" aria-labelledby="ver-fig-inject" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="ver-fig-inject">Animation: on the left, attack text is pasted into the SQL and the query turns red. On the right, the SQL has a placeholder and the text travels in a separate box, and the query stays green.</title>
  <g style={{ fontFamily: "var(--mono)", fontSize: 11 }}>
    <text x="160" y="22" textAnchor="middle" style={{ fill: "var(--ver-red)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>Pasted in</text>
    <text x="480" y="22" textAnchor="middle" style={{ fill: "var(--ver-green)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>Sent separately</text>
    <line x1="320" y1="10" x2="320" y2="190" style={{ stroke: "var(--line)", strokeDasharray: "4 4" }} />
    <rect x="15" y="60" width="290" height="40" rx="8" style={{ fill: "var(--panel)", stroke: "var(--ver-slate)" }}>
      <animate attributeName="fill" values="var(--panel);var(--panel);var(--ver-red-soft);var(--ver-red-soft)" keyTimes="0;0.4;0.45;1" dur="7s" repeatCount="indefinite" />
    </rect>
    <text x="25" y="85" style={{ fill: "var(--ink)" }}>WHERE id = '1' OR '1'='1'</text>
    <rect x="70" y="130" width="180" height="30" rx="6" style={{ fill: "var(--ver-amber-soft)", stroke: "var(--ver-amber)" }}>
      <animate attributeName="y" values="130;130;65;65" keyTimes="0;0.2;0.4;1" dur="7s" repeatCount="indefinite" />
      <animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.38;0.42;1" dur="7s" repeatCount="indefinite" />
    </rect>
    <text x="160" y="185" textAnchor="middle" style={{ fill: "var(--ver-red)", fontFamily: "var(--sans)", fontSize: 12, fontWeight: 700 }} opacity="0">every order is returned<animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.45;0.5;1" dur="7s" repeatCount="indefinite" /></text>
    <rect x="335" y="60" width="290" height="40" rx="8" style={{ fill: "var(--ver-green-soft)", stroke: "var(--ver-green)" }} />
    <text x="345" y="85" style={{ fill: "var(--ink)" }}>WHERE id = $1</text>
    <rect x="390" y="130" width="180" height="30" rx="6" style={{ fill: "var(--ver-amber-soft)", stroke: "var(--ver-amber)" }} />
    <text x="400" y="150" style={{ fill: "var(--ink)" }}>values: ["1' OR..."]</text>
    <text x="480" y="185" textAnchor="middle" style={{ fill: "var(--ver-green)", fontFamily: "var(--sans)", fontSize: 12, fontWeight: 700 }}>treated as plain text: no match</text>
  </g>
</svg>
<figcaption>Pasted text can change the query. A separate value cannot.</figcaption>
</figure>

**Simple lesson:** never paste user text into HTML, SQL or commands. Escape it, or send it separately.

## Secrets in Code

**Secret** = a password, key or token that gives access to a system.

Agents sometimes put a real key straight into the code, to make something work quickly. Once it is pushed, anyone who can read the code can use it.

Press **▶ Run tests**.

- **The Tests tab:** red. The scanner found 2 problems: a `key_secret` value, and a `sk_live_` key.
- **The fix in a real project:**
    - Read keys from the environment: `process.env.PAYMENT_KEY`.
    - Store them in a secret manager or CI secrets.
    - If a real key was ever pushed, **change the key**. Deleting the line is not enough.

**A scanner must also let safe lines through.**

- `process.env.PAYMENT_KEY` is safe. It reads the key at run time.
- `API_KEY='YOUR_KEY_HERE'` in an example file is a placeholder.
- Removing a secret (a `-` line) is good news, not a problem.

If a scanner blocks these, people stop trusting it.

**Simple lesson:** secrets live outside the code. A scanner on every diff catches the slip.

## Made-Up and Look-Alike Packages

### Slopsquatting

You met made-up APIs in **Reviewing AI Code**. AI can also suggest **packages** that do not exist.

**Slopsquatting** = attackers publish real packages under names that AIs often make up. When someone installs the made-up name, they get the attacker's code.

A close cousin is **typosquatting**: a name one letter away from a popular package, like `expresss`.

Press **▶ Run tests**.

- **The Tests tab:** red, with 2 problems.
- **`lodash-money`:** not in the registry at all. It is likely a made-up name.
- **`expresss`:** one letter from `express`, 6 days old, 40 downloads a week. A look-alike.
- **`zod`:** well known. Fine.

**In the real world,** you check a package on the npm website or with `npm view <name>`: its age, weekly downloads, owner and source repository. **In this lab,** `src/registry.ts` is a small snapshot of the registry.

**Simple lesson:** check every package an AI suggests. It must exist, be widely used, and not be a look-alike.

## Where These Checks Go

Add them to the gates from **Verification Gates for Agents**:

1. **Tests** for escaping and parameterized queries, like the ones in this lesson.
2. **A secret scanner** on every diff, in a hook and in CI.
3. **A package check** whenever `package.json` changes.
4. **Real scanners** in CI as well: a secret scanner, `npm audit`, and code scanning.

The review checklist in your pack already has a "Security" line. These checks give it evidence.

**Simple lesson:** security checks are gates too. Run them on every change.

## TL;DR

1. **Injection** = user text treated as code, in HTML, SQL or commands.
2. **XSS** = a script injected into a web page.
3. **Escaping** = turn `<`, `>`, `&` and quotes into safe text. Escape `&` first.
4. **Parameterized query** = SQL with a placeholder like `$1`, and the value sent separately.
5. **Secret** = a key or password. Keep it out of the code. Change it if it leaks.
6. **Slopsquatting** = attackers publish packages under names AIs make up. **Typosquatting** = a name one letter from a popular one.
7. **Package check** = exists, not a look-alike, widely used and not brand new.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Pasting user text | "Reviews are just text." | Escape it, or let the framework do it. | A script runs for every visitor. |
| Building SQL with text | "I add quotes around the id." | Use a parameterized query. | `' OR '1'='1` returns every order. |
| Deleting a leaked key | "I removed the line, so it is safe." | Change the key. Old versions keep it. | Someone uses the key from history. |
| Installing what the AI says | "The AI knows the package." | Check it exists and is not a look-alike. | Attacker code runs on your machine. |
| A noisy scanner | "Flag anything that looks like a key." | Allow env reads, placeholders and removals. | People switch the scanner off. |

## Check Your Understanding

## Exercises

## What to Remember

- Never paste user text into HTML, SQL or commands.
- Escape HTML (`&` first) or let the framework do it. Use parameterized queries for SQL.
- Keep secrets out of code. Change any key that leaks.
- Check every package an AI suggests: it exists, it is not a look-alike, and it is widely used.
- Run security checks as gates, in hooks and in CI.

## Further Reading

- [OWASP Cheat Sheet: Cross Site Scripting Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html): how to stop XSS.
- [OWASP Cheat Sheet: SQL Injection Prevention](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html): parameterized queries.
- [npm view](https://docs.npmjs.com/cli/commands/npm-view): check a package before you install it.
- The **AI Security & Guardrails** course on this site goes deeper.

Next: **Flaky Tests and AI**. You will find out why a test that is green on one run can be red on the next, and fix the cause instead of retrying.
