pinata-security-cli 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/categories/definitions/concurrency/deadlock.yml +426 -0
- package/src/categories/definitions/concurrency/idempotency-missing.yml +465 -0
- package/src/categories/definitions/concurrency/race-condition.yml +423 -0
- package/src/categories/definitions/concurrency/retry-storm.yml +490 -0
- package/src/categories/definitions/concurrency/thread-safety.yml +395 -0
- package/src/categories/definitions/concurrency/timeout-missing.yml +479 -0
- package/src/categories/definitions/data/bulk-operation.yml +267 -0
- package/src/categories/definitions/data/data-race.yml +489 -0
- package/src/categories/definitions/data/data-truncation.yml +425 -0
- package/src/categories/definitions/data/data-validation.yml +279 -0
- package/src/categories/definitions/data/encoding-mismatch.yml +404 -0
- package/src/categories/definitions/data/null-handling.yml +411 -0
- package/src/categories/definitions/data/precision-loss.yml +418 -0
- package/src/categories/definitions/data/schema-migration.yml +441 -0
- package/src/categories/definitions/input/boundary-testing.yml +421 -0
- package/src/categories/definitions/input/injection-fuzzing.yml +403 -0
- package/src/categories/definitions/input/null-undefined.yml +386 -0
- package/src/categories/definitions/network/connection-failure.yml +432 -0
- package/src/categories/definitions/network/high-latency.yml +529 -0
- package/src/categories/definitions/network/network-partition.yml +475 -0
- package/src/categories/definitions/network/network-timeout.yml +433 -0
- package/src/categories/definitions/network/packet-loss.yml +513 -0
- package/src/categories/definitions/network/thundering-herd.yml +508 -0
- package/src/categories/definitions/performance/blocking-io.yml +495 -0
- package/src/categories/definitions/performance/cpu-spin.yml +506 -0
- package/src/categories/definitions/performance/memory-bloat.yml +501 -0
- package/src/categories/definitions/resource/connection-pool-exhaustion.yml +481 -0
- package/src/categories/definitions/resource/file-handle-leak.yml +437 -0
- package/src/categories/definitions/resource/memory-leak.yml +443 -0
- package/src/categories/definitions/security/auth-failures.yml +514 -0
- package/src/categories/definitions/security/command-injection.yml +462 -0
- package/src/categories/definitions/security/csrf.yml +408 -0
- package/src/categories/definitions/security/data-exposure.yml +450 -0
- package/src/categories/definitions/security/dependency-risks.yml +472 -0
- package/src/categories/definitions/security/deserialization.yml +419 -0
- package/src/categories/definitions/security/file-upload.yml +495 -0
- package/src/categories/definitions/security/hardcoded-secrets.yml +447 -0
- package/src/categories/definitions/security/ldap-injection.yml +412 -0
- package/src/categories/definitions/security/path-traversal.yml +500 -0
- package/src/categories/definitions/security/rate-limiting.yml +441 -0
- package/src/categories/definitions/security/sql-injection.yml +448 -0
- package/src/categories/definitions/security/ssrf.yml +477 -0
- package/src/categories/definitions/security/timing-attack.yml +489 -0
- package/src/categories/definitions/security/xss.yml +463 -0
- package/src/categories/definitions/security/xxe.yml +416 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinata-security-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "AI-powered test coverage analysis and generation tool. Find security blind spots before attackers do.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
21
|
"wasm",
|
|
22
|
+
"src/categories/definitions",
|
|
22
23
|
"README.md",
|
|
23
24
|
"LICENSE"
|
|
24
25
|
],
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
id: deadlock
|
|
2
|
+
version: 1
|
|
3
|
+
name: Deadlock
|
|
4
|
+
description: |
|
|
5
|
+
Detects deadlock vulnerabilities where threads or processes wait indefinitely
|
|
6
|
+
for resources held by each other. Includes lock ordering issues, nested lock
|
|
7
|
+
acquisition, and database transaction deadlocks. Deadlocks cause application
|
|
8
|
+
hangs and require careful lock ordering or timeout mechanisms.
|
|
9
|
+
domain: concurrency
|
|
10
|
+
level: integration
|
|
11
|
+
priority: P0
|
|
12
|
+
severity: critical
|
|
13
|
+
applicableLanguages:
|
|
14
|
+
- python
|
|
15
|
+
- typescript
|
|
16
|
+
- javascript
|
|
17
|
+
|
|
18
|
+
references:
|
|
19
|
+
- https://cwe.mitre.org/data/definitions/833.html
|
|
20
|
+
- https://en.wikipedia.org/wiki/Deadlock
|
|
21
|
+
- https://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html
|
|
22
|
+
|
|
23
|
+
detectionPatterns:
|
|
24
|
+
- id: python-nested-locks
|
|
25
|
+
type: regex
|
|
26
|
+
language: python
|
|
27
|
+
pattern: "with\\s+\\w+lock.*:.*with\\s+\\w+lock"
|
|
28
|
+
confidence: high
|
|
29
|
+
description: Detects nested lock acquisition
|
|
30
|
+
|
|
31
|
+
- id: python-multiple-acquire
|
|
32
|
+
type: regex
|
|
33
|
+
language: python
|
|
34
|
+
pattern: "\\.acquire\\(\\).*\\.acquire\\(\\)"
|
|
35
|
+
confidence: high
|
|
36
|
+
description: Detects multiple lock acquisitions
|
|
37
|
+
|
|
38
|
+
- id: python-lock-no-timeout
|
|
39
|
+
type: regex
|
|
40
|
+
language: python
|
|
41
|
+
pattern: "\\.acquire\\(\\)(?!.*timeout)"
|
|
42
|
+
confidence: medium
|
|
43
|
+
description: Detects lock acquire without timeout
|
|
44
|
+
|
|
45
|
+
- id: python-rlock-missing
|
|
46
|
+
type: regex
|
|
47
|
+
language: python
|
|
48
|
+
pattern: "Lock\\(\\)(?!.*RLock)"
|
|
49
|
+
confidence: low
|
|
50
|
+
description: Detects Lock usage (consider RLock for reentrant)
|
|
51
|
+
|
|
52
|
+
- id: ts-async-mutex-nested
|
|
53
|
+
type: regex
|
|
54
|
+
language: typescript
|
|
55
|
+
pattern: "await\\s+\\w+\\.acquire.*await\\s+\\w+\\.acquire"
|
|
56
|
+
confidence: high
|
|
57
|
+
description: Detects nested async mutex acquisition
|
|
58
|
+
|
|
59
|
+
- id: ts-promise-circular
|
|
60
|
+
type: regex
|
|
61
|
+
language: typescript
|
|
62
|
+
pattern: "await\\s+\\w+\\s*;.*await\\s+\\w+"
|
|
63
|
+
confidence: low
|
|
64
|
+
description: Detects multiple awaits (verify no circular deps)
|
|
65
|
+
|
|
66
|
+
- id: ts-database-transaction
|
|
67
|
+
type: regex
|
|
68
|
+
language: typescript
|
|
69
|
+
pattern: "transaction.*transaction|BEGIN.*BEGIN"
|
|
70
|
+
confidence: high
|
|
71
|
+
description: Detects nested database transactions
|
|
72
|
+
|
|
73
|
+
testTemplates:
|
|
74
|
+
- id: pytest-deadlock
|
|
75
|
+
language: python
|
|
76
|
+
framework: pytest
|
|
77
|
+
template: |
|
|
78
|
+
import pytest
|
|
79
|
+
import threading
|
|
80
|
+
import time
|
|
81
|
+
|
|
82
|
+
class Test{{className}}Deadlock:
|
|
83
|
+
"""Deadlock tests for {{functionName}}"""
|
|
84
|
+
|
|
85
|
+
def test_no_deadlock_under_contention(self, {{fixtures}}):
|
|
86
|
+
"""Test no deadlock with concurrent lock acquisition"""
|
|
87
|
+
completed = []
|
|
88
|
+
errors = []
|
|
89
|
+
timeout = 5
|
|
90
|
+
|
|
91
|
+
def worker(worker_id):
|
|
92
|
+
try:
|
|
93
|
+
result = {{functionCall}}(worker_id)
|
|
94
|
+
completed.append(worker_id)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
errors.append(str(e))
|
|
97
|
+
|
|
98
|
+
threads = [threading.Thread(target=worker, args=(i,)) for i in range(10)]
|
|
99
|
+
start = time.time()
|
|
100
|
+
|
|
101
|
+
for t in threads:
|
|
102
|
+
t.start()
|
|
103
|
+
for t in threads:
|
|
104
|
+
t.join(timeout=timeout)
|
|
105
|
+
|
|
106
|
+
elapsed = time.time() - start
|
|
107
|
+
|
|
108
|
+
# All should complete without deadlock
|
|
109
|
+
assert len(completed) == 10, f"Possible deadlock: only {len(completed)}/10 completed"
|
|
110
|
+
assert elapsed < timeout, f"Timeout suggests deadlock"
|
|
111
|
+
|
|
112
|
+
def test_lock_ordering(self, {{fixtures}}):
|
|
113
|
+
"""Test consistent lock ordering prevents deadlock"""
|
|
114
|
+
results = []
|
|
115
|
+
|
|
116
|
+
def task_a():
|
|
117
|
+
{{lockAFirstCall}}()
|
|
118
|
+
results.append('A')
|
|
119
|
+
|
|
120
|
+
def task_b():
|
|
121
|
+
{{lockBFirstCall}}()
|
|
122
|
+
results.append('B')
|
|
123
|
+
|
|
124
|
+
t1 = threading.Thread(target=task_a)
|
|
125
|
+
t2 = threading.Thread(target=task_b)
|
|
126
|
+
|
|
127
|
+
t1.start()
|
|
128
|
+
t2.start()
|
|
129
|
+
|
|
130
|
+
t1.join(timeout=2)
|
|
131
|
+
t2.join(timeout=2)
|
|
132
|
+
|
|
133
|
+
assert len(results) == 2, "Deadlock detected in lock ordering"
|
|
134
|
+
|
|
135
|
+
def test_lock_timeout(self, {{fixtures}}):
|
|
136
|
+
"""Test lock acquisition has timeout"""
|
|
137
|
+
lock_held = threading.Event()
|
|
138
|
+
|
|
139
|
+
def holder():
|
|
140
|
+
{{acquireLockCall}}()
|
|
141
|
+
lock_held.set()
|
|
142
|
+
time.sleep(10)
|
|
143
|
+
{{releaseLockCall}}()
|
|
144
|
+
|
|
145
|
+
holder_thread = threading.Thread(target=holder)
|
|
146
|
+
holder_thread.start()
|
|
147
|
+
lock_held.wait()
|
|
148
|
+
|
|
149
|
+
# Should timeout, not block forever
|
|
150
|
+
start = time.time()
|
|
151
|
+
result = {{tryAcquireCall}}(timeout=1)
|
|
152
|
+
elapsed = time.time() - start
|
|
153
|
+
|
|
154
|
+
assert elapsed < 2, "Lock should timeout, not block"
|
|
155
|
+
assert result is False, "Should return False on timeout"
|
|
156
|
+
|
|
157
|
+
def test_reentrant_safe(self, {{fixtures}}):
|
|
158
|
+
"""Test reentrant lock acquisition is safe"""
|
|
159
|
+
def recursive_function(depth=3):
|
|
160
|
+
{{acquireLockCall}}()
|
|
161
|
+
if depth > 0:
|
|
162
|
+
recursive_function(depth - 1)
|
|
163
|
+
{{releaseLockCall}}()
|
|
164
|
+
|
|
165
|
+
# Should not deadlock on reentrant acquisition
|
|
166
|
+
recursive_function()
|
|
167
|
+
variables:
|
|
168
|
+
- name: className
|
|
169
|
+
type: string
|
|
170
|
+
description: Class name
|
|
171
|
+
required: true
|
|
172
|
+
- name: functionName
|
|
173
|
+
type: string
|
|
174
|
+
description: Function name
|
|
175
|
+
required: true
|
|
176
|
+
- name: functionCall
|
|
177
|
+
type: string
|
|
178
|
+
description: Main function call
|
|
179
|
+
required: true
|
|
180
|
+
- name: lockAFirstCall
|
|
181
|
+
type: string
|
|
182
|
+
description: Lock A first function
|
|
183
|
+
required: true
|
|
184
|
+
- name: lockBFirstCall
|
|
185
|
+
type: string
|
|
186
|
+
description: Lock B first function
|
|
187
|
+
required: true
|
|
188
|
+
- name: acquireLockCall
|
|
189
|
+
type: string
|
|
190
|
+
description: Acquire lock function
|
|
191
|
+
required: true
|
|
192
|
+
- name: releaseLockCall
|
|
193
|
+
type: string
|
|
194
|
+
description: Release lock function
|
|
195
|
+
required: true
|
|
196
|
+
- name: tryAcquireCall
|
|
197
|
+
type: string
|
|
198
|
+
description: Try acquire with timeout
|
|
199
|
+
required: true
|
|
200
|
+
- name: fixtures
|
|
201
|
+
type: string
|
|
202
|
+
description: pytest fixtures
|
|
203
|
+
required: false
|
|
204
|
+
defaultValue: ""
|
|
205
|
+
|
|
206
|
+
- id: jest-deadlock
|
|
207
|
+
language: typescript
|
|
208
|
+
framework: jest
|
|
209
|
+
template: |
|
|
210
|
+
import { {{functionName}} } from '{{modulePath}}';
|
|
211
|
+
|
|
212
|
+
describe('{{className}} Deadlock', () => {
|
|
213
|
+
jest.setTimeout(10000);
|
|
214
|
+
|
|
215
|
+
describe('contention handling', () => {
|
|
216
|
+
it('completes all operations under contention', async () => {
|
|
217
|
+
const promises = Array(10).fill(null).map((_, i) =>
|
|
218
|
+
{{functionCall}}(i)
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const results = await Promise.race([
|
|
222
|
+
Promise.all(promises),
|
|
223
|
+
new Promise((_, reject) =>
|
|
224
|
+
setTimeout(() => reject(new Error('Deadlock timeout')), 5000)
|
|
225
|
+
),
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
expect(results).toHaveLength(10);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('lock timeout', () => {
|
|
233
|
+
it('returns error on lock timeout instead of hanging', async () => {
|
|
234
|
+
// First acquire should succeed
|
|
235
|
+
const lock = await {{acquireLockCall}}();
|
|
236
|
+
|
|
237
|
+
// Second acquire should timeout, not hang
|
|
238
|
+
await expect(
|
|
239
|
+
{{acquireLockWithTimeoutCall}}(100)
|
|
240
|
+
).rejects.toThrow('timeout');
|
|
241
|
+
|
|
242
|
+
await {{releaseLockCall}}(lock);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('database transactions', () => {
|
|
247
|
+
it('handles transaction deadlock gracefully', async () => {
|
|
248
|
+
const results = await Promise.allSettled([
|
|
249
|
+
{{transactionACall}}(),
|
|
250
|
+
{{transactionBCall}}(),
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
// At least one should succeed, failed one should retry
|
|
254
|
+
const successes = results.filter(r => r.status === 'fulfilled');
|
|
255
|
+
expect(successes.length).toBeGreaterThanOrEqual(1);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('uses consistent lock ordering', async () => {
|
|
259
|
+
// Both functions should acquire locks in same order
|
|
260
|
+
const [r1, r2] = await Promise.all([
|
|
261
|
+
{{orderedLockCall}}(['A', 'B']),
|
|
262
|
+
{{orderedLockCall}}(['A', 'B']),
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
expect(r1).toBeDefined();
|
|
266
|
+
expect(r2).toBeDefined();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
variables:
|
|
271
|
+
- name: className
|
|
272
|
+
type: string
|
|
273
|
+
description: Class name
|
|
274
|
+
required: true
|
|
275
|
+
- name: functionName
|
|
276
|
+
type: string
|
|
277
|
+
description: Function name
|
|
278
|
+
required: true
|
|
279
|
+
- name: functionCall
|
|
280
|
+
type: string
|
|
281
|
+
description: Main function call
|
|
282
|
+
required: true
|
|
283
|
+
- name: acquireLockCall
|
|
284
|
+
type: string
|
|
285
|
+
description: Acquire lock function
|
|
286
|
+
required: true
|
|
287
|
+
- name: acquireLockWithTimeoutCall
|
|
288
|
+
type: string
|
|
289
|
+
description: Acquire with timeout
|
|
290
|
+
required: true
|
|
291
|
+
- name: releaseLockCall
|
|
292
|
+
type: string
|
|
293
|
+
description: Release lock function
|
|
294
|
+
required: true
|
|
295
|
+
- name: transactionACall
|
|
296
|
+
type: string
|
|
297
|
+
description: Transaction A function
|
|
298
|
+
required: true
|
|
299
|
+
- name: transactionBCall
|
|
300
|
+
type: string
|
|
301
|
+
description: Transaction B function
|
|
302
|
+
required: true
|
|
303
|
+
- name: orderedLockCall
|
|
304
|
+
type: string
|
|
305
|
+
description: Ordered lock function
|
|
306
|
+
required: true
|
|
307
|
+
- name: modulePath
|
|
308
|
+
type: string
|
|
309
|
+
description: Module path
|
|
310
|
+
required: true
|
|
311
|
+
|
|
312
|
+
examples:
|
|
313
|
+
- name: python-lock-ordering
|
|
314
|
+
concept: |
|
|
315
|
+
Deadlock from inconsistent lock ordering. Thread A acquires lock1 then lock2,
|
|
316
|
+
while Thread B acquires lock2 then lock1. If both acquire their first lock
|
|
317
|
+
simultaneously, deadlock occurs. Always acquire locks in consistent order.
|
|
318
|
+
vulnerableCode: |
|
|
319
|
+
lock1 = threading.Lock()
|
|
320
|
+
lock2 = threading.Lock()
|
|
321
|
+
|
|
322
|
+
def task_a():
|
|
323
|
+
with lock1: # Gets lock1
|
|
324
|
+
time.sleep(0.1)
|
|
325
|
+
with lock2: # Waits for lock2
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
def task_b():
|
|
329
|
+
with lock2: # Gets lock2
|
|
330
|
+
time.sleep(0.1)
|
|
331
|
+
with lock1: # Waits for lock1 - DEADLOCK
|
|
332
|
+
pass
|
|
333
|
+
testCode: |
|
|
334
|
+
import threading
|
|
335
|
+
|
|
336
|
+
def test_no_deadlock():
|
|
337
|
+
completed = []
|
|
338
|
+
|
|
339
|
+
t1 = threading.Thread(target=task_a)
|
|
340
|
+
t2 = threading.Thread(target=task_b)
|
|
341
|
+
|
|
342
|
+
t1.start()
|
|
343
|
+
t2.start()
|
|
344
|
+
|
|
345
|
+
t1.join(timeout=2)
|
|
346
|
+
t2.join(timeout=2)
|
|
347
|
+
|
|
348
|
+
assert not t1.is_alive() and not t2.is_alive()
|
|
349
|
+
language: python
|
|
350
|
+
severity: critical
|
|
351
|
+
|
|
352
|
+
- name: database-transaction-deadlock
|
|
353
|
+
concept: |
|
|
354
|
+
Database deadlock from conflicting row locks. Two transactions update rows
|
|
355
|
+
in opposite order. Transaction A locks row 1, Transaction B locks row 2,
|
|
356
|
+
then each tries to lock the other's row. Use consistent ordering or retry.
|
|
357
|
+
vulnerableCode: |
|
|
358
|
+
async function transferA() {
|
|
359
|
+
await db.transaction(async (tx) => {
|
|
360
|
+
await tx.update('accounts', { id: 1 }, { balance: 100 });
|
|
361
|
+
await delay(100);
|
|
362
|
+
await tx.update('accounts', { id: 2 }, { balance: 200 });
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function transferB() {
|
|
367
|
+
await db.transaction(async (tx) => {
|
|
368
|
+
await tx.update('accounts', { id: 2 }, { balance: 300 });
|
|
369
|
+
await delay(100);
|
|
370
|
+
await tx.update('accounts', { id: 1 }, { balance: 400 }); // DEADLOCK
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
testCode: |
|
|
374
|
+
describe('transfer deadlock', () => {
|
|
375
|
+
it('handles deadlock with retry', async () => {
|
|
376
|
+
const results = await Promise.allSettled([
|
|
377
|
+
transferA(),
|
|
378
|
+
transferB(),
|
|
379
|
+
]);
|
|
380
|
+
|
|
381
|
+
// Should retry and succeed
|
|
382
|
+
const successes = results.filter(r => r.status === 'fulfilled');
|
|
383
|
+
expect(successes.length).toBe(2);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
language: typescript
|
|
387
|
+
severity: high
|
|
388
|
+
|
|
389
|
+
- name: async-mutex-deadlock
|
|
390
|
+
concept: |
|
|
391
|
+
Deadlock with async mutexes. Even in single-threaded JavaScript, async
|
|
392
|
+
mutexes can deadlock if acquired in inconsistent order across different
|
|
393
|
+
async functions.
|
|
394
|
+
vulnerableCode: |
|
|
395
|
+
const mutexA = new Mutex();
|
|
396
|
+
const mutexB = new Mutex();
|
|
397
|
+
|
|
398
|
+
async function operationX() {
|
|
399
|
+
const releaseA = await mutexA.acquire();
|
|
400
|
+
await delay(10);
|
|
401
|
+
const releaseB = await mutexB.acquire(); // Waits
|
|
402
|
+
releaseB();
|
|
403
|
+
releaseA();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function operationY() {
|
|
407
|
+
const releaseB = await mutexB.acquire();
|
|
408
|
+
await delay(10);
|
|
409
|
+
const releaseA = await mutexA.acquire(); // DEADLOCK
|
|
410
|
+
releaseA();
|
|
411
|
+
releaseB();
|
|
412
|
+
}
|
|
413
|
+
testCode: |
|
|
414
|
+
describe('async mutex deadlock', () => {
|
|
415
|
+
it('uses consistent lock ordering', async () => {
|
|
416
|
+
await Promise.race([
|
|
417
|
+
Promise.all([operationX(), operationY()]),
|
|
418
|
+
delay(1000).then(() => { throw new Error('Deadlock'); }),
|
|
419
|
+
]);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
language: typescript
|
|
423
|
+
severity: high
|
|
424
|
+
|
|
425
|
+
createdAt: 2024-01-01
|
|
426
|
+
updatedAt: 2024-01-01
|