security-env — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited security-env (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.
Security flags are non-negotiable stops. Do not proceed past a flagged issue without explicit user instruction.
Rule: No secret, API key, token, connection string, or credential ever appears in source code.
Any time code contains or is about to contain:
sk-, pk_, Bearer , base64 encoded, etc.)Stop immediately. Do not write the code. Instead, output:
🔴 SECURITY STOP — Secret detected
Pattern: [what was about to be written]
Action required:
1. Add to .env.local: [VARIABLE_NAME]="your-value-here"
2. Add to .env.example: [VARIABLE_NAME]="" (no value — just the key)
3. Verify .env.local is in .gitignore
4. In code: process.env.VARIABLE_NAME or import from a config module
5. In Vercel/deployment: add to environment variables in dashboardNever suggest that secrets are acceptable "temporarily" or "for development."
dangerouslySetInnerHTML — Mandatory Justification GateAny use of dangerouslySetInnerHTML requires:
// ❌ NEVER
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// ✓ Only after explicit instruction + sanitization
import DOMPurify from 'dompurify'
const sanitized = DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
ALLOWED_ATTR: []
})
<div dangerouslySetInnerHTML={{ __html: sanitized }} />
// Reason: [must be documented here]Flag DOMPurify as a new dependency before adding it — user must approve.
Every external API call must include:
// ✓ Required pattern
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10_000) // 10s timeout
try {
const response = await fetch(url, {
signal: controller.signal,
headers: { /* explicit headers */ }
})
clearTimeout(timeoutId)
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`)
}
return await response.json()
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
throw new Error('Request timed out — please try again')
}
throw error
} finally {
clearTimeout(timeoutId)
}Rules:
catch (e) {} is a bug)response.ok check requiredEvery protected route must:
// Next.js App Router — server component
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'
export default async function ProtectedPage() {
const session = await getSession()
if (!session) {
redirect('/login?next=/protected-path')
// Never: return null, return <div />, or silently render nothing
}
return <PageContent session={session} />
}Rules:
401 (not 404) for unauthenticated requests — 404 can leak route existence to attackersWhen any input connects to a database:
🟡 SECURITY FLAG — Server-side validation required
Client-side validation detected on [field name].
Client-side validation can be bypassed.
Before this code is complete, add server-side validation:
- Next.js API route / Server Action: validate with Zod or equivalent before any DB call
- Supabase: verify RLS policies cover this operation
- Return 400 with descriptive error if validation fails — never insert unvalidated dataRequired pattern for any data mutation:
import { z } from 'zod'
const schema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100).trim(),
// ... all fields explicitly typed and constrained
})
export async function POST(request: Request) {
const body = await request.json()
const result = schema.safeParse(body)
if (!result.success) {
return Response.json(
{ error: 'Invalid input', details: result.error.flatten() },
{ status: 400 }
)
}
// Only reach the DB with validated data
await db.insert(result.data)
}🔴 SECURITY STOP — Wildcard CORS in production
origin: '*' is unacceptable in production API routes.Required:
const allowedOrigins = [
process.env.NEXT_PUBLIC_APP_URL,
// additional origins explicitly listed
].filter(Boolean)
const origin = request.headers.get('origin')
const isAllowed = allowedOrigins.includes(origin ?? '')
// Only set CORS headers for allowed origins
if (isAllowed) {
headers.set('Access-Control-Allow-Origin', origin!)
}Never suggest Access-Control-Allow-Origin: * for routes that handle user data or authentication.
Before any npm install recommendation:
📦 DEPENDENCY CHECK — [package-name]
Before installing, verify:
1. CVEs: https://snyk.io/vuln/?q=[package-name] or `npm audit` after install
2. Last published: check npmjs.com — flag if > 12 months with no updates
3. Weekly downloads: flag if < 10,000/week (limited community, slower CVE response)
4. Bundle size: bundlephobia.com/package/[package-name] — flag if > 50kb gzipped
5. Native alternative: confirm no Web API, React built-in, or existing dependency covers this need
If any of the above are red flags, do not proceed — present findings to user first.Flag any package with:
Any public-facing API route (no authentication required) must be flagged:
🟡 SECURITY FLAG — Public API route without rate limiting
Route: [path]
This route is accessible without authentication.
Before production, add rate limiting to prevent abuse:
- Recommended: Upstash Rate Limit (Redis-based, Vercel-compatible)
- Alternative: Vercel Edge Middleware with IP-based limiting
- At minimum: limit to 100 requests/minute per IPRoutes with authentication can have higher limits, but still should not be unlimited.
🔴 SECURITY STOP — HTTP URL in production-bound code
Found: http://[url]
Replace with: https://[url]
If this is localhost/development only, wrap it:
const apiUrl = process.env.NODE_ENV === 'development'
? 'http://localhost:3000'
: 'https://api.example.com'Flag every hardcoded http:// URL that isn't explicitly scoped to development.
| Level | Trigger | Action |
|---|---|---|
| 🔴 STOP | Secret in code, wildcard CORS, HTTP in production, dangerouslySetInnerHTML without sanitization | Do not write code. Present the issue and required fix. Wait for instruction. |
| 🟡 FLAG | No server-side validation, missing rate limiting, missing timeout, unverified dependency | Write a comment block flagging the issue in the code. Note it clearly in the response. Continue only if the flag is addressed. |
| 🔵 NOTE | Suboptimal but not dangerous pattern | Note it, suggest the better approach, proceed with what was asked. |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.