Secure Code Guardian Guide: Usage, Examples, and Best Practices for Beginners
Purpose
This post demonstrates how to use the Secure Code Guardian skill in Claude Code to build security into your development workflow.
Environment
- Claude Code CLI
- claude-skills plugin
- Security development workflow
What is Secure Code Guardian?
Secure Code Guardian is a security review skill that activates automatically when you work with authentication, user input, secrets, API endpoints, or payment features. It provides a comprehensive security checklist and patterns to help you identify vulnerabilities before they reach production.
The skill addresses these security areas:
- Authentication and authorization
- Input validation and sanitization
- SQL injection prevention
- XSS protection
- CSRF protection
- Secret management
- Rate limiting
- Error message security
Installation and Setup
First, install the claude-skills plugin if you haven’t already:
npm install -g @jeffallan/claude-skillsThen verify the installation:
claude-skills --versionSecure Code Guardian activates automatically based on context. You don’t need to manually invoke it - it triggers when Claude detects security-sensitive work.
Core Usage Patterns
The skill activates in these scenarios:
Automatic Triggers:
- Adding authentication features
- Handling user input (forms, APIs, CLI)
- Working with secrets (API keys, tokens)
- Creating API endpoints
- Implementing payment processing
- Managing sessions
Manual Invocation: You can also explicitly call the skill:
Use security-review to check this auth implementationExample 1: Authentication Implementation
When I add authentication to an Express.js application, Secure Code Guardian activates automatically:
import express from 'express'import bcrypt from 'bcrypt'
const app = express()
app.post('/login', async (req, res) => { const { username, password } = req.body
// Look up user const user = await db.users.findOne({ username })
// Verify password const isValid = await bcrypt.compare(password, user.passwordHash)
if (isValid) { res.json({ token: generateToken(user) }) } else { res.status(401).json({ error: 'Invalid credentials' }) }})The skill immediately flagged several issues:
- Missing rate limiting - Brute force attacks possible
- No input validation - Malformed input could crash the server
- Generic error messages - Reveals user existence
- No account lockout - Unlimited password attempts
- Missing HTTPS enforcement - Credentials sent in plaintext
Here’s the secured version:
import express from 'express'import bcrypt from 'bcrypt'import rateLimit from 'express-rate-limit'import { z } from 'zod'
// Rate limitingconst loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5 // 5 attempts per window})
const loginSchema = z.object({ username: z.string().min(3).max(50).regex(/^[a-zA-Z0-9_]+$/), password: z.string().min(8)})
app.post('/login', loginLimiter, async (req, res) => { // Validate input const validated = loginSchema.parse(req.body) const { username, password } = validated
// Look up user const user = await db.users.findOne({ username })
// Generic error message if (!user) { return res.status(401).json({ error: 'Invalid username or password' }) }
// Check account lockout if (user.lockedUntil && user.lockedUntil > Date.now()) { return res.status(429).json({ error: 'Account temporarily locked' }) }
// Verify password const isValid = await bcrypt.compare(password, user.passwordHash)
if (isValid) { // Reset failed attempts await db.users.updateOne( { _id: user._id }, { $set: { failedAttempts: 0 } } )
res.json({ token: generateToken(user) }) } else { // Increment failed attempts const attempts = (user.failedAttempts || 0) + 1
if (attempts >= 5) { await db.users.updateOne( { _id: user._id }, { $set: { failedAttempts: attempts, lockedUntil: Date.now() + 30 * 60 * 1000 // 30 minutes } } ) } else { await db.users.updateOne( { _id: user._id }, { $set: { failedAttempts: attempts } } ) }
res.status(401).json({ error: 'Invalid username or password' }) }})
// Force HTTPS in productionif (process.env.NODE_ENV === 'production') { app.use((req, res, next) => { if (!req.secure) { return res.redirect('https://' + req.headers.host + req.url) } next() })}Example 2: SQL Injection Prevention
When I query a database with user input, Secure Code Guardian checks for SQL injection vulnerabilities:
// DON'T: Direct string concatenationasync function getUser(userId: string) { const query = `SELECT * FROM users WHERE id = '${userId}'` return db.query(query)}The skill immediately identified this as critical. Here’s the fix:
// DO: Parameterized queriesasync function getUser(userId: string) { const query = 'SELECT * FROM users WHERE id = $1' return db.query(query, [userId])}Or using an ORM:
// DO: ORM with built-in protectionasync function getUser(userId: string) { return prisma.user.findUnique({ where: { id: userId } })}Example 3: Secret Management
Hardcoded secrets are a major security risk. When I wrote this:
// DON'T: Hardcoded secretsconst apiKey = "sk-proj-xxxxxxxxxxxxx"const dbPassword = "MySuperSecretPassword123!"Secure Code Guardian flagged every hardcoded secret and guided me to:
// DO: Environment variablesconst apiKey = process.env.OPENAI_API_KEYconst dbPassword = process.env.DB_PASSWORD
if (!apiKey || !dbPassword) { throw new Error('Required environment variables not configured')}Then create a .env file (added to .gitignore):
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxDB_PASSWORD=MySuperSecretPassword123!Best Practices
DO
Use parameterized queries:
// Safedb.query('SELECT * FROM users WHERE id = $1', [userId])
// Not safedb.query(`SELECT * FROM users WHERE id = '${userId}'`)Validate all input:
import { z } from 'zod'
const schema = z.object({ email: z.string().email(), age: z.number().int().min(0).max(150)})
const validated = schema.parse userInput)Use HTTPS in production:
if (process.env.NODE_ENV === 'production') { app.use((req, res, next) => { if (!req.secure) { return res.redirect('https://' + req.headers.host + req.url) } next() })}Implement rate limiting:
import rateLimit from 'express-rate-limit'
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100})
app.use('/api/', limiter)Sanitize HTML output:
import DOMPurify from 'dompurify'
const clean = DOMPurify.sanitize(userInput)DON’T
Don’t expose errors:
// DON'T: Leak implementation detailsres.status(500).json({ error: `Database connection failed: ${err.message}`})
// DO: Generic messagesres.status(500).json({ error: 'An error occurred. Please try again later.'})
// Log the actual error server-sideconsole.error('Database error:', err)Don’t store plain text passwords:
// DON'Tuser.password = password
// DOconst hash = await bcrypt.hash(password, 10)user.passwordHash = hashDon’t trust client-side validation:
// Client-side validation improves UX but isn't secure// Always validate on the serverconst validated = serverSchema.parse(userInput)Don’t use weak crypto:
// DON'T: MD5 is brokenconst hash = md5(password)
// DO: bcrypt, Argon2, or scryptconst hash = await bcrypt.hash(password, 10)Common Security Checklist
Before committing code, verify:
- No hardcoded secrets (API keys, passwords, tokens)
- All user inputs validated
- SQL injection prevention (parameterized queries)
- XSS prevention (sanitized HTML)
- CSRF protection enabled
- Authentication/authorization verified
- Rate limiting on all endpoints
- Error messages don’t leak sensitive data
- HTTPS enforced in production
- Dependencies are up to date
Integration with Other Skills
Secure Code Guardian works alongside other claude-skills:
- tdd-workflow: Write security tests first
- code-reviewer: General code quality after security review
- springboot-security: Java Spring Security patterns
- backend-patterns: API security design
Summary
In this post, I showed how to use Secure Code Guardian to build security into your development workflow. The key points are:
- The skill activates automatically for security-sensitive code
- It covers authentication, input validation, SQL injection, XSS, CSRF, secrets, rate limiting, and error handling
- Always validate input, use parameterized queries, and avoid hardcoded secrets
- Security testing should be part of your TDD workflow
- Use the security checklist before committing
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments