input-validation-guide — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited input-validation-guide (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Validate all external input at system boundaries using schema validation libraries. Reject invalid data early with clear error messages. Never trust client-side validation as the sole defense.
The standard for TypeScript schema validation. Parse, don't validate.
import { z } from 'zod';
// Define schemas as the source of truth
const CreateUserSchema = z.object({
email: z.string().email('Invalid email format'),
name: z.string().min(1, 'Name required').max(100),
age: z.number().int().min(13, 'Must be 13 or older').max(150),
role: z.enum(['user', 'admin', 'moderator']).default('user'),
website: z.string().url().optional(),
});
// Infer TypeScript types from schemas
type CreateUserInput = z.infer<typeof CreateUserSchema>;
// Parse at the boundary, use typed data internally
app.post('/api/users', async (req, res) => {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: 'Validation failed',
issues: result.error.issues.map(i => ({
field: i.path.join('.'),
message: i.message,
})),
});
}
// result.data is fully typed as CreateUserInput
const user = await createUser(result.data);
res.json(user);
});Advanced Zod patterns for complex validation:
// Transform and refine
const SearchParamsSchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.enum(['name', 'date', 'relevance']).default('relevance'),
q: z.string().trim().min(1).max(200).optional(),
});
// Discriminated unions for type-safe variants
const PaymentSchema = z.discriminatedUnion('method', [
z.object({
method: z.literal('card'),
cardNumber: z.string().regex(/^\d{16}$/),
expiry: z.string().regex(/^\d{2}\/\d{2}$/),
cvv: z.string().regex(/^\d{3,4}$/),
}),
z.object({
method: z.literal('bank_transfer'),
accountNumber: z.string().min(8).max(20),
routingNumber: z.string().length(9),
}),
]);
// Custom refinements with cross-field validation
const DateRangeSchema = z.object({
startDate: z.coerce.date(),
endDate: z.coerce.date(),
}).refine(
data => data.endDate > data.startDate,
{ message: 'End date must be after start date', path: ['endDate'] }
);The standard for Python data validation using type annotations.
from pydantic import BaseModel, Field, field_validator, EmailStr
from datetime import date
from enum import Enum
class UserRole(str, Enum):
USER = "user"
ADMIN = "admin"
MODERATOR = "moderator"
class CreateUserRequest(BaseModel):
email: EmailStr
name: str = Field(min_length=1, max_length=100)
age: int = Field(ge=13, le=150)
role: UserRole = UserRole.USER
website: str | None = None
@field_validator("name")
@classmethod
def name_must_not_be_empty(cls, v: str) -> str:
stripped = v.strip()
if not stripped:
raise ValueError("Name cannot be blank")
return stripped
# FastAPI integration - validation is automatic
@app.post("/api/users")
async def create_user(data: CreateUserRequest):
# data is already validated and typed
return await save_user(data)Always prefer allowlists. Denylists are inherently incomplete.
// WRONG: Denylist approach - always has gaps
function sanitizeFilename(name: string): string {
return name.replace(/[<>:"/\\|?*]/g, ''); // What about null bytes? Unicode tricks?
}
// CORRECT: Allowlist approach - only permit known-good characters
function sanitizeFilename(name: string): string {
const sanitized = name.replace(/[^a-zA-Z0-9._-]/g, '');
if (!sanitized || sanitized.startsWith('.')) {
throw new Error('Invalid filename');
}
return sanitized;
}
// CORRECT: Allowlist for content types
const ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'application/pdf']);
function validateContentType(type: string): boolean {
return ALLOWED_TYPES.has(type);
}Prevent attacks that exploit JavaScript's loose type coercion.
// WRONG: Vulnerable to type coercion
app.get('/api/users', (req, res) => {
// req.query.admin could be "true" (string), true (if parsed), or ["true"] (array)
if (req.query.admin == true) { // Loose equality - dangerous
return getAdminData();
}
});
// CORRECT: Strict parsing with Zod
const QuerySchema = z.object({
admin: z.enum(['true', 'false']).transform(v => v === 'true').optional(),
id: z.coerce.number().int().positive(), // Explicit coercion
});
app.get('/api/users', (req, res) => {
const query = QuerySchema.parse(req.query);
// query.admin is boolean | undefined, query.id is number
});
// WRONG: JSON.parse without validation
const config = JSON.parse(userInput); // Could be any type
// CORRECT: Parse then validate
const rawData = JSON.parse(userInput);
const config = ConfigSchema.parse(rawData);Apply context-appropriate sanitization after validation.
import DOMPurify from 'dompurify';
import sqlstring from 'sqlstring';
// HTML content: strip dangerous tags
function sanitizeHtml(input: string): string {
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br', 'ul', 'li'],
ALLOWED_ATTR: [],
});
}
// Path traversal prevention
function safePath(basedir: string, userPath: string): string {
const resolved = path.resolve(basedir, userPath);
if (!resolved.startsWith(basedir)) {
throw new Error('Path traversal detected');
}
return resolved;
}==) instead of strict equality (===) in JavaScriptContent-Type headers without verifying actual content| Language | Library | Key Pattern |
|---|---|---|
| TypeScript | Zod | schema.safeParse(input) returns { success, data, error } |
| TypeScript | Joi | schema.validate(input) returns { value, error } |
| Python | Pydantic | Class-based models with type annotations |
| Go | validator | Struct tags: validate:"required,email" |
| Principle | Rule |
|---|---|
| Validate early | At system boundaries, not deep in business logic |
| Allowlist | Permit known-good, reject everything else |
| Parse, don't validate | Transform raw input into typed domain objects |
| Fail loudly | Return specific field-level error messages |
| Never trust client | Server-side validation is mandatory |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.