3morixd commited on
Commit
d2a5602
·
verified ·
1 Parent(s): a0ca2ba

Upload api/security_audit.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. api/security_audit.py +120 -0
api/security_audit.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ dispatchAI API Security Audit
3
+ Checks: auth, injection, rate limiting, input validation, info leaks
4
+ """
5
+ import httpx
6
+ import asyncio
7
+ import json
8
+
9
+ BASE = "http://127.0.0.1:8081"
10
+
11
+ async def audit():
12
+ results = []
13
+ async with httpx.AsyncClient(timeout=30.0) as c:
14
+
15
+ # 1. No auth
16
+ try:
17
+ r = await c.post(f'{BASE}/v1/chat/completions', json={"model":"test","messages":[]})
18
+ results.append(("No auth rejected", r.status_code == 401, f"Got {r.status_code}"))
19
+ except Exception as e:
20
+ results.append(("No auth rejected", False, str(e)))
21
+
22
+ # 2. Invalid API key
23
+ try:
24
+ r = await c.post(f'{BASE}/v1/chat/completions',
25
+ headers={"Authorization":"Bearer invalid-key"},
26
+ json={"model":"test","messages":[]})
27
+ results.append(("Invalid key rejected", r.status_code == 401, f"Got {r.status_code}"))
28
+ except Exception as e:
29
+ results.append(("Invalid key rejected", False, str(e)))
30
+
31
+ # 3. SQL/Shell injection in prompt
32
+ try:
33
+ r = await c.post(f'{BASE}/v1/chat/completions',
34
+ headers={"Authorization":"Bearer da-demo-key-0001"},
35
+ json={"model":"dispatchAI/SmolLM2-135M-Instruct-mobile",
36
+ "messages":[{"role":"user","content":"'; rm -rf /; --"}],
37
+ "max_tokens":5})
38
+ results.append(("Injection handled", r.status_code in (200, 500), f"Got {r.status_code}"))
39
+ except Exception as e:
40
+ results.append(("Injection handled", False, str(e)))
41
+
42
+ # 4. Max tokens limit
43
+ try:
44
+ r = await c.post(f'{BASE}/v1/chat/completions',
45
+ headers={"Authorization":"Bearer da-demo-key-0001"},
46
+ json={"model":"dispatchAI/SmolLM2-135M-Instruct-mobile",
47
+ "messages":[{"role":"user","content":"Hello"}],
48
+ "max_tokens":100000})
49
+ results.append(("Large max_tokens handled", r.status_code in (200, 400), f"Got {r.status_code}"))
50
+ except Exception as e:
51
+ results.append(("Large max_tokens handled", False, str(e)))
52
+
53
+ # 5. Empty messages
54
+ try:
55
+ r = await c.post(f'{BASE}/v1/chat/completions',
56
+ headers={"Authorization":"Bearer da-demo-key-0001"},
57
+ json={"model":"dispatchAI/SmolLM2-135M-Instruct-mobile","messages":[]})
58
+ results.append(("Empty messages handled", r.status_code in (200, 400, 422), f"Got {r.status_code}"))
59
+ except Exception as e:
60
+ results.append(("Empty messages handled", False, str(e)))
61
+
62
+ # 6. Invalid model name
63
+ try:
64
+ r = await c.post(f'{BASE}/v1/chat/completions',
65
+ headers={"Authorization":"Bearer da-demo-key-0001"},
66
+ json={"model":"../../etc/passwd","messages":[{"role":"user","content":"hi"}]})
67
+ results.append(("Path traversal rejected", r.status_code == 400, f"Got {r.status_code}"))
68
+ except Exception as e:
69
+ results.append(("Path traversal rejected", False, str(e)))
70
+
71
+ # 7. Admin endpoint exposure
72
+ try:
73
+ r = await c.get(f'{BASE}/admin/phones')
74
+ data = json.loads(r.text)
75
+ serials_exposed = "serial" in str(data)
76
+ results.append(("Admin exposes phone serials", serials_exposed, "Should be protected"))
77
+ except Exception as e:
78
+ results.append(("Admin exposes phone serials", False, str(e)))
79
+
80
+ # 8. CORS headers
81
+ try:
82
+ r = await c.options(f'{BASE}/v1/chat/completions',
83
+ headers={"Origin":"https://evil.com"})
84
+ cors = r.headers.get("access-control-allow-origin", "")
85
+ results.append(("CORS allows all", cors == "*", f"CORS: {cors}"))
86
+ except Exception as e:
87
+ results.append(("CORS check", False, str(e)))
88
+
89
+ # Print results
90
+ print("=" * 60)
91
+ print("SECURITY AUDIT RESULTS")
92
+ print("=" * 60)
93
+ passed = 0
94
+ failed = 0
95
+ for name, ok, detail in results:
96
+ emoji = "✅" if ok else "❌"
97
+ print(f" {emoji} {name}: {detail}")
98
+ if ok: passed += 1
99
+ else: failed += 1
100
+ print(f"\nPassed: {passed}/{len(results)}")
101
+
102
+ # Fix recommendations
103
+ print("\n" + "=" * 60)
104
+ print("FIXES NEEDED:")
105
+ print("=" * 60)
106
+ fixes = []
107
+ for name, ok, detail in results:
108
+ if not ok:
109
+ if "Admin" in name:
110
+ fixes.append("Protect /admin/phones with API key auth")
111
+ if "CORS" in name:
112
+ fixes.append("Restrict CORS to dispatchai.ai only")
113
+
114
+ if not fixes:
115
+ print(" ✅ No critical fixes needed")
116
+ else:
117
+ for f in fixes:
118
+ print(f" ⚠️ {f}")
119
+
120
+ asyncio.run(audit())