sharp-edges — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited sharp-edges (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.
Sharp edges are APIs, configurations, and patterns that are easy to use incorrectly. They work in the happy path but break in subtle, dangerous ways.
When evaluating sharp edges, consider three types of users:
APIs whose defaults do something unexpected:
// SHARP: parseInt without radix
parseInt("08") // 0 in old engines (octal), 8 in modern
parseInt("08", 10) // Always 8
// SHARP: Array.sort() without comparator
[10, 2, 1].sort() // [1, 10, 2] -- sorts as strings!
[10, 2, 1].sort((a, b) => a - b) // [1, 2, 10]
// SHARP: JSON.parse reviver runs bottom-up
JSON.parse('{"a": {"b": 1}}', (key, val) => {
// 'b' fires before 'a' -- counterintuitive
})
// SHARP: fetch() doesn't reject on HTTP errors
const res = await fetch('/api') // 404 doesn't throw!
if (!res.ok) throw new Error(`HTTP ${res.status}`)Operations that fail without telling you:
// SHARP: Object.freeze is shallow
const obj = Object.freeze({ nested: { value: 1 } })
obj.nested.value = 2 // Succeeds! Only top level is frozen
// SHARP: Map vs Object key coercion
const map = new Map()
map.set(1, 'number')
map.set('1', 'string')
map.get(1) // 'number' -- Map preserves key types
// But:
const obj = {}
obj[1] = 'number'
obj['1'] = 'string'
obj[1] // 'string' -- Object coerces keys to strings
// SHARP: Promise.all fails fast
Promise.all([p1, p2, p3]) // If p1 fails, p2/p3 results are lost
Promise.allSettled([p1, p2, p3]) // Always returns all results// SHARP: == vs ===
null == undefined // true
0 == '' // true
false == '0' // true
// Always use ===
// SHARP: typeof null
typeof null // 'object' -- historical bug, never fixed
// SHARP: NaN
NaN === NaN // false
Number.isNaN(x) // Use this instead of x === NaN// SHARP: async forEach doesn't await
[1, 2, 3].forEach(async (item) => {
await processItem(item) // Fires all at once, doesn't wait
})
// Use for...of instead
for (const item of [1, 2, 3]) {
await processItem(item)
}
// SHARP: Race condition in check-then-act
const exists = await db.findOne({ email })
if (!exists) {
await db.create({ email }) // Another request might create it between check and act
}
// Use upsert or unique constraint instead// SHARP: URL parsing inconsistencies
new URL('http://evil.com\\@good.com') // Different browsers parse differently
// SHARP: RegExp without anchors
/admin/.test('not-admin-page') // true! No ^ or $
// SHARP: Timing attacks on string comparison
if (userToken === storedToken) { } // Vulnerable to timing attack
// Use crypto.timingSafeEqual instead
// SHARP: Path traversal via join
path.join('/uploads', userInput) // '../../../etc/passwd' works!
path.resolve('/uploads', userInput) // Still dangerous
// Validate that result starts with base directory// SHARP: MongoDB operator injection
db.users.find({ username: req.body.username })
// If req.body.username = { "$ne": "" }, returns all users!
// Sanitize: validate input is a string
// SHARP: SQL LIKE injection
db.query(`SELECT * FROM users WHERE name LIKE '%${input}%'`)
// Input: "%" returns all, "_" matches any char
// Use parameterized queries with ESCAPE clause
// SHARP: ORM lazy loading in loops (N+1)
const users = await User.findAll()
for (const user of users) {
const posts = await user.getPosts() // N+1 queries!
}
// Use eager loading: User.findAll({ include: Post })// SHARP: React useEffect cleanup race
useEffect(() => {
let cancelled = false
fetchData().then(data => {
if (!cancelled) setState(data) // Without this, stale updates
})
return () => { cancelled = true }
}, [])
// SHARP: Express middleware order matters
app.use(cors())
app.use(helmet())
app.use(authMiddleware)
app.use(rateLimiter)
// If rateLimiter is AFTER auth, unauthenticated requests aren't limited
// SHARP: Next.js revalidate: 0 is NOT "no cache"
// revalidate: 0 means "revalidate on every request" (still caches)
// Use { cache: 'no-store' } for truly no cacheFor each API/function/config in review:
[ ] What happens with empty/null/undefined input?
[ ] What happens with extremely large input?
[ ] What happens with concurrent access?
[ ] What happens when the network is slow/down?
[ ] What are the default values? Are they safe?
[ ] Does it fail silently or loudly?
[ ] Is the error message helpful or misleading?
[ ] Will a future developer understand the constraints?
[ ] Is there a safer alternative API?When you find a sharp edge, document it:
SHARP EDGE: [API/pattern name]
SURPRISE: [What happens that developers don't expect]
DANGER: [What can go wrong -- security, data loss, correctness]
FIX: [The safe alternative]
AFFECTED: [Which files/modules in this codebase use it]Inspired by Trail of Bits sharp-edges plugin.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.