thuban 0.4.0 → 0.4.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.
Files changed (209) hide show
  1. package/dist/package.json +63 -0
  2. package/dist/packages/crucible/rules/code-scanner-core.json +129 -0
  3. package/dist/packages/crucible/rules/command-injection.json +411 -0
  4. package/dist/packages/crucible/rules/crypto-misuse.json +35 -0
  5. package/dist/packages/crucible/rules/deserialization.json +152 -0
  6. package/dist/packages/crucible/rules/env-injection.json +46 -0
  7. package/dist/packages/crucible/rules/eval-abuse.json +104 -0
  8. package/dist/packages/crucible/rules/file-upload.json +46 -0
  9. package/dist/packages/crucible/rules/hallucination.json +22 -0
  10. package/dist/packages/crucible/rules/hardcoded-secret.json +397 -0
  11. package/dist/packages/crucible/rules/hardcoded-url.json +13 -0
  12. package/dist/packages/crucible/rules/insecure-crypto.json +302 -0
  13. package/dist/packages/crucible/rules/integer-overflow.json +35 -0
  14. package/dist/packages/crucible/rules/log-injection.json +33 -0
  15. package/dist/packages/crucible/rules/mass-assignment.json +112 -0
  16. package/dist/packages/crucible/rules/open-redirect.json +196 -0
  17. package/dist/packages/crucible/rules/path-traversal.json +422 -0
  18. package/dist/packages/crucible/rules/prototype-pollution.json +22 -0
  19. package/dist/packages/crucible/rules/sql-injection.json +597 -0
  20. package/dist/packages/crucible/rules/ssrf.json +557 -0
  21. package/dist/packages/crucible/rules/timing-attack.json +55 -0
  22. package/dist/packages/crucible/rules/unsafe-block.json +24 -0
  23. package/dist/packages/crucible/rules/xss.json +641 -0
  24. package/dist/packages/crucible/rules/xxe.json +66 -0
  25. package/dist/packages/crucible/rules/yaml-deserialization.json +22 -0
  26. package/dist/packages/crucible/rules/yaml-injection.json +22 -0
  27. package/dist/packages/scanner/executive-report.js +1 -1
  28. package/dist/packages/scanner/hallucination-detector.js +1 -1
  29. package/dist/packages/scanner/secret-scanner.js +1 -1
  30. package/package.json +7 -4
  31. package/dist/packages/crucible/seeds/python/seed-021 3.py +0 -37
  32. package/dist/packages/crucible/seeds/python/seed-022 3.py +0 -34
  33. package/dist/packages/crucible/seeds/python/seed-023 3.py +0 -37
  34. package/dist/packages/crucible/seeds/python/seed-024 3.py +0 -38
  35. package/dist/packages/crucible/seeds/python/seed-025 3.py +0 -40
  36. package/dist/packages/crucible/seeds/python/seed-026 3.py +0 -35
  37. package/dist/packages/crucible/seeds/python/seed-027 3.py +0 -35
  38. package/dist/packages/crucible/seeds/python/seed-028 3.py +0 -42
  39. package/dist/packages/crucible/seeds/python/seed-030 3.py +0 -37
  40. package/dist/packages/crucible/seeds/python/seed-031 3.py +0 -34
  41. package/dist/packages/crucible/seeds/python/seed-036 3.py +0 -33
  42. package/dist/packages/crucible/seeds/python/seed-037 3.py +0 -41
  43. package/dist/packages/crucible/seeds/python/seed-038 3.py +0 -33
  44. package/dist/packages/crucible/seeds/python/seed-039 3.py +0 -39
  45. package/dist/packages/crucible/seeds/python/seed-040 3.py +0 -39
  46. package/dist/packages/crucible/seeds/python/seed-041 3.py +0 -37
  47. package/dist/packages/crucible/seeds/python/seed-042 3.py +0 -38
  48. package/dist/packages/crucible/seeds/python/seed-043 3.py +0 -32
  49. package/dist/packages/crucible/seeds/python/seed-044 3.py +0 -38
  50. package/dist/packages/crucible/seeds/python/seed-045 3.py +0 -36
  51. package/dist/packages/crucible/seeds/python/seed-046 3.py +0 -33
  52. package/dist/packages/crucible/seeds/python/seed-047 3.py +0 -44
  53. package/dist/packages/crucible/seeds/python/seed-048 3.py +0 -35
  54. package/dist/packages/crucible/seeds/python/seed-049 3.py +0 -39
  55. package/dist/packages/crucible/seeds/python/seed-050 3.py +0 -39
  56. package/dist/packages/crucible/seeds/python/seed-051 3.py +0 -38
  57. package/dist/packages/crucible/seeds/python/seed-052 3.py +0 -41
  58. package/dist/packages/crucible/seeds/ruby/seed-001 3.rb +0 -15
  59. package/dist/packages/crucible/seeds/ruby/seed-002 3.rb +0 -22
  60. package/dist/packages/crucible/seeds/ruby/seed-003 3.rb +0 -25
  61. package/dist/packages/crucible/seeds/ruby/seed-004 3.rb +0 -17
  62. package/dist/packages/crucible/seeds/ruby/seed-005 3.rb +0 -21
  63. package/dist/packages/crucible/seeds/ruby/seed-006 3.rb +0 -17
  64. package/dist/packages/crucible/seeds/ruby/seed-007 3.rb +0 -16
  65. package/dist/packages/crucible/seeds/ruby/seed-008 3.rb +0 -18
  66. package/dist/packages/crucible/seeds/ruby/seed-009 3.rb +0 -20
  67. package/dist/packages/crucible/seeds/ruby/seed-010 3.rb +0 -24
  68. package/dist/packages/crucible/seeds/ruby/seed-011 3.rb +0 -21
  69. package/dist/packages/crucible/seeds/ruby/seed-012 3.rb +0 -22
  70. package/dist/packages/crucible/seeds/ruby/seed-013 3.rb +0 -21
  71. package/dist/packages/crucible/seeds/ruby/seed-014 3.rb +0 -16
  72. package/dist/packages/crucible/seeds/ruby/seed-015 3.rb +0 -18
  73. package/dist/packages/crucible/seeds/ruby/seed-016 3.rb +0 -17
  74. package/dist/packages/crucible/seeds/ruby/seed-017 3.rb +0 -25
  75. package/dist/packages/crucible/seeds/ruby/seed-018 3.rb +0 -23
  76. package/dist/packages/crucible/seeds/ruby/seed-019 3.rb +0 -20
  77. package/dist/packages/crucible/seeds/ruby/seed-020 3.rb +0 -17
  78. package/dist/packages/crucible/seeds/ruby/seed-021 3.rb +0 -20
  79. package/dist/packages/crucible/seeds/ruby/seed-022 3.rb +0 -21
  80. package/dist/packages/crucible/seeds/ruby/seed-023 3.rb +0 -19
  81. package/dist/packages/crucible/seeds/ruby/seed-024 3.rb +0 -17
  82. package/dist/packages/crucible/seeds/ruby/seed-025 3.rb +0 -17
  83. package/dist/packages/crucible/seeds/ruby/seed-026 3.rb +0 -18
  84. package/dist/packages/crucible/seeds/ruby/seed-027 3.rb +0 -21
  85. package/dist/packages/crucible/seeds/ruby/seed-028 3.rb +0 -22
  86. package/dist/packages/crucible/seeds/ruby/seed-029 3.rb +0 -19
  87. package/dist/packages/crucible/seeds/ruby/seed-030 3.rb +0 -20
  88. package/dist/packages/crucible/seeds/ruby/seed-031 3.rb +0 -17
  89. package/dist/packages/crucible/seeds/ruby/seed-032 3.rb +0 -22
  90. package/dist/packages/crucible/seeds/ruby/seed-033 3.rb +0 -18
  91. package/dist/packages/crucible/seeds/ruby/seed-034 3.rb +0 -19
  92. package/dist/packages/crucible/seeds/ruby/seed-035 3.rb +0 -19
  93. package/dist/packages/crucible/seeds/ruby/seed-036 3.rb +0 -18
  94. package/dist/packages/crucible/seeds/ruby/seed-037 3.rb +0 -21
  95. package/dist/packages/crucible/seeds/ruby/seed-038 3.rb +0 -24
  96. package/dist/packages/crucible/seeds/ruby/seed-039 3.rb +0 -24
  97. package/dist/packages/crucible/seeds/ruby/seed-040 3.rb +0 -22
  98. package/dist/packages/crucible/seeds/ruby/seed-041 3.rb +0 -23
  99. package/dist/packages/crucible/seeds/ruby/seed-042 3.rb +0 -25
  100. package/dist/packages/crucible/seeds/ruby/seed-043 3.rb +0 -23
  101. package/dist/packages/crucible/seeds/ruby/seed-044 3.rb +0 -16
  102. package/dist/packages/crucible/seeds/ruby/seed-045 3.rb +0 -22
  103. package/dist/packages/crucible/seeds/ruby/seed-046 3.rb +0 -27
  104. package/dist/packages/crucible/seeds/ruby/seed-047 3.rb +0 -26
  105. package/dist/packages/crucible/seeds/ruby/seed-048 3.rb +0 -24
  106. package/dist/packages/crucible/seeds/ruby/seed-049 3.rb +0 -20
  107. package/dist/packages/crucible/seeds/ruby/seed-050 3.rb +0 -27
  108. package/dist/packages/crucible/seeds/rust/seed-001 3.rs +0 -25
  109. package/dist/packages/crucible/seeds/rust/seed-002 3.rs +0 -20
  110. package/dist/packages/crucible/seeds/rust/seed-003 3.rs +0 -23
  111. package/dist/packages/crucible/seeds/rust/seed-004 3.rs +0 -21
  112. package/dist/packages/crucible/seeds/rust/seed-005 3.rs +0 -21
  113. package/dist/packages/crucible/seeds/rust/seed-006 3.rs +0 -24
  114. package/dist/packages/crucible/seeds/rust/seed-007 3.rs +0 -20
  115. package/dist/packages/crucible/seeds/rust/seed-008 3.rs +0 -19
  116. package/dist/packages/crucible/seeds/rust/seed-009 3.rs +0 -28
  117. package/dist/packages/crucible/seeds/rust/seed-010 3.rs +0 -28
  118. package/dist/packages/crucible/seeds/rust/seed-011 3.rs +0 -25
  119. package/dist/packages/crucible/seeds/rust/seed-012 3.rs +0 -31
  120. package/dist/packages/crucible/seeds/rust/seed-013 3.rs +0 -27
  121. package/dist/packages/crucible/seeds/rust/seed-014 3.rs +0 -30
  122. package/dist/packages/crucible/seeds/rust/seed-015 3.rs +0 -33
  123. package/dist/packages/crucible/seeds/rust/seed-016 3.rs +0 -22
  124. package/dist/packages/crucible/seeds/rust/seed-017 3.rs +0 -28
  125. package/dist/packages/crucible/seeds/rust/seed-018 3.rs +0 -21
  126. package/dist/packages/crucible/seeds/rust/seed-019 3.rs +0 -36
  127. package/dist/packages/crucible/seeds/rust/seed-020 3.rs +0 -27
  128. package/dist/packages/crucible/seeds/rust/seed-021 3.rs +0 -26
  129. package/dist/packages/crucible/seeds/rust/seed-022 3.rs +0 -23
  130. package/dist/packages/crucible/seeds/rust/seed-023 3.rs +0 -22
  131. package/dist/packages/crucible/seeds/rust/seed-024 3.rs +0 -24
  132. package/dist/packages/crucible/seeds/rust/seed-025 3.rs +0 -29
  133. package/dist/packages/crucible/seeds/rust/seed-026 3.rs +0 -23
  134. package/dist/packages/crucible/seeds/rust/seed-027 3.rs +0 -24
  135. package/dist/packages/crucible/seeds/rust/seed-028 3.rs +0 -25
  136. package/dist/packages/crucible/seeds/rust/seed-029 3.rs +0 -25
  137. package/dist/packages/crucible/seeds/rust/seed-030 3.rs +0 -30
  138. package/dist/packages/crucible/seeds/rust/seed-031 3.rs +0 -22
  139. package/dist/packages/crucible/seeds/rust/seed-032 3.rs +0 -25
  140. package/dist/packages/crucible/seeds/rust/seed-033 3.rs +0 -25
  141. package/dist/packages/crucible/seeds/rust/seed-034 3.rs +0 -20
  142. package/dist/packages/crucible/seeds/rust/seed-035 3.rs +0 -28
  143. package/dist/packages/crucible/seeds/rust/seed-036 3.rs +0 -26
  144. package/dist/packages/crucible/seeds/rust/seed-037 3.rs +0 -31
  145. package/dist/packages/crucible/seeds/rust/seed-038 3.rs +0 -25
  146. package/dist/packages/crucible/seeds/rust/seed-039 3.rs +0 -28
  147. package/dist/packages/crucible/seeds/rust/seed-040 3.rs +0 -27
  148. package/dist/packages/crucible/seeds/rust/seed-041 3.rs +0 -32
  149. package/dist/packages/crucible/seeds/rust/seed-042 3.rs +0 -27
  150. package/dist/packages/crucible/seeds/rust/seed-043 3.rs +0 -29
  151. package/dist/packages/crucible/seeds/rust/seed-044 3.rs +0 -25
  152. package/dist/packages/crucible/seeds/rust/seed-045 3.rs +0 -28
  153. package/dist/packages/crucible/seeds/rust/seed-046 3.rs +0 -25
  154. package/dist/packages/crucible/seeds/rust/seed-047 3.rs +0 -34
  155. package/dist/packages/crucible/seeds/rust/seed-048 3.rs +0 -21
  156. package/dist/packages/crucible/seeds/rust/seed-049 3.rs +0 -26
  157. package/dist/packages/crucible/seeds/rust/seed-050 3.rs +0 -23
  158. package/dist/packages/crucible/seeds/ts/seed-001 3.ts +0 -32
  159. package/dist/packages/crucible/seeds/ts/seed-002 3.ts +0 -34
  160. package/dist/packages/crucible/seeds/ts/seed-003 3.ts +0 -28
  161. package/dist/packages/crucible/seeds/ts/seed-004 3.ts +0 -34
  162. package/dist/packages/crucible/seeds/ts/seed-005 3.ts +0 -32
  163. package/dist/packages/crucible/seeds/ts/seed-006 3.ts +0 -31
  164. package/dist/packages/crucible/seeds/ts/seed-007 3.ts +0 -28
  165. package/dist/packages/crucible/seeds/ts/seed-008 3.ts +0 -40
  166. package/dist/packages/crucible/seeds/ts/seed-009 3.ts +0 -31
  167. package/dist/packages/crucible/seeds/ts/seed-010 3.ts +0 -33
  168. package/dist/packages/crucible/seeds/ts/seed-011 3.ts +0 -29
  169. package/dist/packages/crucible/seeds/ts/seed-012 3.ts +0 -34
  170. package/dist/packages/crucible/seeds/ts/seed-013 3.ts +0 -31
  171. package/dist/packages/crucible/seeds/ts/seed-014 3.ts +0 -36
  172. package/dist/packages/crucible/seeds/ts/seed-015 3.ts +0 -31
  173. package/dist/packages/crucible/seeds/ts/seed-016 3.ts +0 -37
  174. package/dist/packages/crucible/seeds/ts/seed-017 3.ts +0 -44
  175. package/dist/packages/crucible/seeds/ts/seed-018 3.ts +0 -33
  176. package/dist/packages/crucible/seeds/ts/seed-019 3.ts +0 -32
  177. package/dist/packages/crucible/seeds/ts/seed-020 3.ts +0 -33
  178. package/dist/packages/crucible/seeds/ts/seed-021 3.ts +0 -33
  179. package/dist/packages/crucible/seeds/ts/seed-022 3.ts +0 -34
  180. package/dist/packages/crucible/seeds/ts/seed-023 3.ts +0 -33
  181. package/dist/packages/crucible/seeds/ts/seed-024 3.ts +0 -35
  182. package/dist/packages/crucible/seeds/ts/seed-025 3.ts +0 -29
  183. package/dist/packages/crucible/seeds/ts/seed-026 3.ts +0 -36
  184. package/dist/packages/crucible/seeds/ts/seed-027 3.ts +0 -30
  185. package/dist/packages/crucible/seeds/ts/seed-028 3.ts +0 -32
  186. package/dist/packages/crucible/seeds/ts/seed-029 3.ts +0 -34
  187. package/dist/packages/crucible/seeds/ts/seed-030 3.ts +0 -37
  188. package/dist/packages/crucible/seeds/ts/seed-031 3.ts +0 -31
  189. package/dist/packages/crucible/seeds/ts/seed-032 3.ts +0 -32
  190. package/dist/packages/crucible/seeds/ts/seed-033 3.ts +0 -32
  191. package/dist/packages/crucible/seeds/ts/seed-034 3.ts +0 -34
  192. package/dist/packages/crucible/seeds/ts/seed-035 3.ts +0 -29
  193. package/dist/packages/crucible/seeds/ts/seed-036 3.ts +0 -34
  194. package/dist/packages/crucible/seeds/ts/seed-037 3.ts +0 -33
  195. package/dist/packages/crucible/seeds/ts/seed-038 3.ts +0 -29
  196. package/dist/packages/crucible/seeds/ts/seed-039 3.ts +0 -35
  197. package/dist/packages/crucible/seeds/ts/seed-040 3.ts +0 -31
  198. package/dist/packages/crucible/seeds/ts/seed-041 3.ts +0 -29
  199. package/dist/packages/crucible/seeds/ts/seed-042 3.ts +0 -33
  200. package/dist/packages/crucible/seeds/ts/seed-043 3.ts +0 -32
  201. package/dist/packages/crucible/seeds/ts/seed-044 3.ts +0 -36
  202. package/dist/packages/crucible/seeds/ts/seed-045 3.ts +0 -39
  203. package/dist/packages/crucible/seeds/ts/seed-046 3.ts +0 -41
  204. package/dist/packages/crucible/seeds/ts/seed-047 3.ts +0 -33
  205. package/dist/packages/crucible/seeds/ts/seed-048 3.ts +0 -33
  206. package/dist/packages/crucible/seeds/ts/seed-049 3.ts +0 -34
  207. package/dist/packages/crucible/seeds/ts/seed-050 3.ts +0 -41
  208. package/dist/packages/crucible/seeds/ts/seed-051 3.ts +0 -34
  209. package/dist/packages/crucible/seeds/ts/seed-052 3.ts +0 -36
@@ -1,36 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: ssrf
3
- # severity: high
4
- # language: python
5
- # expected: SSRF via httpx.get with user-controlled URL and redirect following
6
-
7
- import httpx
8
- from fastapi import FastAPI, Query
9
- from fastapi.responses import JSONResponse
10
-
11
- app = FastAPI()
12
-
13
-
14
- @app.get("/import/data")
15
- async def import_data(source: str = Query(...)):
16
- # source from user — can be internal URL or cloud metadata endpoint
17
- async with httpx.AsyncClient(follow_redirects=True, timeout=10.0) as client:
18
- resp = await client.get(source)
19
- return JSONResponse({"status": resp.status_code, "preview": resp.text[:500]})
20
-
21
-
22
- @app.post("/oembed")
23
- async def fetch_oembed(url: str = Query(...), provider: str = Query(...)):
24
- # provider is an oembed endpoint from user — SSRF to internal services
25
- oembed_url = f"{provider}?url={url}&format=json"
26
- async with httpx.AsyncClient(timeout=8.0) as client:
27
- resp = await client.get(oembed_url)
28
- return resp.json()
29
-
30
-
31
- @app.get("/link-preview")
32
- async def link_preview(target: str = Query(...)):
33
- async with httpx.AsyncClient(timeout=5.0, follow_redirects=True) as client:
34
- resp = await client.get(target)
35
- title = resp.text.split("<title>")[1].split("</title>")[0] if "<title>" in resp.text else ""
36
- return {"title": title, "url": target}
@@ -1,33 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: ssrf
3
- # severity: high
4
- # language: python
5
- # expected: SSRF via urllib.request.urlopen with unvalidated user URL
6
-
7
- import urllib.request
8
- import urllib.parse
9
- from django.http import JsonResponse
10
-
11
-
12
- def fetch_remote_resource(request):
13
- """Fetch an external resource on behalf of the client."""
14
- url = request.GET.get("url", "")
15
-
16
- # No validation — internal URLs, file://, gopher:// etc. all accepted
17
- try:
18
- with urllib.request.urlopen(url, timeout=10) as response:
19
- content = response.read(4096).decode("utf-8", errors="replace")
20
- content_type = response.headers.get("Content-Type", "")
21
- return JsonResponse({"content": content, "content_type": content_type})
22
- except Exception as e:
23
- return JsonResponse({"error": str(e)}, status=400)
24
-
25
-
26
- def check_url_status(request):
27
- target = request.POST.get("target", "")
28
- try:
29
- req = urllib.request.Request(target, headers={"User-Agent": "StatusChecker/1.0"})
30
- with urllib.request.urlopen(req, timeout=8) as resp:
31
- return JsonResponse({"status": resp.status, "url": target})
32
- except urllib.error.HTTPError as e:
33
- return JsonResponse({"status": e.code})
@@ -1,44 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: ssrf
3
- # severity: high
4
- # language: python
5
- # expected: SSRF via aiohttp.ClientSession with user-controlled URL in async service
6
-
7
- import aiohttp
8
- from fastapi import FastAPI, Body
9
- from pydantic import BaseModel
10
-
11
- app = FastAPI()
12
-
13
-
14
- class NotificationConfig(BaseModel):
15
- webhook_url: str # user-controlled
16
- event_type: str
17
- secret: str = ""
18
-
19
-
20
- class ImportRequest(BaseModel):
21
- data_url: str # user-controlled source URL
22
- format: str = "json"
23
-
24
-
25
- @app.post("/notifications/send")
26
- async def send_notification(config: NotificationConfig):
27
- async with aiohttp.ClientSession() as session:
28
- # webhook_url from user — attacker targets http://10.0.0.1/admin
29
- async with session.post(
30
- config.webhook_url,
31
- json={"event": config.event_type},
32
- headers={"X-Secret": config.secret},
33
- timeout=aiohttp.ClientTimeout(total=10),
34
- allow_redirects=True, # follows redirects — pivot to internal
35
- ) as resp:
36
- return {"status": resp.status}
37
-
38
-
39
- @app.post("/data/import")
40
- async def import_data(req: ImportRequest):
41
- async with aiohttp.ClientSession() as session:
42
- async with session.get(req.data_url) as resp:
43
- data = await resp.text()
44
- return {"preview": data[:500], "format": req.format}
@@ -1,35 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: deserialization
3
- # severity: critical
4
- # language: python
5
- # expected: pickle.loads used with untrusted data — arbitrary code execution
6
-
7
- import pickle
8
- import base64
9
- from flask import Flask, request, jsonify
10
-
11
- app = Flask(__name__)
12
-
13
-
14
- @app.route("/session/restore", methods=["POST"])
15
- def restore_session():
16
- session_data = request.cookies.get("session", "")
17
- if not session_data:
18
- return jsonify({"error": "No session"}), 401
19
-
20
- try:
21
- # pickle.loads executes arbitrary Python code embedded in the payload
22
- # Attacker payload: object with __reduce__ returning os.system('id')
23
- decoded = base64.b64decode(session_data)
24
- session = pickle.loads(decoded)
25
- return jsonify({"user": session.get("user"), "role": session.get("role")})
26
- except Exception as e:
27
- return jsonify({"error": "Invalid session"}), 400
28
-
29
-
30
- @app.route("/cache/get", methods=["POST"])
31
- def get_cached():
32
- raw = request.get_json().get("data", "")
33
- # Data from Redis — but Redis can be poisoned by attacker
34
- obj = pickle.loads(base64.b64decode(raw))
35
- return jsonify({"value": str(obj)})
@@ -1,39 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: deserialization
3
- # severity: critical
4
- # language: python
5
- # expected: pickle.load used with untrusted file upload and shelve with user-controlled key
6
-
7
- import pickle
8
- import shelve
9
- from fastapi import FastAPI, UploadFile, File, HTTPException
10
-
11
- app = FastAPI()
12
-
13
-
14
- @app.post("/models/load")
15
- async def load_model(file: UploadFile = File(...)):
16
- contents = await file.read()
17
- try:
18
- # User-uploaded .pkl file deserialized without any validation
19
- model = pickle.loads(contents)
20
- return {"model_type": type(model).__name__}
21
- except Exception as e:
22
- raise HTTPException(status_code=400, detail=str(e))
23
-
24
-
25
- @app.get("/cache/{key}")
26
- def get_from_cache(key: str):
27
- # shelve uses pickle internally — user-controlled key reads arbitrary shelf entries
28
- with shelve.open("/var/cache/app_cache") as db:
29
- if key not in db:
30
- raise HTTPException(status_code=404, detail="Not found")
31
- value = db[key] # deserialized via pickle
32
- return {"value": value}
33
-
34
-
35
- @app.post("/tasks/restore")
36
- async def restore_task_state(file: UploadFile = File(...)):
37
- data = await file.read()
38
- state = pickle.loads(data) # untrusted task state from client
39
- return {"task_id": state.get("id"), "status": state.get("status")}
@@ -1,39 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: deserialization
3
- # severity: high
4
- # language: python
5
- # expected: marshal.loads and jsonpickle.decode used with untrusted data
6
-
7
- import marshal
8
- import jsonpickle
9
- from flask import Flask, request, jsonify
10
-
11
- app = Flask(__name__)
12
-
13
-
14
- @app.post("/code/load")
15
- def load_code():
16
- """Load a compiled Python code object from client."""
17
- data = request.get_data()
18
- # marshal.loads with user data can reconstruct code objects — arbitrary execution
19
- code_obj = marshal.loads(data)
20
- result = eval(code_obj)
21
- return jsonify({"result": str(result)})
22
-
23
-
24
- @app.post("/object/restore")
25
- def restore_object():
26
- payload = request.get_json()
27
- serialized = payload.get("object", "")
28
- # jsonpickle can deserialize arbitrary Python objects including those with __reduce__
29
- obj = jsonpickle.decode(serialized)
30
- return jsonify({"type": type(obj).__name__, "repr": repr(obj)})
31
-
32
-
33
- @app.post("/state/import")
34
- def import_state():
35
- body = request.get_json()
36
- state_json = body.get("state", "null")
37
- # jsonpickle.decode with py/object tags allows instantiation of arbitrary classes
38
- state = jsonpickle.decode(state_json, classes=None)
39
- return jsonify({"imported": True, "keys": list(state.keys()) if isinstance(state, dict) else []})
@@ -1,38 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: yaml_injection
3
- # severity: critical
4
- # language: python
5
- # expected: yaml.load() with untrusted input — arbitrary code execution via YAML deserialization
6
-
7
- import yaml
8
- from flask import Flask, request, jsonify
9
-
10
- app = Flask(__name__)
11
-
12
-
13
- @app.route("/config/upload", methods=["POST"])
14
- def upload_config():
15
- raw_yaml = request.data.decode("utf-8")
16
- # yaml.load without Loader= defaults to full deserialization — RCE possible
17
- # Attacker payload: "!!python/object/apply:os.system ['id']"
18
- config = yaml.load(raw_yaml)
19
- return jsonify({"loaded_keys": list(config.keys()) if isinstance(config, dict) else []})
20
-
21
-
22
- @app.route("/workflow/import", methods=["POST"])
23
- def import_workflow():
24
- yaml_content = request.get_json().get("yaml", "")
25
- # yaml.load with untrusted string
26
- workflow = yaml.load(yaml_content)
27
- steps = workflow.get("steps", []) if isinstance(workflow, dict) else []
28
- return jsonify({"steps": steps})
29
-
30
-
31
- @app.route("/settings/restore", methods=["POST"])
32
- def restore_settings():
33
- uploaded = request.files.get("settings")
34
- if not uploaded:
35
- return jsonify({"error": "No file"}), 400
36
- content = uploaded.read().decode("utf-8")
37
- settings = yaml.load(content) # unsafe loader
38
- return jsonify({"restored": True, "settings": settings})
@@ -1,41 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: yaml_injection
3
- # severity: critical
4
- # language: python
5
- # expected: yaml.load() without safe_load — RCE via !!python/object tags in YAML
6
-
7
- import yaml
8
- import os
9
- from pathlib import Path
10
-
11
-
12
- def load_pipeline_config(config_path: str) -> dict:
13
- """Load a pipeline configuration from a YAML file."""
14
- with open(config_path, "r") as f:
15
- # yaml.load() without Loader allows arbitrary Python object instantiation
16
- # Attacker-controlled config file: !!python/object/apply:subprocess.check_output [['id']]
17
- return yaml.load(f)
18
-
19
-
20
- def load_user_preferences(user_id: str) -> dict:
21
- prefs_path = Path(f"/var/app/preferences/{user_id}.yaml")
22
- if not prefs_path.exists():
23
- return {}
24
- with prefs_path.open() as f:
25
- # User controls their own preference file — YAML injection if file content is user-controlled
26
- return yaml.load(f.read())
27
-
28
-
29
- def merge_configs(*config_paths: str) -> dict:
30
- merged = {}
31
- for path in config_paths:
32
- with open(path) as f:
33
- data = yaml.load(f) # unsafe
34
- if isinstance(data, dict):
35
- merged.update(data)
36
- return merged
37
-
38
-
39
- def parse_webhook_payload(yaml_str: str) -> dict:
40
- # Webhook body parsed as YAML without safe_load
41
- return yaml.load(yaml_str)
@@ -1,15 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: hardcoded_secret
3
- # severity: critical
4
- # language: ruby
5
- # expected: Hardcoded Rails secret_key_base in source code
6
-
7
- module MyApp
8
- class Application < Rails::Application
9
- config.secret_key_base = "b3f6d9e2a1c4f7a8b9d0e1f2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2"
10
-
11
- config.active_record.encryption.primary_key = "my_primary_encryption_key_not_secret"
12
- config.active_record.encryption.deterministic_key = "my_deterministic_key_not_secret_too"
13
- config.active_record.encryption.key_derivation_salt = "my_key_derivation_salt_hardcoded"
14
- end
15
- end
@@ -1,22 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: hardcoded_secret
3
- # severity: critical
4
- # language: ruby
5
- # expected: Hardcoded database password in database.yml equivalent or initializer
6
-
7
- DB_CONFIG = {
8
- adapter: 'postgresql',
9
- host: 'prod-db.example.com',
10
- database: 'production_db',
11
- username: 'app_user',
12
- password: 'Sup3rS3cr3tP@ssw0rd!2024',
13
- port: 5432
14
- }.freeze
15
-
16
- REDIS_URL = 'redis://:myR3d1sS3cret@redis.prod.example.com:6379/0'
17
- MONGO_URI = 'mongodb://admin:M0ng0P@ssw0rd@mongo.example.com:27017/proddb'
18
-
19
- def establish_db_connection
20
- puts "Connecting with password: #{DB_CONFIG[:password]}"
21
- ActiveRecord::Base.establish_connection(DB_CONFIG)
22
- end
@@ -1,25 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: hardcoded_secret
3
- # severity: critical
4
- # language: ruby
5
- # expected: Hardcoded AWS credentials in Ruby initializer
6
-
7
- require 'aws-sdk-s3'
8
-
9
- AWS_ACCESS_KEY = 'AKIAIOSFODNN7EXAMPLE1'
10
- AWS_SECRET_KEY = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
11
- AWS_REGION = 'us-east-1'
12
- S3_BUCKET = 'my-production-bucket'
13
-
14
- Aws.config.update(
15
- region: AWS_REGION,
16
- credentials: Aws::Credentials.new(AWS_ACCESS_KEY, AWS_SECRET_KEY)
17
- )
18
-
19
- def s3_client
20
- Aws::S3::Client.new(
21
- access_key_id: AWS_ACCESS_KEY,
22
- secret_access_key: AWS_SECRET_KEY,
23
- region: AWS_REGION
24
- )
25
- end
@@ -1,17 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: hardcoded_secret
3
- # severity: critical
4
- # language: ruby
5
- # expected: Hardcoded Stripe API keys in Rails initializer
6
-
7
- Stripe.api_key = 'sk_live_51HxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefgh'
8
-
9
- STRIPE_CONFIG = {
10
- publishable_key: 'pk_live_51HxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321zyxwvu',
11
- webhook_secret: 'whsec_abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJ',
12
- api_key: 'sk_live_51HxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefgh'
13
- }.freeze
14
-
15
- def charge_customer(amount, token)
16
- Stripe::Charge.create(amount: amount, currency: 'usd', source: token)
17
- end
@@ -1,21 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: hardcoded_secret
3
- # severity: high
4
- # language: ruby
5
- # expected: Hardcoded JWT secret and admin API token
6
-
7
- JWT_SECRET = 'mySuperSecretJWTKey_doNotShare_2024_production'
8
- ADMIN_TOKEN = 'admin_sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
9
- HMAC_SECRET = 'hmac-signing-secret-do-not-commit-to-git!!!!'
10
-
11
- def generate_jwt(payload)
12
- JWT.encode(payload, JWT_SECRET, 'HS256')
13
- end
14
-
15
- def verify_admin(token)
16
- token == ADMIN_TOKEN
17
- end
18
-
19
- def sign_request(body)
20
- OpenSSL::HMAC.hexdigest('SHA256', HMAC_SECRET, body)
21
- end
@@ -1,17 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: hardcoded_secret
3
- # severity: critical
4
- # language: ruby
5
- # expected: Hardcoded OAuth client secret and social login credentials
6
-
7
- GOOGLE_CLIENT_ID = '1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com'
8
- GOOGLE_CLIENT_SECRET = 'GOCSPX-abcdefghijklmnopqrstuvwxyz123456'
9
- GITHUB_CLIENT_SECRET = 'ghsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
10
- FACEBOOK_APP_SECRET = 'abcdef0123456789abcdef0123456789'
11
-
12
- OmniAuth.config.on_failure = proc { |env| OmniAuth::FailureEndpoint.call(env) }
13
-
14
- Rails.application.config.middleware.use OmniAuth::Builder do
15
- provider :google_oauth2, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
16
- provider :github, 'github_client_id_xxxxx', GITHUB_CLIENT_SECRET
17
- end
@@ -1,16 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: hardcoded_secret
3
- # severity: critical
4
- # language: ruby
5
- # expected: Hardcoded Twilio credentials and SendGrid API key
6
-
7
- TWILIO_ACCOUNT_SID = 'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
8
- TWILIO_AUTH_TOKEN = 'your_auth_token_here_32chars_long!!'
9
- TWILIO_PHONE = '+15005550006'
10
- SENDGRID_API_KEY = 'SG.xxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
11
-
12
- def send_sms(to, message)
13
- puts "Using SID: #{TWILIO_ACCOUNT_SID}, Token: #{TWILIO_AUTH_TOKEN}"
14
- client = Twilio::REST::Client.new(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
15
- client.messages.create(from: TWILIO_PHONE, to: to, body: message)
16
- end
@@ -1,18 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: hardcoded_secret
3
- # severity: critical
4
- # language: ruby
5
- # expected: Hardcoded Slack webhook URL and signing secret
6
-
7
- SLACK_BOT_TOKEN = 'xoxb-1234567890-1234567890123-abcdefghijklmnopqrstuvwx'
8
- SLACK_SIGNING_SECRET = 'abcdef0123456789abcdef0123456789'
9
- SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
10
-
11
- def post_to_slack(message)
12
- puts "Posting with token: #{SLACK_BOT_TOKEN[0..14]}..."
13
- Net::HTTP.post(
14
- URI(SLACK_WEBHOOK_URL),
15
- { text: message }.to_json,
16
- 'Content-Type' => 'application/json'
17
- )
18
- end
@@ -1,20 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: sql_injection
3
- # severity: critical
4
- # language: ruby
5
- # expected: SQL injection via string interpolation in ActiveRecord where()
6
-
7
- class UserController < ApplicationController
8
- def search
9
- username = params[:username]
10
- @users = User.where("username = '#{username}'")
11
- render json: @users
12
- end
13
-
14
- def filter
15
- role = params[:role]
16
- since = params[:since]
17
- @records = User.where("role = '#{role}' AND created_at >= '#{since}'")
18
- render json: @records
19
- end
20
- end
@@ -1,24 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: sql_injection
3
- # severity: critical
4
- # language: ruby
5
- # expected: ActiveRecord find_by_sql with string interpolation
6
-
7
- class ReportController < ApplicationController
8
- def show
9
- user_id = params[:user_id]
10
- type = params[:type]
11
- @data = Report.find_by_sql(
12
- "SELECT * FROM reports WHERE user_id = #{user_id} AND type = '#{type}'"
13
- )
14
- render json: @data
15
- end
16
-
17
- def custom_query
18
- condition = params[:condition]
19
- @results = ActiveRecord::Base.connection.execute(
20
- "SELECT * FROM events WHERE #{condition}"
21
- )
22
- render json: @results.to_a
23
- end
24
- end
@@ -1,21 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: sql_injection
3
- # severity: critical
4
- # language: ruby
5
- # expected: ORDER BY clause built from user-supplied column name without whitelist
6
-
7
- class ProductsController < ApplicationController
8
- def index
9
- sort_col = params[:sort_by]
10
- sort_dir = params[:direction]
11
- @products = Product.where("active = true").order("#{sort_col} #{sort_dir}")
12
- render json: @products
13
- end
14
-
15
- def search
16
- term = params[:q]
17
- category = params[:category]
18
- @items = Product.where("name LIKE '%#{term}%' AND category = '#{category}'")
19
- render json: @items
20
- end
21
- end
@@ -1,22 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: sql_injection
3
- # severity: critical
4
- # language: ruby
5
- # expected: Raw SQL executed via connection.execute with user input
6
-
7
- def run_analytics(dimension, filter_expr)
8
- sql = "SELECT #{dimension}, COUNT(*) FROM events WHERE #{filter_expr} GROUP BY #{dimension}"
9
- ActiveRecord::Base.connection.execute(sql)
10
- end
11
-
12
- def delete_records(table, condition)
13
- ActiveRecord::Base.connection.execute(
14
- "DELETE FROM #{table} WHERE #{condition}"
15
- )
16
- end
17
-
18
- def update_field(table, field, value, id)
19
- ActiveRecord::Base.connection.execute(
20
- "UPDATE #{table} SET #{field} = '#{value}' WHERE id = #{id}"
21
- )
22
- end
@@ -1,21 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: sql_injection
3
- # severity: critical
4
- # language: ruby
5
- # expected: Sequel dataset filter with string interpolation
6
-
7
- require 'sequel'
8
-
9
- DB = Sequel.connect('sqlite://db/production.sqlite3')
10
-
11
- def find_user(username, password)
12
- DB[:users].where("username = '#{username}' AND password = '#{password}'").first
13
- end
14
-
15
- def get_orders(status, user_id)
16
- DB[:orders].where("status = '#{status}' AND user_id = #{user_id}").all
17
- end
18
-
19
- def search_items(keyword)
20
- DB[:items].where("name LIKE '%#{keyword}%' OR description LIKE '%#{keyword}%'").all
21
- end
@@ -1,16 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: sql_injection
3
- # severity: high
4
- # language: ruby
5
- # expected: ActiveRecord group/having clause with unparameterized user input
6
-
7
- class AnalyticsController < ApplicationController
8
- def aggregate
9
- group_by = params[:group_by]
10
- having = params[:having]
11
- @data = Order.group(group_by)
12
- .having("COUNT(*) #{having}")
13
- .select("#{group_by}, COUNT(*) as total")
14
- render json: @data
15
- end
16
- end
@@ -1,18 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: sql_injection
3
- # severity: critical
4
- # language: ruby
5
- # expected: pg gem exec_params replaced with exec — user string in raw SQL
6
-
7
- require 'pg'
8
-
9
- conn = PG.connect(dbname: 'production')
10
-
11
- def login(conn, username, password)
12
- result = conn.exec("SELECT * FROM users WHERE username = '#{username}' AND password_hash = '#{password}'")
13
- result.first
14
- end
15
-
16
- def get_profile(conn, user_id)
17
- conn.exec("SELECT * FROM profiles WHERE user_id = #{user_id}").first
18
- end
@@ -1,17 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: xss
3
- # severity: high
4
- # language: ruby
5
- # expected: render html: with user content — unescaped XSS
6
-
7
- class CommentsController < ApplicationController
8
- def show
9
- @comment = Comment.find(params[:id])
10
- render html: @comment.body.html_safe
11
- end
12
-
13
- def preview
14
- content = params[:content]
15
- render html: content.html_safe
16
- end
17
- end
@@ -1,25 +0,0 @@
1
- # [CRUCIBLE-SEED]
2
- # category: xss
3
- # severity: high
4
- # language: ruby
5
- # expected: raw() helper used with user-controlled content in ERB template
6
-
7
- # In a Rails view (equivalent Ruby)
8
- class ArticlesController < ApplicationController
9
- helper_method :render_article
10
-
11
- def show
12
- @article = Article.find(params[:id])
13
- @title = params[:title] || @article.title
14
- @body = @article.body
15
- end
16
- end
17
-
18
- # Equivalent ERB logic (Ruby):
19
- def render_article_html(title, body, author_bio)
20
- "<h1>#{raw(title)}</h1><div>#{raw(body)}</div><footer>#{raw(author_bio)}</footer>"
21
- end
22
-
23
- def raw(str)
24
- str.to_s
25
- end