codejury 0.1.0__py3-none-any.whl

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 (67) hide show
  1. codejury/__init__.py +8 -0
  2. codejury/agents/__init__.py +6 -0
  3. codejury/agents/base.py +21 -0
  4. codejury/agents/debate.py +188 -0
  5. codejury/agents/mock.py +38 -0
  6. codejury/agents/parsing.py +42 -0
  7. codejury/agents/verifier.py +106 -0
  8. codejury/assembly.py +76 -0
  9. codejury/cli.py +196 -0
  10. codejury/data/capabilities/authentication.yaml +67 -0
  11. codejury/data/capabilities/authorization.yaml +55 -0
  12. codejury/data/capabilities/business_logic.yaml +58 -0
  13. codejury/data/capabilities/crypto.yaml +78 -0
  14. codejury/data/capabilities/data_protection.yaml +57 -0
  15. codejury/data/capabilities/dependency_config.yaml +52 -0
  16. codejury/data/capabilities/error_logging.yaml +49 -0
  17. codejury/data/capabilities/input_validation.yaml +92 -0
  18. codejury/data/capabilities/output_encoding.yaml +56 -0
  19. codejury/data/capabilities/secrets.yaml +51 -0
  20. codejury/data/capabilities/session.yaml +60 -0
  21. codejury/data/golden/authn_bcrypt_password.yaml +5 -0
  22. codejury/data/golden/authn_sha256_password.yaml +5 -0
  23. codejury/data/golden/sqli_fstring_query.yaml +5 -0
  24. codejury/data/golden/sqli_parameterized_query.yaml +5 -0
  25. codejury/data/tasks/audit_diff_debate.yaml +4 -0
  26. codejury/data/tasks/quick_scan_single.yaml +4 -0
  27. codejury/domain/__init__.py +5 -0
  28. codejury/domain/artifact.py +20 -0
  29. codejury/domain/capability.py +123 -0
  30. codejury/domain/context.py +26 -0
  31. codejury/domain/observation.py +104 -0
  32. codejury/domain/result.py +19 -0
  33. codejury/evaluation.py +107 -0
  34. codejury/infrastructure/__init__.py +4 -0
  35. codejury/infrastructure/json_parse.py +57 -0
  36. codejury/orchestrators/__init__.py +6 -0
  37. codejury/orchestrators/base.py +19 -0
  38. codejury/orchestrators/debate.py +57 -0
  39. codejury/orchestrators/pipeline.py +32 -0
  40. codejury/orchestrators/reflexion.py +58 -0
  41. codejury/orchestrators/single.py +24 -0
  42. codejury/providers/__init__.py +5 -0
  43. codejury/providers/anthropic.py +68 -0
  44. codejury/providers/base.py +42 -0
  45. codejury/providers/litellm.py +68 -0
  46. codejury/providers/mock.py +32 -0
  47. codejury/providers/openai.py +57 -0
  48. codejury/providers/openai_format.py +30 -0
  49. codejury/providers/retry.py +48 -0
  50. codejury/reporting.py +114 -0
  51. codejury/resources.py +13 -0
  52. codejury/sources/__init__.py +6 -0
  53. codejury/sources/base.py +17 -0
  54. codejury/sources/chunker.py +33 -0
  55. codejury/sources/diff.py +69 -0
  56. codejury/sources/function.py +35 -0
  57. codejury/sources/mock.py +25 -0
  58. codejury/sources/repo.py +44 -0
  59. codejury/tasks/__init__.py +6 -0
  60. codejury/tasks/base.py +55 -0
  61. codejury/tasks/registry.py +22 -0
  62. codejury-0.1.0.dist-info/METADATA +110 -0
  63. codejury-0.1.0.dist-info/RECORD +67 -0
  64. codejury-0.1.0.dist-info/WHEEL +5 -0
  65. codejury-0.1.0.dist-info/entry_points.txt +2 -0
  66. codejury-0.1.0.dist-info/licenses/LICENSE +21 -0
  67. codejury-0.1.0.dist-info/top_level.txt +1 -0
codejury/cli.py ADDED
@@ -0,0 +1,196 @@
1
+ """Command-line entry point.
2
+
3
+ ``dry-run`` wires every mock layer together with no API key, proving the
4
+ contracts compose. ``audit`` runs the real pipeline against the capability
5
+ library, backed by the Anthropic provider, under a chosen orchestration strategy
6
+ (single verifier, or finder/challenger/judge debate).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import sys
13
+
14
+ from codejury.agents.mock import MockAgent
15
+ from codejury.assembly import (
16
+ DEFAULT_MODEL,
17
+ PROVIDERS,
18
+ STRATEGIES,
19
+ build_orchestration,
20
+ make_provider,
21
+ run_over_source,
22
+ )
23
+ from codejury.domain.artifact import CodeArtifact
24
+ from codejury.domain.capability import Capability, load_capabilities
25
+ from codejury.domain.context import AnalysisContext
26
+ from codejury.domain.observation import Observation
27
+ from codejury.domain.result import AnalysisResult
28
+ from codejury.evaluation import Metrics, evaluate, load_cases
29
+ from codejury.orchestrators.single import SingleOrchestrator
30
+ from codejury.providers.base import Provider
31
+ from codejury.providers.mock import MockProvider
32
+ from codejury.reporting import to_json, to_markdown
33
+ from codejury.resources import CAPABILITIES_DIR, GOLDEN_DIR, TASKS_DIR
34
+ from codejury.sources.diff import DiffSource
35
+ from codejury.tasks.base import run_task
36
+ from codejury.tasks.registry import load_tasks
37
+
38
+ _FORMATS = ("text", "markdown", "json")
39
+
40
+
41
+ def dry_run() -> AnalysisResult:
42
+ provider = MockProvider(default="[mock] no real backend was called")
43
+ agent = MockAgent(provider=provider, role="verifier")
44
+ orchestrator = SingleOrchestrator()
45
+ capabilities = [
46
+ Capability(id="authn", name="Authentication"),
47
+ Capability(id="crypto", name="Cryptography"),
48
+ ]
49
+ ctx = AnalysisContext(
50
+ artifact=CodeArtifact(kind="diff", path="auth.py", content="+ hashlib.sha256(pwd)"),
51
+ capabilities=capabilities,
52
+ )
53
+ return orchestrator.run({"verifier": agent}, ctx)
54
+
55
+
56
+ def audit(
57
+ diff_text: str,
58
+ capabilities: list[Capability],
59
+ *,
60
+ provider: Provider,
61
+ model: str,
62
+ max_tokens: int = 2048,
63
+ strategy: str = "single",
64
+ ) -> list[tuple[str, AnalysisResult]]:
65
+ """Audit each changed file in `diff_text`, returning (path, result) per file."""
66
+ agents, orchestrator = build_orchestration(strategy, provider=provider, model=model, max_tokens=max_tokens)
67
+ return run_over_source(DiffSource(diff_text), capabilities, agents, orchestrator)
68
+
69
+
70
+ def _render_dry_run(result: AnalysisResult) -> str:
71
+ lines = [f"observations: {len(result.observations)}"]
72
+ for o in result.observations:
73
+ lines.append(f" [{o.kind}] {o.capability} by {o.produced_by} -> {getattr(o, 'status', '-')}")
74
+ if result.error:
75
+ lines.append(f"error: {result.error}")
76
+ return "\n".join(lines)
77
+
78
+
79
+ def _render_audit(results: list[tuple[str, AnalysisResult]]) -> str:
80
+ if not results:
81
+ return "no changed files in diff"
82
+ lines = []
83
+ for path, result in results:
84
+ lines.append(f"== {path} ==")
85
+ if result.error:
86
+ lines.append(f" error: {result.error}")
87
+ for o in result.observations:
88
+ lines.append(" " + _render_observation(o))
89
+ return "\n".join(lines)
90
+
91
+
92
+ def _render_observation(o: Observation) -> str:
93
+ if o.kind == "verdict":
94
+ matched = o.matched_anti or o.matched_correct
95
+ suffix = f" [{', '.join(matched)}]" if matched else ""
96
+ return f"{o.status:<11} {o.capability}{suffix}"
97
+ if o.kind == "finding":
98
+ cwe = f" {o.cwe}" if o.cwe else ""
99
+ return f"{'FINDING':<11} [{o.severity}{cwe}] {o.title}"
100
+ if o.kind == "concession":
101
+ return f"{'DISMISSED':<11} {o.target}: {o.reason}"
102
+ return f"{o.kind}: {o.capability}"
103
+
104
+
105
+ def _render_results(fmt: str, results: list[tuple[str, AnalysisResult]]) -> str:
106
+ return {"text": _render_audit, "markdown": to_markdown, "json": to_json}[fmt](results)
107
+
108
+
109
+ def _render_metrics(m: Metrics) -> str:
110
+ return (
111
+ f"cases: {m.total} (tp={m.tp} fp={m.fp} tn={m.tn} fn={m.fn})\n"
112
+ f"precision: {m.precision:.2f} recall: {m.recall:.2f} accuracy: {m.accuracy:.2f}"
113
+ )
114
+
115
+
116
+ def _read_diff(path: str) -> str:
117
+ if path == "-":
118
+ return sys.stdin.read()
119
+ with open(path, encoding="utf-8") as f:
120
+ return f.read()
121
+
122
+
123
+ def main(argv: list[str] | None = None) -> int:
124
+ parser = argparse.ArgumentParser(prog="codejury")
125
+ sub = parser.add_subparsers(dest="command")
126
+
127
+ sub.add_parser("dry-run", help="run the mock pipeline end to end")
128
+
129
+ audit_p = sub.add_parser("audit", help="audit a unified diff against the capability library")
130
+ audit_p.add_argument("diff", nargs="?", default="-", help="unified diff file, or - for stdin")
131
+ audit_p.add_argument("--capabilities", default=CAPABILITIES_DIR, help="capability YAML directory")
132
+ audit_p.add_argument("--orchestrator", choices=STRATEGIES, default="single")
133
+ audit_p.add_argument("--provider", choices=PROVIDERS, default="anthropic")
134
+ audit_p.add_argument("--format", choices=_FORMATS, default="text", dest="fmt")
135
+ audit_p.add_argument("--model", default=DEFAULT_MODEL)
136
+ audit_p.add_argument("--max-tokens", type=int, default=2048)
137
+ audit_p.add_argument("--retries", type=int, default=0, help="provider retry attempts on failure")
138
+
139
+ run_p = sub.add_parser("run", help="run a named task preset against a unified diff")
140
+ run_p.add_argument("task", help="task name")
141
+ run_p.add_argument("diff", nargs="?", default="-", help="unified diff file, or - for stdin")
142
+ run_p.add_argument("--tasks", default=TASKS_DIR, help="task YAML directory")
143
+ run_p.add_argument("--capabilities", default=CAPABILITIES_DIR, help="capability YAML directory")
144
+ run_p.add_argument("--format", choices=_FORMATS, default="text", dest="fmt")
145
+
146
+ eval_p = sub.add_parser("eval", help="score golden cases and report precision/recall")
147
+ eval_p.add_argument("--golden", default=GOLDEN_DIR, help="golden case YAML directory")
148
+ eval_p.add_argument("--capabilities", default=CAPABILITIES_DIR, help="capability YAML directory")
149
+ eval_p.add_argument("--provider", choices=PROVIDERS, default="anthropic")
150
+ eval_p.add_argument("--model", default=DEFAULT_MODEL)
151
+
152
+ args = parser.parse_args(argv)
153
+
154
+ if args.command == "audit":
155
+ results = audit(
156
+ _read_diff(args.diff),
157
+ load_capabilities(args.capabilities),
158
+ provider=make_provider(args.provider, retries=args.retries),
159
+ model=args.model,
160
+ max_tokens=args.max_tokens,
161
+ strategy=args.orchestrator,
162
+ )
163
+ print(_render_results(args.fmt, results))
164
+ return 0
165
+
166
+ if args.command == "run":
167
+ tasks = load_tasks(args.tasks)
168
+ if args.task not in tasks:
169
+ print(f"unknown task {args.task!r}; available: {', '.join(sorted(tasks)) or '(none)'}")
170
+ return 1
171
+ results = run_task(
172
+ tasks[args.task], DiffSource(_read_diff(args.diff)), load_capabilities(args.capabilities)
173
+ )
174
+ print(_render_results(args.fmt, results))
175
+ return 0
176
+
177
+ if args.command == "eval":
178
+ metrics = evaluate(
179
+ load_cases(args.golden),
180
+ load_capabilities(args.capabilities),
181
+ provider=make_provider(args.provider),
182
+ model=args.model,
183
+ )
184
+ print(_render_metrics(metrics))
185
+ return 0
186
+
187
+ if args.command in (None, "dry-run"):
188
+ print(_render_dry_run(dry_run()))
189
+ return 0
190
+
191
+ parser.print_help()
192
+ return 1
193
+
194
+
195
+ if __name__ == "__main__":
196
+ raise SystemExit(main())
@@ -0,0 +1,67 @@
1
+ id: authn
2
+ name: Authentication
3
+ asvs_chapter: V2
4
+ description: Mechanisms that verify a caller's claimed identity.
5
+
6
+ sub_capabilities:
7
+ password_storage:
8
+ correct_patterns:
9
+ - id: PWD-OK-1
10
+ description: Hash passwords with bcrypt, scrypt, or Argon2id at an OWASP-recommended cost
11
+ signals: ["bcrypt.hashpw", "argon2.PasswordHasher", "hashlib.scrypt", "passlib"]
12
+ why_ok: Slow, salted, memory-hard hashing resists GPU brute force and rainbow tables
13
+
14
+ anti_patterns:
15
+ - id: PWD-BAD-1
16
+ cwe: CWE-916
17
+ severity: HIGH
18
+ description: Hash passwords with a fast general-purpose digest such as MD5, SHA-1, or SHA-256
19
+ signals: ["hashlib.md5(", "hashlib.sha1(", "hashlib.sha256("]
20
+ why_bad: Unsalted fast hashes are brute-forced at billions of guesses per second on commodity GPUs
21
+ example_bad: |
22
+ hashlib.sha256(password.encode()).hexdigest()
23
+ example_good: |
24
+ bcrypt.hashpw(password.encode(), bcrypt.gensalt())
25
+
26
+ - id: PWD-BAD-2
27
+ cwe: CWE-256
28
+ severity: HIGH
29
+ description: Store passwords in plaintext or with reversible encryption
30
+ why_bad: A single database leak exposes every credential directly
31
+
32
+ - id: PWD-BAD-3
33
+ cwe: CWE-759
34
+ severity: MEDIUM
35
+ description: Hash without a per-user salt, using a global salt or no salt
36
+ why_bad: Identical passwords produce identical hashes, so precomputed tables apply across users
37
+
38
+ jwt_verification:
39
+ correct_patterns:
40
+ - id: JWT-OK-1
41
+ description: Verify the signature and validate iss, aud, exp, and nbf before trusting any claim
42
+ signals: ["algorithms=", "audience=", "issuer="]
43
+ why_ok: Rejects forged, expired, and misrouted tokens before their claims are used
44
+
45
+ anti_patterns:
46
+ - id: JWT-BAD-1
47
+ cwe: CWE-347
48
+ severity: HIGH
49
+ description: Accept the "none" algorithm, or sign with a weak or hardcoded HS256 secret
50
+ signals: ['algorithms=["none"]', 'algorithms=["HS256"]']
51
+ why_bad: An attacker can forge tokens that the server accepts as authentic
52
+
53
+ - id: JWT-BAD-2
54
+ cwe: CWE-345
55
+ severity: HIGH
56
+ description: Read claims before verifying the signature, or skip verification entirely
57
+ signals: ['verify_signature": False', "verify=False"]
58
+ why_bad: Attacker-controlled claims drive trust and authorization decisions
59
+ example_bad: |
60
+ claims = jwt.decode(token, options={"verify_signature": False})
61
+ example_good: |
62
+ claims = jwt.decode(token, key, algorithms=["RS256"], audience=AUD, issuer=ISS)
63
+
64
+ trigger_signals:
65
+ - routes matching /login, /register, /auth, or /token appear
66
+ - imports of jwt, pyjwt, python-jose, or authlib
67
+ - a user model with a password or password_hash field
@@ -0,0 +1,55 @@
1
+ id: authz
2
+ name: Authorization
3
+ asvs_chapter: V4
4
+ description: Deciding whether an authenticated caller is allowed to perform an action on a resource.
5
+
6
+ sub_capabilities:
7
+ object_level:
8
+ correct_patterns:
9
+ - id: OBJ-OK-1
10
+ description: Confirm the authenticated user owns or may access the object before acting on it
11
+ signals: ["filter(owner=request.user", "get_object_or_404(..., user=", "current_user.id =="]
12
+ why_ok: Ties every object access to the caller's identity, closing direct-object-reference holes
13
+
14
+ anti_patterns:
15
+ - id: IDOR-BAD-1
16
+ cwe: CWE-639
17
+ severity: HIGH
18
+ description: Fetch or mutate a record by a user-supplied id with no ownership or access check
19
+ signals: ["get(id=request", "objects.get(pk=", "WHERE id ="]
20
+ why_bad: Any user can read or change another user's data by changing the id (IDOR)
21
+ example_bad: |
22
+ account = Account.objects.get(id=request.GET["account_id"])
23
+ example_good: |
24
+ account = Account.objects.get(id=request.GET["account_id"], owner=request.user)
25
+
26
+ - id: AUTHZ-BAD-1
27
+ cwe: CWE-862
28
+ severity: HIGH
29
+ description: Missing function or endpoint level authorization, so any authenticated user reaches privileged actions
30
+ why_bad: Authentication is checked but authorization is not; non-admins can hit admin routes
31
+
32
+ privilege:
33
+ correct_patterns:
34
+ - id: PRIV-OK-1
35
+ description: Derive roles and permissions server-side from a trusted store, checked per request
36
+ why_ok: The client cannot grant itself privileges it was not assigned
37
+
38
+ anti_patterns:
39
+ - id: PRIV-BAD-1
40
+ cwe: CWE-269
41
+ severity: HIGH
42
+ description: Derive role or permission from a client-controlled field (request body, query param, client-set claim)
43
+ signals: ["request.json[\"role\"]", "is_admin = request", "request.POST.get(\"role\""]
44
+ why_bad: An attacker sets the field and escalates to admin
45
+
46
+ - id: PRIV-BAD-2
47
+ cwe: CWE-602
48
+ severity: MEDIUM
49
+ description: Enforce authorization only in the client or UI, not on the server
50
+ why_bad: The server is the only trust boundary; hidden buttons stop nothing
51
+
52
+ trigger_signals:
53
+ - routes with an :id or <pk> path parameter
54
+ - admin or privileged endpoints
55
+ - role, permission, or is_admin checks
@@ -0,0 +1,58 @@
1
+ id: business_logic
2
+ name: Business Logic
3
+ asvs_chapter: V11
4
+ description: Correctness of stateful workflows against abuse -- ordering, races, replay, and value validation.
5
+
6
+ sub_capabilities:
7
+ state_and_sequence:
8
+ correct_patterns:
9
+ - id: BL-OK-1
10
+ description: Enforce the workflow state machine server-side and make sensitive actions idempotent
11
+ signals: ["idempotency_key", "select_for_update", "with lock"]
12
+ why_ok: Steps cannot be skipped or applied twice regardless of client behavior
13
+
14
+ anti_patterns:
15
+ - id: SEQ-BAD-1
16
+ cwe: CWE-841
17
+ severity: MEDIUM
18
+ description: Do not enforce step ordering, so a later step can be invoked without the earlier ones
19
+ why_bad: An attacker reaches checkout or fulfillment without payment or validation
20
+
21
+ - id: RACE-BAD-1
22
+ cwe: CWE-362
23
+ severity: HIGH
24
+ description: Check-then-act on shared state without a lock or atomic update
25
+ signals: ["if balance >=", "balance -=", ".get(...)\n ...save()"]
26
+ why_bad: Concurrent requests both pass the check, enabling double spend
27
+ example_bad: |
28
+ if account.balance >= amount:
29
+ account.balance -= amount
30
+ example_good: |
31
+ with transaction.atomic():
32
+ acct = Account.objects.select_for_update().get(pk=id)
33
+ if acct.balance >= amount: acct.balance -= amount
34
+
35
+ limits_and_replay:
36
+ correct_patterns:
37
+ - id: LIM-OK-1
38
+ description: Validate amounts, quantities, and ranges server-side and rate-limit sensitive actions
39
+ why_ok: Client-supplied values cannot drive the outcome
40
+
41
+ anti_patterns:
42
+ - id: AMT-BAD-1
43
+ cwe: CWE-840
44
+ severity: MEDIUM
45
+ description: Trust a client-supplied price, amount, or quantity without server validation
46
+ signals: ["request.json[\"price\"]", "total = request", "quantity = request"]
47
+ why_bad: A user sets a negative or tiny amount and underpays
48
+
49
+ - id: REPLAY-BAD-1
50
+ cwe: CWE-799
51
+ severity: MEDIUM
52
+ description: No rate limiting or replay protection on sensitive actions
53
+ why_bad: Requests can be replayed or brute-forced without restriction
54
+
55
+ trigger_signals:
56
+ - multi-step flows such as checkout, transfer, or approval
57
+ - balance, amount, price, or quantity arithmetic
58
+ - shared-state updates without locking or transactions
@@ -0,0 +1,78 @@
1
+ id: crypto
2
+ name: Cryptography
3
+ asvs_chapter: V6
4
+ description: Correct choice and use of cryptographic primitives for confidentiality and integrity.
5
+
6
+ sub_capabilities:
7
+ algorithm:
8
+ correct_patterns:
9
+ - id: CRYPTO-OK-1
10
+ description: Use vetted authenticated encryption such as AES-GCM or ChaCha20-Poly1305
11
+ signals: ["AESGCM", "ChaCha20Poly1305", "AES.MODE_GCM"]
12
+ why_ok: Modern AEAD provides confidentiality and integrity with no mode pitfalls
13
+
14
+ anti_patterns:
15
+ - id: CRYPTO-BAD-1
16
+ cwe: CWE-327
17
+ severity: HIGH
18
+ description: Use a broken or obsolete cipher or hash for security (DES, 3DES, RC4, MD5, SHA-1)
19
+ signals: ["DES.new", "ARC4", "Crypto.Cipher.DES", "hashlib.md5", "hashlib.sha1"]
20
+ why_bad: These are practically attackable and unfit for protecting data
21
+ example_bad: |
22
+ cipher = DES.new(key, DES.MODE_ECB)
23
+ example_good: |
24
+ cipher = AESGCM(key); ct = cipher.encrypt(nonce, data, None)
25
+
26
+ - id: CRYPTO-BAD-2
27
+ cwe: CWE-327
28
+ severity: HIGH
29
+ description: Use ECB mode for a block cipher
30
+ signals: ["MODE_ECB"]
31
+ why_bad: ECB leaks plaintext structure because identical blocks encrypt identically
32
+
33
+ - id: CRYPTO-BAD-3
34
+ cwe: CWE-326
35
+ severity: MEDIUM
36
+ description: Use an inadequate key size (e.g. RSA shorter than 2048 bits)
37
+ why_bad: Undersized keys are within reach of feasible attacks
38
+
39
+ key_and_nonce:
40
+ correct_patterns:
41
+ - id: KEY-OK-1
42
+ description: Load keys from a KMS or secret store and rotate them; derive a fresh random nonce per message
43
+ why_ok: Keys are not exposed in code and nonces stay unique
44
+
45
+ anti_patterns:
46
+ - id: KEY-BAD-1
47
+ cwe: CWE-321
48
+ severity: HIGH
49
+ description: Hardcode a cryptographic key in source
50
+ signals: ["key = b\"", "SECRET_KEY = \"", "AES_KEY ="]
51
+ why_bad: Anyone with the source can decrypt everything
52
+
53
+ - id: IV-BAD-1
54
+ cwe: CWE-329
55
+ severity: HIGH
56
+ description: Use a static or reused IV/nonce
57
+ signals: ["iv = b\"\\x00", "nonce = b\"", "IV = bytes(16)"]
58
+ why_bad: Nonce reuse breaks GCM and reveals patterns under CBC
59
+
60
+ randomness:
61
+ correct_patterns:
62
+ - id: RND-OK-1
63
+ description: Use a CSPRNG (secrets, os.urandom) for tokens, keys, and salts
64
+ signals: ["secrets.token_", "os.urandom("]
65
+ why_ok: Cryptographically strong randomness is unpredictable
66
+
67
+ anti_patterns:
68
+ - id: RND-BAD-1
69
+ cwe: CWE-338
70
+ severity: MEDIUM
71
+ description: Use a non-cryptographic PRNG for security-sensitive values
72
+ signals: ["random.random(", "random.randint(", "random.choice("]
73
+ why_bad: Mersenne Twister output is predictable from a few samples
74
+
75
+ trigger_signals:
76
+ - imports of hashlib, Crypto, cryptography, or ssl
77
+ - encrypt, decrypt, sign, or token generation
78
+ - key, iv, nonce, or salt literals
@@ -0,0 +1,57 @@
1
+ id: data_protection
2
+ name: Data Protection
3
+ asvs_chapter: V8
4
+ description: Protecting sensitive data in transit, at rest, and in use, including PII handling.
5
+
6
+ sub_capabilities:
7
+ in_transit:
8
+ correct_patterns:
9
+ - id: TLS-OK-1
10
+ description: Send sensitive data only over TLS and verify the peer certificate
11
+ signals: ["https://", "verify=True", "ssl.create_default_context"]
12
+ why_ok: Confidentiality and integrity on the wire, with a checked endpoint identity
13
+
14
+ anti_patterns:
15
+ - id: TLS-BAD-1
16
+ cwe: CWE-319
17
+ severity: HIGH
18
+ description: Transmit sensitive data over plaintext HTTP
19
+ signals: ["http://"]
20
+ why_bad: Anyone on the path can read or alter the data
21
+
22
+ - id: TLS-BAD-2
23
+ cwe: CWE-295
24
+ severity: HIGH
25
+ description: Disable TLS certificate verification
26
+ signals: ["verify=False", "ssl._create_unverified_context", "CERT_NONE"]
27
+ why_bad: Removes protection against man-in-the-middle interception
28
+ example_bad: |
29
+ requests.get(url, verify=False)
30
+ example_good: |
31
+ requests.get(url) # verification on by default
32
+
33
+ at_rest:
34
+ correct_patterns:
35
+ - id: REST-OK-1
36
+ description: Encrypt sensitive fields and backups at rest
37
+ why_ok: A storage or backup leak does not expose plaintext
38
+
39
+ anti_patterns:
40
+ - id: REST-BAD-1
41
+ cwe: CWE-311
42
+ severity: MEDIUM
43
+ description: Store sensitive data (PII, tokens, financial) unencrypted
44
+ why_bad: Any database or disk access reveals it directly
45
+
46
+ pii:
47
+ anti_patterns:
48
+ - id: PII-BAD-1
49
+ cwe: CWE-359
50
+ severity: MEDIUM
51
+ description: Expose PII in URLs, responses, or logs beyond what the operation needs
52
+ why_bad: PII spreads into caches, history, and logs, widening breach impact
53
+
54
+ trigger_signals:
55
+ - outbound requests or URLs carrying user data
56
+ - models or tables with PII, token, or financial fields
57
+ - TLS or certificate verification configuration
@@ -0,0 +1,52 @@
1
+ id: dependency_config
2
+ name: Dependencies and Configuration
3
+ asvs_chapter: V14
4
+ description: Software supply chain and deployment configuration -- known-vulnerable components and unsafe defaults.
5
+
6
+ sub_capabilities:
7
+ dependencies:
8
+ correct_patterns:
9
+ - id: DEP-OK-1
10
+ description: Pin dependency versions and scan them for known vulnerabilities
11
+ signals: ["==", "requirements.txt", "poetry.lock", "pip-audit"]
12
+ why_ok: Builds are reproducible and known-vulnerable versions are caught
13
+
14
+ anti_patterns:
15
+ - id: DEP-BAD-1
16
+ cwe: CWE-1104
17
+ severity: MEDIUM
18
+ description: Depend on unmaintained or known-vulnerable components
19
+ why_bad: Public CVEs in shipped dependencies are directly exploitable
20
+
21
+ - id: DEP-BAD-2
22
+ cwe: CWE-494
23
+ severity: MEDIUM
24
+ description: Download or install code at runtime without an integrity or signature check
25
+ signals: ["curl | sh", "pip install http", "urlretrieve"]
26
+ why_bad: A tampered or hijacked source injects code into the build or host
27
+
28
+ configuration:
29
+ correct_patterns:
30
+ - id: CFG-OK-1
31
+ description: Ship secure defaults with least privilege and a minimal exposed surface
32
+ why_ok: Misconfiguration is the default-off state, not something to remember
33
+
34
+ anti_patterns:
35
+ - id: CFG-BAD-1
36
+ cwe: CWE-732
37
+ severity: MEDIUM
38
+ description: Grant overly permissive permissions (world-writable files, public storage buckets)
39
+ signals: ["chmod 0777", "0o777", "ACL: public-read", "AllUsers"]
40
+ why_bad: Anyone can read or modify resources that should be restricted
41
+
42
+ - id: CFG-BAD-2
43
+ cwe: CWE-1392
44
+ severity: HIGH
45
+ description: Ship default or sample credentials
46
+ signals: ["admin:admin", "password=admin", "changeme"]
47
+ why_bad: Default credentials are public knowledge and trivially abused
48
+
49
+ trigger_signals:
50
+ - dependency manifests and lock files
51
+ - install or bootstrap scripts fetching remote code
52
+ - file permission, bucket ACL, or default credential settings
@@ -0,0 +1,49 @@
1
+ id: error_logging
2
+ name: Error Handling and Logging
3
+ asvs_chapter: V7
4
+ description: Failing without leaking information, and recording enough to investigate incidents.
5
+
6
+ sub_capabilities:
7
+ information_leakage:
8
+ correct_patterns:
9
+ - id: ERR-OK-1
10
+ description: Return a generic error to the client and log the detail server-side
11
+ signals: ["except Exception", "return 500", "abort(500)"]
12
+ why_ok: The caller learns nothing exploitable while operators keep the detail
13
+
14
+ anti_patterns:
15
+ - id: ERR-BAD-1
16
+ cwe: CWE-209
17
+ severity: MEDIUM
18
+ description: Return a stack trace or exception detail to the client
19
+ signals: ["traceback.format_exc()", "str(e)", "return jsonify(error=str"]
20
+ why_bad: Internal paths, queries, and versions help an attacker map the system
21
+ example_bad: |
22
+ return jsonify(error=traceback.format_exc()), 500
23
+ example_good: |
24
+ app.logger.exception("checkout failed"); return jsonify(error="internal error"), 500
25
+
26
+ - id: ERR-BAD-2
27
+ cwe: CWE-489
28
+ severity: LOW
29
+ description: Leave a debug feature or verbose mode enabled in production
30
+ signals: ["DEBUG = True", "app.run(debug=True", "FLASK_DEBUG=1"]
31
+ why_bad: Debug pages expose internals and sometimes allow code execution
32
+
33
+ audit_trail:
34
+ correct_patterns:
35
+ - id: AUDIT-OK-1
36
+ description: Log security-relevant events (auth attempts, access denials, admin actions) with timestamp and actor
37
+ why_ok: Gives a forensic trail to detect and reconstruct abuse
38
+
39
+ anti_patterns:
40
+ - id: AUDIT-BAD-1
41
+ cwe: CWE-778
42
+ severity: MEDIUM
43
+ description: Do not log security-relevant events
44
+ why_bad: Without an audit trail, intrusions go unnoticed and uninvestigable
45
+
46
+ trigger_signals:
47
+ - exception handlers and error responses
48
+ - logging configuration and DEBUG flags
49
+ - authentication, authorization, or admin actions