# QA Advisor — Security Testing Reference

*Part of the QA Advisor skill: https://wavect.io/.well-known/agent-skills/qa-advisor/SKILL.md*

The specific OWASP code-level vulnerabilities most test suites miss, with the tests that should exist.

## Security Testing — The Specific Vulnerabilities Most Tests Miss

Generic security advice ("use parameterized queries") is insufficient. These
are the specific attack patterns that most test suites fail to cover.

### OWASP Top 10 — Codebase-Level Checks

**A01 — Broken Access Control**

Insecure Direct Object Reference (IDOR): any endpoint that accepts a user-supplied
ID and returns a resource must verify ownership before returning.

```typescript
// Vulnerable — any authenticated user can fetch any order by changing the ID
GET /api/orders/12345

// Test that must exist and must fail on the buggy implementation:
it('should not allow user A to access user B\'s orders', async () => {
  const userA = await createUser();
  const userB = await createUser();
  const orderB = await createOrder({ userId: userB.id });
  const res = await request(app)
    .get(`/api/orders/${orderB.id}`)
    .set('Authorization', `Bearer ${userA.token}`);
  expect(res.status).toBe(403); // not 200, not 404
});
```

**A02 — Cryptographic Failures**

Check for: storing passwords in plain text or MD5/SHA1, using ECB mode in AES,
seeding PRNG with the current time for token generation.

```bash
# Quick scan for dangerous cryptographic patterns
grep -rn "md5\|sha1\|ECB\|Math.random()" --include="*.ts" src/
grep -rn "hashlib.md5\|hashlib.sha1" --include="*.py" .
```

**A03 — Injection**

SQL injection: parameterized queries must be used everywhere. String concatenation
into SQL is a critical finding regardless of whether the input appears to be sanitized.

```bash
grep -rn "\.query\s*(\`\|\.query\s*('.*\$\|\.query\s*(\".*\$" --include="*.ts" src/
```

NoSQL injection: MongoDB `$where` operator with user input; unvalidated JSON
documents passed to query operators.

**A07 — Identification and Authentication Failures**

JWT `alg:none` attack: if the JWT library accepts `alg: "none"`, an attacker
can strip the signature and forge any token.

```typescript
// Test that must exist
it('should reject JWT with alg:none', async () => {
  const fakeToken = [
    Buffer.from('{"alg":"none","typ":"JWT"}').toString('base64url'),
    Buffer.from('{"sub":"admin","role":"superuser"}').toString('base64url'),
    '', // no signature
  ].join('.');
  const res = await request(app)
    .get('/api/admin')
    .set('Authorization', `Bearer ${fakeToken}`);
  expect(res.status).toBe(401);
});
```

**A10 — Server-Side Request Forgery (SSRF)**

Any endpoint that fetches a URL provided by the user is an SSRF vector.

```typescript
// Vulnerable
async function fetchOgImage(url: string) {
  return axios.get(url); // user controls url
}

// Test that must exist
it('should reject requests to internal network ranges', async () => {
  const internalUrls = [
    'http://169.254.169.254/latest/meta-data/', // AWS metadata
    'http://10.0.0.1/admin',
    'http://localhost:8080/internal',
    'file:///etc/passwd',
  ];
  for (const url of internalUrls) {
    const res = await request(app).post('/api/preview').send({ url });
    expect(res.status).toBeGreaterThanOrEqual(400);
  }
});
```

**XXE (XML External Entity Injection)**

Any XML parsing without `FEATURE_EXTERNAL_GENERAL_ENTITIES` disabled is an
XXE vector. This is common in import features, SAML authentication, and
document processing.

```java
// Vulnerable Java (common in SAML implementations)
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// Missing: dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(inputStream); // XXE possible
```

**Mass Assignment**

REST APIs that pass request body directly to ORM `create()` or `update()` allow
attackers to set fields that should not be user-settable (e.g., `role`, `isAdmin`,
`balance`).

```typescript
// Vulnerable
async create(req: Request) {
  return this.userRepo.create(req.body); // attacker can set { role: 'admin' }
}

// Test that must exist
it('should not allow mass assignment of role field', async () => {
  const res = await request(app)
    .post('/api/users')
    .send({ email: 'attacker@evil.com', password: 'pass', role: 'admin' });
  expect(res.status).toBe(201);
  const created = await userRepo.findOne({ email: 'attacker@evil.com' });
  expect(created.role).not.toBe('admin');
});
```

**Timing Attacks on Secrets**

String comparison with `===` is timing-variant. An attacker can measure
response time to determine prefix-by-prefix which bytes match.

```typescript
// Vulnerable — timing-variant comparison
if (webhookSecret === req.headers['x-webhook-secret']) { ... }

// Correct — constant-time comparison
import { timingSafeEqual } from 'crypto';
const a = Buffer.from(webhookSecret);
const b = Buffer.from(req.headers['x-webhook-secret'] as string);
if (a.length === b.length && timingSafeEqual(a, b)) { ... }
```

### Dependency Vulnerability Scanning

```bash
# JavaScript / Node.js
npm audit --audit-level=high
npx snyk test

# Python
pip-audit
safety check

# Java
./gradlew dependencyCheckAnalyze

# Go
govulncheck ./...
```

Flag any codebase that does not run dependency vulnerability scanning in CI.
Known vulnerability in a dependency is a zero-effort attack vector.
