DEV Community

suhteevah
suhteevah

Posted on

I Automated OWASP Top 10 Checks With a Pre-Commit Hook

Broken Access Control has been OWASP #1 since 2021. Not because developers don't understand authentication — but because auth is a consistency problem.

Your auth can be perfect on 99 endpoints. Endpoint 100 ships without middleware because someone forgot, copied a route template that didn't include it, or added an "admin-only" page during a hackathon and never locked it down.

Most auth tooling is runtime: pentest frameworks, DAST scanners, bug bounties. By the time they find something, the code is deployed and the vulnerability is live. I wanted a pre-commit hook that catches the common stuff before it leaves the developer's machine. So I built one.

How AuthAudit Maps to OWASP

AuthAudit scans for 90 authentication and authorization anti-patterns across 6 categories. Every finding maps to an OWASP Top 10 2021 entry:

OWASP Category AuthAudit Coverage
A01:2021 Broken Access Control Missing auth middleware, IDOR patterns, frontend-only role checks
A02:2021 Cryptographic Failures Weak hashing, missing encryption, token mismanagement
A04:2021 Insecure Design Missing rate limiting, authorization logic flaws
A07:2021 Identification & Auth Failures Session management, password handling, CSRF gaps

Pattern 1: Unprotected Routes

This is the most common finding. A route definition with no authentication middleware between the path and the handler.

// Express — no auth middleware
app.get('/admin/users', (req, res) => {
  const users = await User.findAll();
  res.json(users);
});

// What it should look like
app.get('/admin/users', requireAuth, requireRole('admin'), (req, res) => {
  const users = await User.findAll();
  res.json(users);
});
Enter fullscreen mode Exit fullscreen mode

Why it happens: route templates without auth, copy-paste from tutorials, "I'll add auth later" that never happens.

AuthAudit rule AC-001 catches route definitions with no auth middleware. AC-003 specifically flags admin paths without elevated auth checks.

Pattern 2: JWT Stored in localStorage

// After successful login
const response = await fetch('/api/login', { method: 'POST', body: credentials });
const { token } = await response.json();
localStorage.setItem('token', token); // XSS = full account takeover
Enter fullscreen mode Exit fullscreen mode

Any XSS vulnerability gives the attacker the full JWT. Game over. Full account takeover.

The alternative is HttpOnly cookies — they're not accessible to JavaScript. An XSS attack can still make requests (CSRF), but it can't steal the session token and use it from another machine.

AuthAudit rule TK-003 flags JWT/token storage in localStorage. TK-005 catches sensitive tokens accessible to client-side JavaScript.

Pattern 3: Missing CSRF Protection

State-changing endpoints (POST, PUT, DELETE) without CSRF token validation.

// No CSRF middleware on a state-changing route
app.post('/api/transfer', requireAuth, (req, res) => {
  const { to, amount } = req.body;
  transferFunds(req.user.id, to, amount);
  res.json({ success: true });
});
Enter fullscreen mode Exit fullscreen mode

SPA frameworks handle CSRF differently than server-rendered apps. Developers assume "the frontend handles it" — often nobody handles it.

AuthAudit rule CS-004 catches state-changing endpoints without CSRF protection. CS-006 flags missing SameSite cookie attribute.

Pattern 4: Session Mismanagement

// Express session config — missing security flags
app.use(session({
  secret: 'keyboard cat',
  cookie: {
    // Missing: httpOnly, secure, sameSite
    // Missing: maxAge (no session expiry)
  }
}));
Enter fullscreen mode Exit fullscreen mode

Three things that should always be set on session cookies: HttpOnly (not accessible to JS), Secure (HTTPS only), and SameSite (prevents cross-origin requests from sending the cookie). Plus: rotate the session ID after login to prevent session fixation.

AuthAudit rules CS-001 (missing HttpOnly), CS-002 (missing Secure), CS-007 (no session rotation after authentication).

Pattern 5: Insecure Password Handling

// Plaintext comparison — never do this
if (password === user.password) {
  return { authenticated: true };
}

// MD5/SHA1 — not designed for passwords
const hash = crypto.createHash('md5').update(password).digest('hex');

// What it should look like
const match = await bcrypt.compare(password, user.passwordHash);
Enter fullscreen mode Exit fullscreen mode

Plaintext comparison means passwords are stored in plaintext. MD5 and SHA1 are fast hashes — designed for checksums, not passwords. Use bcrypt, scrypt, or argon2.

AuthAudit rule PW-001 (plaintext password comparison), PW-003 (weak hashing algorithm), PW-006 (login endpoint without rate limiting).

Pattern 6: Frontend-Only Role Checks

// React component — frontend-only authorization
function Dashboard() {
  const { user } = useAuth();
  return (
    <div>
      {user.role === 'admin' && <AdminPanel />}
      {user.role === 'user' && <UserPanel />}
    </div>
  );
}

// But the API endpoint serves admin data to ANY authenticated user
app.get('/api/admin/dashboard', requireAuth, (req, res) => {
  // Missing: requireRole('admin')
  const data = await getAdminDashboard();
  res.json(data);
});
Enter fullscreen mode Exit fullscreen mode

Frontend checks are UX, not security. Anyone can call your API directly. The backend must enforce authorization independently.

AuthAudit rule AC-008 (role check in frontend only, no backend enforcement), AC-011 (API endpoint missing role-based authorization).

Setting Up the Pre-Commit Hook

Install AuthAudit and run an initial scan:

clawhub install authaudit
authaudit scan .
Enter fullscreen mode Exit fullscreen mode

You'll get a scored report (0-100) with findings mapped to OWASP categories:

$ authaudit scan src/

[CRITICAL] AC-001 Unprotected admin route — routes/admin.js:5
[CRITICAL] TK-003 JWT stored in localStorage — auth/client.js:22
[HIGH]     CS-002 Session cookie missing HttpOnly — config/session.js:8
[HIGH]     AC-008 Role check in frontend only — components/admin.tsx:45

Score: 41/100 (Grade: F)
Enter fullscreen mode Exit fullscreen mode

For the pre-commit hook (Pro tier), configure lefthook:

# .lefthook.yml
pre-commit:
  commands:
    authaudit:
      run: authaudit scan --staged --min-score 60
Enter fullscreen mode Exit fullscreen mode

This blocks any commit that would drop the auth security score below 60. Start with a low threshold and raise it as you fix existing issues.

What This Doesn't Replace

AuthAudit finds known anti-patterns. It won't find a novel auth bypass in your custom OAuth implementation. For that, you still need manual security review or a dedicated pentest.

This tool catches the stuff that shouldn't survive code review but consistently does — the missing middleware, the insecure cookie config, the localStorage JWT that someone copied from a tutorial.

clawhub install authaudit
authaudit scan .
Enter fullscreen mode Exit fullscreen mode

Free to scan. Pro ($19/mo) adds the pre-commit hook + OWASP compliance reports. 100% local — your auth code never leaves your machine.

AuthAudit | GitHub

Top comments (0)