← Back to list

security-review
by MLGBJDLW
Terminal-first AI coding assistant
⭐ 0🍴 0📅 Jan 25, 2026
SKILL.md
name: security-review description: Security audit guidelines covering OWASP Top 10 vulnerabilities, secure coding practices, authentication patterns, and code review checklists version: 1.0.0 priority: 25 tags:
- security
- audit
- owasp
- review
- builtin triggers:
- type: keyword pattern: security
- type: keyword pattern: audit
- type: keyword pattern: vulnerability
- type: keyword pattern: owasp
- type: glob pattern: "/auth/"
- type: glob pattern: "/security/" globs:
- "/auth//*.ts"
- "/security//*.ts"
- "/middleware//*.ts"
Security Review
Comprehensive security guidelines for identifying and preventing vulnerabilities.
Rules
- Input Validation: Validate and sanitize ALL user input at system boundaries
- Output Encoding: Encode output appropriate to context (HTML, URL, JS, SQL)
- Authentication: Use established libraries; never roll your own crypto
- Authorization: Check permissions on every protected operation
- Secrets Management: Never hardcode secrets; use environment variables or vaults
- Least Privilege: Grant minimum permissions required for functionality
- Defense in Depth: Multiple security layers; don't rely on single control
- Fail Secure: On error, deny access rather than grant it
- Audit Logging: Log security events with sufficient detail for investigation
Patterns
Input Validation
import { z } from "zod";
import DOMPurify from "isomorphic-dompurify";
// Schema validation at API boundary
const userInputSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).regex(/^[\w\s-]+$/),
bio: z.string().max(1000).optional(),
age: z.number().int().min(13).max(150).optional(),
});
// Sanitize HTML content
function sanitizeHtml(dirty: string): string {
return DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p"],
ALLOWED_ATTR: ["href"],
});
}
// Path traversal prevention
function safePath(basePath: string, userPath: string): string {
const resolved = path.resolve(basePath, userPath);
if (!resolved.startsWith(path.resolve(basePath))) {
throw new Error("Path traversal attempt detected");
}
return resolved;
}
// SQL parameterization (never string concat)
const user = await db.query(
"SELECT * FROM users WHERE id = $1 AND status = $2",
[userId, "active"]
);
```markdown
### Authentication Patterns
```typescript
import { hash, verify } from "@node-rs/argon2";
import { SignJWT, jwtVerify } from "jose";
// Password hashing with Argon2id
async function hashPassword(password: string): Promise<string> {
return hash(password, {
memoryCost: 65536, // 64 MB
timeCost: 3, // 3 iterations
parallelism: 4,
});
}
async function verifyPassword(hash: string, password: string): Promise<boolean> {
try {
return await verify(hash, password);
} catch {
return false;
}
}
// JWT with proper configuration
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET);
async function createToken(payload: JWTPayload): Promise<string> {
return new SignJWT(payload)
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.setIssuedAt()
.setExpirationTime("1h")
.setNotBefore("0s")
.setJti(crypto.randomUUID())
.sign(JWT_SECRET);
}
async function verifyToken(token: string): Promise<JWTPayload> {
const { payload } = await jwtVerify(token, JWT_SECRET, {
algorithms: ["HS256"],
clockTolerance: 30, // seconds
});
return payload as JWTPayload;
}
// Secure session configuration
const sessionConfig = {
name: "__Host-session", // Secure prefix
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true, // HTTPS only
sameSite: "strict" as const,
maxAge: 3600000, // 1 hour
path: "/",
},
};
```markdown
### Authorization Middleware
```typescript
import type { Request, Response, NextFunction } from "express";
interface Permission {
resource: string;
action: "read" | "write" | "delete" | "admin";
}
// Role-based access control
const ROLE_PERMISSIONS: Record<string, Permission[]> = {
user: [
{ resource: "profile", action: "read" },
{ resource: "profile", action: "write" },
],
admin: [
{ resource: "*", action: "admin" },
],
};
function hasPermission(
userRole: string,
resource: string,
action: Permission["action"]
): boolean {
const permissions = ROLE_PERMISSIONS[userRole] ?? [];
return permissions.some(
(p) =>
(p.resource === resource || p.resource === "*") &&
(p.action === action || p.action === "admin")
);
}
// Authorization middleware
function authorize(resource: string, action: Permission["action"]) {
return (req: Request, res: Response, next: NextFunction) => {
const user = req.user;
if (!user) {
return res.status(401).json({
error: { code: "UNAUTHENTICATED", message: "Authentication required" },
});
}
if (!hasPermission(user.role, resource, action)) {
// Log authorization failure
logger.warn("Authorization failed", {
userId: user.id,
resource,
action,
ip: req.ip,
});
return res.status(403).json({
error: { code: "FORBIDDEN", message: "Insufficient permissions" },
});
}
next();
};
}
// Resource ownership check
async function authorizeOwnership(
req: Request,
res: Response,
next: NextFunction
) {
const resourceId = req.params.id;
const resource = await db.findById(resourceId);
if (!resource) {
return res.status(404).json({
error: { code: "NOT_FOUND", message: "Resource not found" },
});
}
// Admin can access any resource
if (req.user?.role === "admin") {
return next();
}
// Owner check
if (resource.ownerId !== req.user?.id) {
return res.status(403).json({
error: { code: "FORBIDDEN", message: "Access denied" },
});
}
next();
}
```markdown
### Security Headers
```typescript
import helmet from "helmet";
// Comprehensive security headers
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
frameAncestors: ["'none'"],
upgradeInsecureRequests: [],
},
},
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: { policy: "same-origin" },
crossOriginResourcePolicy: { policy: "same-origin" },
dnsPrefetchControl: { allow: false },
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
noSniff: true,
originAgentCluster: true,
permittedCrossDomainPolicies: { permittedPolicies: "none" },
referrerPolicy: { policy: "strict-origin-when-cross-origin" },
xssFilter: true,
})
);
// CORS with specific origins
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(",") ?? [],
credentials: true,
methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
maxAge: 86400,
}));
```markdown
### Secrets Management
```typescript
// Environment-based configuration
const config = {
database: {
host: process.env.DB_HOST ?? "localhost",
password: process.env.DB_PASSWORD, // Never default secrets
},
jwt: {
secret: process.env.JWT_SECRET,
issuer: process.env.JWT_ISSUER ?? "app",
},
encryption: {
key: process.env.ENCRYPTION_KEY,
},
};
// Validate required secrets at startup
function validateSecrets(): void {
const required = ["DB_PASSWORD", "JWT_SECRET", "ENCRYPTION_KEY"];
const missing = required.filter((key) => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing required secrets: ${missing.join(", ")}`);
}
}
// Secret rotation support
interface SecretManager {
getSecret(name: string): Promise<string>;
rotateSecret(name: string): Promise<void>;
}
// Never log secrets
function redactSecrets(obj: unknown): unknown {
const sensitiveKeys = /password|secret|token|key|auth|credential/i;
if (typeof obj !== "object" || obj === null) {
return obj;
}
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key,
sensitiveKeys.test(key) ? "[REDACTED]" : redactSecrets(value),
])
);
}
```markdown
## Anti-Patterns
```typescript
// ❌ SQL Injection
const query = `SELECT * FROM users WHERE id = '${userId}'`;
// ✅ Parameterized queries
const query = "SELECT * FROM users WHERE id = $1";
await db.query(query, [userId]);
// ❌ Hardcoded secrets
const API_KEY = "sk_live_abc123";
// ✅ Environment variables
const API_KEY = process.env.API_KEY;
// ❌ Weak password hashing
const hash = crypto.createHash("md5").update(password).digest("hex");
// ✅ Strong password hashing (Argon2, bcrypt)
const hash = await argon2.hash(password);
// ❌ JWT without expiration
const token = jwt.sign({ userId }, secret);
// ✅ JWT with expiration and audience
const token = jwt.sign({ userId }, secret, { expiresIn: "1h", audience: "api" });
// ❌ Trusting user input for paths
const file = fs.readFileSync(`/uploads/${req.params.filename}`);
// ✅ Validate and sanitize paths
const safeName = path.basename(req.params.filename);
const filePath = path.join(UPLOADS_DIR, safeName);
// ❌ Exposing stack traces
res.status(500).json({ error: error.stack });
// ✅ Generic error to client, detailed logging
logger.error("Operation failed", { error, requestId: req.id });
res.status(500).json({ error: { code: "INTERNAL_ERROR", requestId: req.id } });
// ❌ Using eval or Function constructor
eval(userInput);
new Function(userCode)();
// ✅ Never execute user-provided code
throw new Error("Code execution not allowed");
```markdown
## Examples
### Security Review Checklist
```markdown
## Input Handling
- [ ] All user input validated against strict schemas
- [ ] File uploads restricted by type, size, and scanned for malware
- [ ] URLs validated against allowlist (prevent SSRF)
- [ ] HTML content sanitized before rendering
## Authentication
- [ ] Passwords hashed with Argon2id or bcrypt
- [ ] Multi-factor authentication available
- [ ] Account lockout after failed attempts
- [ ] Secure password reset flow
## Session Management
- [ ] Session tokens are cryptographically random
- [ ] Sessions expire after inactivity
- [ ] Sessions invalidated on logout
- [ ] Cookies use Secure, HttpOnly, SameSite flags
## Authorization
- [ ] Every endpoint checks permissions
- [ ] Resource ownership verified
- [ ] Principle of least privilege applied
- [ ] Admin functions protected
## Data Protection
- [ ] Sensitive data encrypted at rest
- [ ] TLS 1.3 for data in transit
- [ ] PII minimized and retention policies defined
- [ ] Secrets stored in vault, not code
## Error Handling
- [ ] Generic errors returned to clients
- [ ] Detailed errors logged server-side
- [ ] Stack traces never exposed
- [ ] Failed auth attempts logged
## Headers & CORS
- [ ] Security headers configured (CSP, HSTS, etc.)
- [ ] CORS restricted to known origins
- [ ] X-Frame-Options prevents clickjacking
## Dependencies
- [ ] Dependencies audited (`npm audit`)
- [ ] No known vulnerabilities
- [ ] Automated security updates enabled
```markdown
### Audit Logging
```typescript
interface AuditEvent {
timestamp: Date;
eventType: string;
userId?: string;
resourceType: string;
resourceId: string;
action: string;
outcome: "success" | "failure";
ipAddress: string;
userAgent: string;
details?: Record<string, unknown>;
}
class AuditLogger {
async log(event: AuditEvent): Promise<void> {
// Immutable audit log
await this.store.append({
...event,
id: crypto.randomUUID(),
timestamp: new Date(),
});
}
// Common events
async logAuth(type: "login" | "logout" | "failed", userId: string, req: Request) {
await this.log({
timestamp: new Date(),
eventType: `auth.${type}`,
userId,
resourceType: "session",
resourceId: req.sessionID ?? "",
action: type,
outcome: type === "failed" ? "failure" : "success",
ipAddress: req.ip ?? "",
userAgent: req.get("user-agent") ?? "",
});
}
async logDataAccess(
userId: string,
resourceType: string,
resourceId: string,
action: string,
req: Request
) {
await this.log({
timestamp: new Date(),
eventType: "data.access",
userId,
resourceType,
resourceId,
action,
outcome: "success",
ipAddress: req.ip ?? "",
userAgent: req.get("user-agent") ?? "",
});
}
}
References
Score
Total Score
65/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon


