Loading...
Loading...
Security auditing for code vulnerabilities (OWASP Top 10, XSS, SQL injection) and dependency scanning (pnpm audit, Snyk). Use when handling user input, adding authentication, before deployments, or resolving CVEs.
npx skill4agent add sgcarstrends/sgcarstrends security# Run audit
pnpm audit
# Only high/critical
pnpm audit --audit-level=high
# Auto-fix
pnpm audit --fix
# JSON report
pnpm audit --json > audit.jsonpnpm-workspace.yaml# Find dependency chain
pnpm why vulnerable-package
# Use overrides as last resort
# package.json
{
"pnpm": {
"overrides": {
"vulnerable-package": "^3.1.0"
}
}
}snyk auth # Authenticate
snyk test # Test for vulnerabilities
snyk monitor # Monitor for new vulnerabilities
snyk fix # Auto-fix// ❌ No authorization
export async function deletePost(postId: string) {
await db.delete(posts).where(eq(posts.id, postId));
}
// ✅ With authorization
export async function deletePost(postId: string, userId: string) {
const post = await db.query.posts.findFirst({ where: eq(posts.id, postId) });
if (post.authorId !== userId) throw new Error("Unauthorized");
await db.delete(posts).where(eq(posts.id, postId));
}// ❌ SQL Injection
const query = `SELECT * FROM users WHERE id = ${userId}`;
// ✅ Parameterized query (Drizzle ORM)
const user = await db.query.users.findFirst({ where: eq(users.id, userId) });sanitize-htmlimport { Ratelimit } from "@upstash/ratelimit";
import { redis } from "@sgcarstrends/utils";
const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(5, "15 m"),
});
export async function login(email: string, password: string, ip: string) {
const { success } = await ratelimit.limit(ip);
if (!success) throw new Error("Too many login attempts");
return verifyCredentials(email, password);
}import bcrypt from "bcrypt";
// ✅ Hash passwords
const hashedPassword = await bcrypt.hash(password, 10);
// ✅ Strong password validation
const passwordSchema = z.string()
.min(12)
.regex(/[A-Z]/, "Must contain uppercase")
.regex(/[a-z]/, "Must contain lowercase")
.regex(/[0-9]/, "Must contain number")
.regex(/[^A-Za-z0-9]/, "Must contain special character");// ❌ SSRF vulnerability
export async function fetchUrl(url: string) {
return await fetch(url);
}
// ✅ Whitelist approach
const ALLOWED_DOMAINS = ["api.example.com", "data.gov.sg"];
export async function fetchUrl(url: string) {
const parsedUrl = new URL(url);
if (!ALLOWED_DOMAINS.includes(parsedUrl.hostname)) {
throw new Error("Domain not allowed");
}
return await fetch(url);
}import { z } from "zod";
const userInputSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150),
});
export async function createUser(data: unknown) {
const validated = userInputSchema.parse(data);
// Now safe to use
}// ❌ Too permissive
app.use(cors({ origin: "*" }));
// ✅ Whitelist specific origins
app.use(cors({
origin: [
"https://sgcarstrends.com",
"https://staging.sgcarstrends.com",
process.env.NODE_ENV === "development" ? "http://localhost:3001" : "",
].filter(Boolean),
credentials: true,
}));// next.config.js
const securityHeaders = [
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-XSS-Protection", value: "1; mode=block" },
{ key: "Referrer-Policy", value: "origin-when-cross-origin" },
];
module.exports = {
async headers() {
return [{ source: "/:path*", headers: securityHeaders }];
},
};// ❌ Hardcoded secret
const apiKey = "sk_live_EXAMPLE_NOT_REAL";
// ✅ From environment with validation
import { z } from "zod";
const envSchema = z.object({
API_KEY: z.string().min(1),
DATABASE_URL: z.string().url(),
});
const env = envSchema.parse(process.env);# .github/workflows/security.yml
name: Security Audit
on:
push:
branches: [main]
schedule:
- cron: '0 0 * * 1' # Weekly
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm audit --audit-level=high