Developer Kit
OWASP Top 10 Scanner
Scans code for OWASP Top 10 vulnerability patterns including injection, XSS, IDOR, and insecure deserialization with severity ratings and remediation snippets. Useful for pre-commit security checks and enterprise compliance. Backend and full-stack engineers shipping user-facing or API-facing code, security-conscious teams running AI-assisted development without a dedicated AppSec program, and indie builders preparing for SOC 2 or enterprise customer security reviews. OWASP Top 10 categories — injection flaws, broken access control, security misconfiguration, XSS, insecure deserialization, and friends — remain the most common real-world vulnerability classes, and AI-generated code is not immune. Without an integrated scanner that runs inside the coding session, findings surface late (in manual review or production) rather than before code lands.
One-Time Purchase
$19.99
OWASP Top 10 Scan — src/api/users.ts
Scan Summary
| Metric | Value |
|---|---|
| Files Scanned | 1 (src/api/users.ts) |
| Total Findings | 12 |
| Critical | 2 |
| High | 4 |
| Medium | 4 |
| Low | 2 |
| OWASP Categories Represented | A01, A02, A03, A05, A07, A09 |
Coverage Note: This scan covers
src/api/users.tsonly (347 lines). Associated middleware, ORM configuration, authentication modules, and environment files were not scanned. Findings marked with ⚠️ may have mitigating controls elsewhere in the codebase that could not be verified.
Findings
FINDING-01
OWASP: A03:2021 — Injection
Severity: 🔴 Critical
Confidence: High
Location: src/api/users.ts, line 84
// Line 83–86
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`;
const result = await db.raw(query);
Description: User-supplied input from req.body.email is interpolated directly into a raw SQL string with no sanitization or parameterization. An attacker can terminate the string literal and inject arbitrary SQL, enabling data exfiltration, authentication bypass, or destructive operations.
Remediation: Replace string interpolation with a parameterized query:
const result = await db('users')
.where({ email: req.body.email })
.select('*');
// Or, using raw with binding:
const result = await db.raw(
'SELECT * FROM users WHERE email = ?',
[req.body.email]
);
FINDING-02
OWASP: A01:2021 — Broken Access Control
Severity: 🔴 Critical
Confidence: High
Location: src/api/users.ts, line 142
// Line 140–145
const targetUser = await User.findById(req.params.userId);
return res.json(targetUser);
Description: The endpoint returns a user record identified by req.params.userId without verifying that the requesting user is authorized to access that record. There is no comparison against req.user.id or a role check, making this a textbook Insecure Direct Object Reference (IDOR). Any authenticated user can enumerate and retrieve any other user's profile.
Remediation: Enforce ownership or role-based authorization before returning the record:
if (req.user.id !== req.params.userId && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const targetUser = await User.findById(req.params.userId);
return res.json(targetUser);
FINDING-03
OWASP: A07:2021 — Identification and Authentication Failures
Severity: 🟠 High
Confidence: High
Location: src/api/users.ts, line 211
// Line 209–213
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ algorithm: 'HS256' }
);
Description: No expiry (expiresIn) is set on the JWT. Tokens issued here are valid indefinitely, meaning a stolen or leaked token can never be invalidated through natural expiry. Combined with no token revocation mechanism visible in this file, credential theft has permanent effect.
Remediation:
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ algorithm: 'HS256', expiresIn: '15m' }
);
// Also issue a short-lived refresh token separately.
FINDING-04
OWASP: A03:2021 — Injection
Severity: 🟠 High
Confidence: High
Location: src/api/users.ts, line 178
// Line 176–179
const filter = req.query.sortBy;
const users = await db.raw(`SELECT * FROM users ORDER BY ${filter}`);
Description: req.query.sortBy is injected into an ORDER BY clause without validation. Parameterized queries cannot bind column names, so this requires allowlist validation. An attacker can inject expressions such as (SELECT CASE WHEN ...) to perform blind SQL injection or cause denial-of-service via expensive sorts.
Remediation:
const ALLOWED_SORT_COLUMNS = ['name', 'email', 'created_at'];
const sortBy = ALLOWED_SORT_COLUMNS.includes(req.query.sortBy as string)
? req.query.sortBy
: 'created_at';
const users = await db('users').orderBy(sortBy);
FINDING-05
OWASP: A05:2021 — Security Misconfiguration
Severity: 🟠 High
Confidence: High
Location: src/api/users.ts, line 23
// Line 21–25
app.use(cors({ origin: '*' }));
Description: CORS is configured to allow all origins (*). For an authenticated API that issues JWT tokens and handles user data, a wildcard CORS policy enables cross-origin requests from any domain, undermining same-origin protections.
Remediation:
const allowedOrigins = (process.env.ALLOWED_ORIGINS ?? '').split(',');
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) callback(null, true);
else callback(new Error('Not allowed by CORS'));
},
credentials: true,
}));
FINDING-06
OWASP: A02:2021 — Cryptographic Failures
Severity: 🟠 High
Confidence: High
Location: src/api/users.ts, line 259
// Line 257–261
const hashedPassword = crypto
.createHash('md5')
.update(req.body.password)
.digest('hex');
Description: MD5 is a cryptographically broken, fast-hashing algorithm unsuitable for password storage. It is trivially reversible via precomputed rainbow tables and can be brute-forced at billions of guesses per second on commodity hardware.
Remediation: Use a purpose-built password hashing algorithm with a work factor:
import bcrypt from 'bcrypt';
const hashedPassword = await bcrypt.hash(req.body.password, 12);
// On verification:
const match = await bcrypt.compare(req.body.password, storedHash);
FINDING-07
OWASP: A03:2021 — Injection (XSS via reflected output)
Severity: 🟡 Medium
Confidence: Medium
Location: src/api/users.ts, line 302
// Line 300–304
return res.send(`<p>Welcome back, ${req.query.name}!</p>`);
Description: req.query.name is reflected into an HTML response without escaping. If the Content-Type is text/html (as res.send with a string sets by default), an attacker can craft a URL with a malicious name parameter to execute arbitrary JavaScript in the victim's browser.
Confidence note: Medium — whether this endpoint is reachable unauthenticated and whether downstream CSP headers mitigate this could not be determined from this file alone.
Remediation:
import escapeHtml from 'escape-html';
return res.send(`<p>Welcome back, ${escapeHtml(String(req.query.name))}!</p>`);
// Preferred: return JSON from API routes and render HTML client-side.
FINDING-08
OWASP: A09:2021 — Security Logging and Monitoring Failures
Severity: 🟡 Medium
Confidence: Medium
Location: src/api/users.ts, line 127
// Line 125–130
} catch (err) {
console.error(err);
return res.status(500).json({ error: err.message });
}
Description: Raw error messages (including potential stack traces and internal details) are returned directly to the client in err.message. This leaks implementation details — database schema, file paths, or dependency versions — that aid an attacker in fingerprinting the system. Additionally, console.error is not a structured logging mechanism; authentication failures and errors are not auditable.
Remediation:
} catch (err) {
logger.error({ err, userId: req.user?.id, path: req.path }, 'Request error');
return res.status(500).json({ error: 'An internal error occurred.' });
}
FINDING-09
OWASP: A01:2021 — Broken Access Control
Severity: 🟡 Medium
Confidence: Medium
Location: src/api/users.ts, line 195
// Line 193–197
if (req.body.role) {
user.role = req.body.role;
}
await user.save();
Description: The user update handler allows the caller to set their own role field from the request body. If an authenticated non-admin user hits this endpoint, they could escalate their own privileges to admin. This is a Mass Assignment vulnerability.
Confidence note: Medium — whether an upstream middleware strips role from the body before this point could not be determined from this file.
Remediation:
// Explicitly allowlist updatable fields
const { displayName, avatarUrl } = req.body;
Object.assign(user, { displayName, avatarUrl });
await user.save();
// Role changes should be a separate admin-only endpoint.
FINDING-10
OWASP: A07:2021 — Identification and Authentication Failures
Severity: 🟡 Medium
Confidence: High
Location: src/api/users.ts, line 68
// Line 66–70
const user = await User.findOne({ email: req.body.email });
if (!user || user.password !== req.body.password) {
return res.status(401).json({ error: 'Invalid credentials' });
}
Description: Passwords are compared with strict equality (!==), implying they are stored and compared in plaintext (or a non-bcrypt format that lacks constant-time comparison). Plaintext storage is a critical data exposure risk. Even if hashed, non-constant-time comparison enables timing attacks.
Remediation:
const user = await User.findOne({ email: req.body.email });
if (!user || !(await bcrypt.compare(req.body.password, user.passwordHash))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
FINDING-11
OWASP: A05:2021 — Security Misconfiguration
Severity: 🔵 Low
Confidence: Low
Location: src/api/users.ts, line 14
// Line 12–16
app.use(express.json({ limit: '50mb' }));
Description: The JSON body size limit is set to 50 MB. While not a standalone vulnerability, an oversized limit increases exposure to denial-of-service via large payload attacks, particularly if this endpoint is publicly reachable.
Confidence note: Low — whether rate limiting or a reverse proxy caps request sizes upstream is unknown from this file.
Remediation: Set a conservative limit appropriate for the payload size this endpoint actually requires (typically 100kb–1mb for user profile APIs):
app.use(express.json({ limit: '1mb' }));
FINDING-12
OWASP: A09:2021 — Security Logging and Monitoring Failures
Severity: 🔵 Low
Confidence: Low
Location: src/api/users.ts, line 75
// Line 73–76
console.log(`Login attempt for: ${req.body.email}`);
Description: Login attempts are logged to stdout via console.log rather than a structured logging system. Audit trails for authentication events are typically required for compliance (SOC 2, PCI-DSS) and incident response. console.log output is ephemeral and unsearchable in most production environments.
Remediation:
logger.info({
event: 'auth.login_attempt',
email: req.body.email,
ip: req.ip,
timestamp: new Date().toISOString(),
}, 'Login attempt');
Top-3 Priority Recommendations
Findings exceed 10. The following three issues represent the highest combined severity and exploitability and should be addressed before any others.
-
Remediate SQL injection immediately (FINDING-01, FINDING-04). Both raw-query injections are directly exploitable with no prerequisites. Migrate to parameterized queries and column allowlists before this code reaches production.
-
Replace MD5 password hashing and plaintext comparison (FINDING-06, FINDING-10). Password storage and comparison are broken at a foundational level. Migrate to
bcryptwith a work factor of ≥12, and run a forced password reset for any users whose credentials were stored under the current scheme. -
Enforce authorization on the user-lookup endpoint (FINDING-02). The IDOR vulnerability on line 142 allows any authenticated user to read any other user's full record. Add an ownership/role check as the first operation in that handler.
Suggested PR Reviewer Checklist
The following items should be verified by a reviewer before approving this file:
- [ ] All database queries use parameterized inputs or ORM methods — no string interpolation in raw SQL
- [ ] Every endpoint that accepts a resource ID verifies the requesting user is authorized to access that resource
- [ ] JWT tokens have an
expiresInvalue set; no indefinitely-valid tokens are issued - [ ] Passwords are hashed with
bcrypt(orargon2) — nocrypto.createHash,md5,sha1, orsha256used for credential storage - [ ] CORS origin is restricted to an explicit allowlist; wildcard
*is not present on authenticated routes - [ ] Error responses return generic messages to clients; full error details go to the structured logger only
- [ ] Request body fields are explicitly allowlisted before assignment to model objects (no mass assignment)
- [ ] All authentication events (attempts, failures, password changes) are emitted to the structured logger with IP, timestamp, and user identifier
Scan performed via static pattern analysis only. No code was executed. Findings reflect patterns visible in src/api/users.ts (347 lines) exclusively — associated modules, middleware, environment configuration, and database layer were not analyzed and may contain additional vulnerabilities or mitigating controls not reflected above.
View full sample →
All sales final. No refunds on digital products.
Includes support for Claude Code, Codex, and OpenClaw in the same license.
What You Get With This Skill
Scans code for OWASP Top 10 vulnerability patterns including injection, XSS, IDOR, and insecure deserialization with severity ratings and remediation snippets. Useful for pre-commit security checks and enterprise compliance.
All ClearPoint Nexus Skills Include
- Production-ready workflow packaging for three supported platforms.
- Reusable structure designed for repeatable operator tasks.
- Clear deliverable format, not just raw prompt output.
Related Skills
$19.99
One-time license
$19.99
One-time license
$19.99
One-time license