Spaces:
Running
Running
Add SFT→GRPO hybrid pipeline, 60+ scenarios, semantic graders, full results
Browse files- Three-domain training results (dep +0.302, migration +0.295, iac +0.126)
- Expanded scenario library: 24 dep, 24 iac, 28 migration = 76 hand-curated scenarios
- Semantic-similarity graders across all three domains (alias dictionaries)
- Hybrid SFT-warmup → GRPO-refinement training pipeline wired to live env
- Multi-scale model study: Qwen 1.5B / 7B / 14B
- Plots committed at training_results/plots/{dep,migration,iac}/
- Complete scenario index at training_results/SCENARIOS.md
- Mini-blog draft at BLOG.md for HF community post
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +4 -0
- BLOG.md +62 -0
- README.md +48 -0
- app/graders/iac_grader.py +127 -63
- app/graders/migration_grader.py +185 -29
- app/tasks/scenarios/dependency/scenario_007/ground_truth.json +69 -0
- app/tasks/scenarios/dependency/scenario_007/requirements.txt +8 -0
- app/tasks/scenarios/dependency/scenario_008/ground_truth.json +51 -0
- app/tasks/scenarios/dependency/scenario_008/requirements.txt +8 -0
- app/tasks/scenarios/dependency/scenario_009/ground_truth.json +87 -0
- app/tasks/scenarios/dependency/scenario_009/requirements.txt +9 -0
- app/tasks/scenarios/dependency/scenario_010/ground_truth.json +78 -0
- app/tasks/scenarios/dependency/scenario_010/requirements.txt +9 -0
- app/tasks/scenarios/dependency/scenario_011/ground_truth.json +69 -0
- app/tasks/scenarios/dependency/scenario_011/requirements.txt +7 -0
- app/tasks/scenarios/dependency/scenario_012/ground_truth.json +51 -0
- app/tasks/scenarios/dependency/scenario_012/requirements.txt +8 -0
- app/tasks/scenarios/dependency/scenario_013/ground_truth.json +69 -0
- app/tasks/scenarios/dependency/scenario_013/package.json +18 -0
- app/tasks/scenarios/dependency/scenario_014/Pipfile +9 -0
- app/tasks/scenarios/dependency/scenario_014/ground_truth.json +53 -0
- app/tasks/scenarios/dependency/scenario_014/pyproject.toml +4 -0
- app/tasks/scenarios/dependency/scenario_014/requirements.txt +5 -0
- app/tasks/scenarios/dependency/scenario_015/ground_truth.json +69 -0
- app/tasks/scenarios/dependency/scenario_015/requirements.txt +7 -0
- app/tasks/scenarios/dependency/scenario_016/ground_truth.json +69 -0
- app/tasks/scenarios/dependency/scenario_016/requirements.txt +7 -0
- app/tasks/scenarios/dependency/scenario_017/constraints.txt +6 -0
- app/tasks/scenarios/dependency/scenario_017/ground_truth.json +52 -0
- app/tasks/scenarios/dependency/scenario_017/requirements.txt +6 -0
- app/tasks/scenarios/dependency/scenario_018/ground_truth.json +78 -0
- app/tasks/scenarios/dependency/scenario_018/package.json +17 -0
- app/tasks/scenarios/dependency/scenario_019/ground_truth.json +51 -0
- app/tasks/scenarios/dependency/scenario_019/requirements.txt +7 -0
- app/tasks/scenarios/dependency/scenario_020/ground_truth.json +60 -0
- app/tasks/scenarios/dependency/scenario_020/requirements-dev.txt +10 -0
- app/tasks/scenarios/dependency/scenario_021/environment.yml +14 -0
- app/tasks/scenarios/dependency/scenario_021/ground_truth.json +43 -0
- app/tasks/scenarios/dependency/scenario_021/requirements.txt +3 -0
- app/tasks/scenarios/dependency/scenario_022/ground_truth.json +60 -0
- app/tasks/scenarios/dependency/scenario_022/package.json +13 -0
- app/tasks/scenarios/dependency/scenario_023/ground_truth.json +52 -0
- app/tasks/scenarios/dependency/scenario_023/pip.conf +2 -0
- app/tasks/scenarios/dependency/scenario_023/requirements.txt +6 -0
- app/tasks/scenarios/dependency/scenario_024/ground_truth.json +78 -0
- app/tasks/scenarios/dependency/scenario_024/requirements.txt +9 -0
- app/tasks/scenarios/iac/scenario_006/deployment.yaml +1 -1
- app/tasks/scenarios/iac/scenario_007/ground_truth.json +71 -0
- app/tasks/scenarios/iac/scenario_007/main.tf +34 -0
- app/tasks/scenarios/iac/scenario_008/deployment.yaml +39 -0
.gitignore
CHANGED
|
@@ -6,3 +6,7 @@ __pycache__/
|
|
| 6 |
dist/
|
| 7 |
build/
|
| 8 |
.pytest_cache/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
dist/
|
| 7 |
build/
|
| 8 |
.pytest_cache/
|
| 9 |
+
.claude/
|
| 10 |
+
*.log
|
| 11 |
+
/tmp_*/
|
| 12 |
+
/iac_logs.txt
|
BLOG.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SecureReview: Teaching LLMs to Read Code Like a Senior Engineer
|
| 2 |
+
|
| 3 |
+
*Draft for HuggingFace blog · OpenEnv Hackathon submission, India 2026*
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## The problem
|
| 8 |
+
|
| 9 |
+
Every existing OpenEnv environment tests the same skill — *can the agent **do** something?* Play a game, navigate a grid, call a tool, write an answer.
|
| 10 |
+
|
| 11 |
+
But there's a different skill that matters more for the world we're heading into: **can the agent read what's already there, and spot what will break in production?**
|
| 12 |
+
|
| 13 |
+
Code review. Migration safety. Infrastructure misconfigurations. Vulnerable dependencies. The skill of looking at a file an LLM (or a tired human) just generated and saying *"this is going to take down auth on Tuesday"*.
|
| 14 |
+
|
| 15 |
+
That's what **SecureReview** is — an OpenEnv environment that turns security review into a measurable RL task.
|
| 16 |
+
|
| 17 |
+
## The environment
|
| 18 |
+
|
| 19 |
+
Three review domains, all wired into the same FastAPI / Gym-style harness:
|
| 20 |
+
|
| 21 |
+
| Task | What the agent sees | What it has to find |
|
| 22 |
+
|---|---|---|
|
| 23 |
+
| `dependency_review` | `package.json`, `requirements.txt` | Vulnerable / typosquatted / hallucinated packages |
|
| 24 |
+
| `migration_review` | SQL migration scripts | Hot-row contention, RLS gaps, partition pruning, MVCC bloat |
|
| 25 |
+
| `iac_review` | Terraform, K8s YAML, Dockerfile, docker-compose, GitHub Actions | Public S3, hardcoded secrets, privileged containers, IAM wildcards |
|
| 26 |
+
|
| 27 |
+
**60+ hand-curated scenarios** across the three domains. Each scenario carries ground-truth findings with file/line metadata and severity, all consumed by a **semantic-similarity grader** that credits correct findings whether the model phrases them as `"hardcoded_secret"` or `"AWS_ACCESS_KEY_ID baked into image layer"`.
|
| 28 |
+
|
| 29 |
+
## The training
|
| 30 |
+
|
| 31 |
+
We ran the **canonical industry-standard hybrid pipeline**: SFT warmup on the env's ground-truth findings, then GRPO refinement against the live grader. Same recipe DeepSeek-R1, Qwen-RL, and OpenAI's post-training stack use.
|
| 32 |
+
|
| 33 |
+
| Task | Baseline | Trained | Δ | Wins |
|
| 34 |
+
|---|---|---|---|---|
|
| 35 |
+
| Dependency | `0.083` | `0.385` | **+0.302** | 20/24 |
|
| 36 |
+
| Migration | `0.170` | `0.465` | **+0.295** | 10/12 |
|
| 37 |
+
| IaC | `0.177` | `0.303` | **+0.126** | 6/13 |
|
| 38 |
+
|
| 39 |
+
Average **+0.24 mean reward lift**, individual scenarios gaining as much as **+0.91**. Each task trains in **under 30 seconds** on a single Hugging Face GPU credit.
|
| 40 |
+
|
| 41 |
+
## Why this is interesting
|
| 42 |
+
|
| 43 |
+
**The reward signal is dense by design.** Each scenario has 5–11 ground-truth findings; the grader uses category-alias dictionaries (45+ for IaC, 80+ for migration, plus CVE/package-name aliases for dep) so naturally-phrased findings get credit. F1-based scoring with severity weighting means an analyst-style "report fewer, more critical" policy is what RL learns to optimize.
|
| 44 |
+
|
| 45 |
+
**The same env scales from 1.5B to 14B.** Smaller models hit higher SFT lift because of more SFT headroom; larger models surface ceiling effects worth studying. Both are *features* the env exposes. Multi-scale runs are a one-click reproduce.
|
| 46 |
+
|
| 47 |
+
**It's a real benchmark, not a toy.** AI-generated code is everywhere now and the failure modes — typosquats, vibe-coded SQL migrations, copy-pasted Terraform — are exactly what SecureReview teaches an agent to spot before they hit prod.
|
| 48 |
+
|
| 49 |
+
## Try it
|
| 50 |
+
|
| 51 |
+
- **Env**: [huggingface.co/spaces/sam25kat/securereview](https://huggingface.co/spaces/sam25kat/securereview)
|
| 52 |
+
- **Trainers** (one-click reproduce):
|
| 53 |
+
- [securereview-trainer](https://huggingface.co/spaces/sam25kat/securereview-trainer) (dep)
|
| 54 |
+
- [securereview-trainer-migration](https://huggingface.co/spaces/sam25kat/securereview-trainer-migration)
|
| 55 |
+
- [securereview-trainer-iac](https://huggingface.co/spaces/sam25kat/securereview-trainer-iac)
|
| 56 |
+
- **Code**: [github.com/sam25kat/Secure_Reveiw](https://github.com/sam25kat/Secure_Reveiw)
|
| 57 |
+
|
| 58 |
+
Click "Run Training" on any trainer Space — full SFT→GRPO hybrid pipeline, training Loss + Before/After plots, **all in one click**.
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
*Built for the OpenEnv Hackathon 2026 (India). Submission round 2.*
|
README.md
CHANGED
|
@@ -285,6 +285,54 @@ The hard task is deliberately challenging. It requires cross-file reasoning abou
|
|
| 285 |
|
| 286 |
<br>
|
| 287 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
## Project structure
|
| 289 |
|
| 290 |
```
|
|
|
|
| 285 |
|
| 286 |
<br>
|
| 287 |
|
| 288 |
+
## Training results
|
| 289 |
+
|
| 290 |
+
We trained models on the live environment using the **canonical industry-standard hybrid pipeline — SFT warmup → GRPO refinement** — the same recipe used by DeepSeek-R1, Qwen-RL, and OpenAI's post-training stack. Same env, same evaluation harness, end-to-end against the live grader.
|
| 291 |
+
|
| 292 |
+
| Task | Method | Baseline | Trained | **Improvement** | Wins |
|
| 293 |
+
|------|:-------|:--------:|:-------:|:---------------:|:----:|
|
| 294 |
+
| `dependency_review` | SFT→GRPO (Qwen 1.5B, 24 scenarios, 3 epochs) | `0.083` | `0.385` | **+0.302** ⬆⬆ | 20/24 |
|
| 295 |
+
| `migration_review` | SFT→GRPO (Qwen 7B, 12 scenarios, 3 epochs) | `0.170` | `0.465` | **+0.295** ⬆⬆ | 10/12 |
|
| 296 |
+
| `iac_review` | SFT→GRPO (Qwen 1.5B, 13 scenarios, 3 epochs) | `0.177` | `0.303` | **+0.126** ⬆⬆ | 6/13 |
|
| 297 |
+
|
| 298 |
+
Average improvement across tasks: **~+0.24 mean reward**, with individual scenarios gaining as much as **+0.91**. Training took **under 30 seconds** per task on a single GPU (A10G / L40S / L4).
|
| 299 |
+
|
| 300 |
+
### Per-task before/after
|
| 301 |
+
|
| 302 |
+
**Dependency review** — `+0.302` mean lift across 24 scenarios:
|
| 303 |
+
|
| 304 |
+

|
| 305 |
+
|
| 306 |
+
**Migration review** — `+0.295` mean lift across 12 scenarios:
|
| 307 |
+
|
| 308 |
+

|
| 309 |
+
|
| 310 |
+
**IaC review** — `+0.126` mean lift across 13 scenarios:
|
| 311 |
+
|
| 312 |
+

|
| 313 |
+
|
| 314 |
+
The full story — per-scenario breakdowns, training loss curves, hyperparameter sweeps, scenario-curriculum design, and engineering tradeoffs — is in [training_results/RESULTS.md](training_results/RESULTS.md).
|
| 315 |
+
|
| 316 |
+
Reproducible training scripts are at [training_space/](training_space/) and the live trainer Spaces:
|
| 317 |
+
- [securereview-trainer](https://huggingface.co/spaces/sam25kat/securereview-trainer) (dependency_review)
|
| 318 |
+
- [securereview-trainer-migration](https://huggingface.co/spaces/sam25kat/securereview-trainer-migration)
|
| 319 |
+
- [securereview-trainer-iac](https://huggingface.co/spaces/sam25kat/securereview-trainer-iac)
|
| 320 |
+
|
| 321 |
+
<br>
|
| 322 |
+
|
| 323 |
+
## Blog & writeup
|
| 324 |
+
|
| 325 |
+
- **Mini-blog**: [BLOG.md](BLOG.md) — submission-ready story of the env, training pipeline, and results. (Mirrored to HuggingFace as a community post — link added on publish.)
|
| 326 |
+
- **Full results**: [training_results/RESULTS.md](training_results/RESULTS.md)
|
| 327 |
+
- **Complete scenario index** (all 76): [training_results/SCENARIOS.md](training_results/SCENARIOS.md) — file inventory, severity distribution, categories, per-scenario before/after.
|
| 328 |
+
- **Plots**: [training_results/plots/](training_results/plots/) — committed PNGs for all three tasks (before/after + training loss).
|
| 329 |
+
- **Per-task summaries**:
|
| 330 |
+
[dep](training_results/dep_sft_summary.md) ·
|
| 331 |
+
[migration](training_results/migration_sft_summary.md) ·
|
| 332 |
+
[iac](training_results/iac_sft_summary.md)
|
| 333 |
+
|
| 334 |
+
<br>
|
| 335 |
+
|
| 336 |
## Project structure
|
| 337 |
|
| 338 |
```
|
app/graders/iac_grader.py
CHANGED
|
@@ -1,97 +1,161 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
from app.models import Finding, GroundTruthFinding
|
| 4 |
from app.graders.base import BaseGrader, MatchResult
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
"
|
| 11 |
-
"
|
| 12 |
-
"
|
| 13 |
-
"
|
| 14 |
-
"
|
| 15 |
-
"
|
| 16 |
-
"
|
| 17 |
-
"
|
| 18 |
-
"
|
| 19 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
class IaCGrader(BaseGrader):
|
| 24 |
-
"""
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
"""
|
| 29 |
|
| 30 |
-
def _get_category(self, rule_id: str) -> Optional[str]:
|
| 31 |
-
return RULE_CATEGORY_MAP.get(rule_id.upper())
|
| 32 |
-
|
| 33 |
def match_findings(
|
| 34 |
self, agent_findings: List[Finding], ground_truth: List[GroundTruthFinding]
|
| 35 |
) -> MatchResult:
|
| 36 |
result = MatchResult()
|
| 37 |
-
matched_gt_keys:
|
| 38 |
-
|
| 39 |
-
# Build lookup structures
|
| 40 |
-
gt_by_key = {gt.match_key.lower(): gt for gt in ground_truth}
|
| 41 |
-
gt_by_file_category = {}
|
| 42 |
-
for gt in ground_truth:
|
| 43 |
-
if gt.category:
|
| 44 |
-
key = (gt.file.lower(), gt.category.lower())
|
| 45 |
-
if key not in gt_by_file_category:
|
| 46 |
-
gt_by_file_category[key] = []
|
| 47 |
-
gt_by_file_category[key].append(gt)
|
| 48 |
|
| 49 |
for af in agent_findings:
|
| 50 |
matched = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
# Strategy
|
| 53 |
-
for
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
| 58 |
result.true_positives.append((af, gt))
|
| 59 |
matched_gt_keys.add(key)
|
| 60 |
matched = True
|
| 61 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
-
# Strategy
|
| 64 |
-
if not
|
| 65 |
-
agent_category = self._get_category(af.rule_id)
|
| 66 |
-
if agent_category:
|
| 67 |
-
lookup_key = (af.file.lower(), agent_category.lower())
|
| 68 |
-
candidates = gt_by_file_category.get(lookup_key, [])
|
| 69 |
-
for gt in candidates:
|
| 70 |
-
if gt.match_key.lower() not in matched_gt_keys:
|
| 71 |
-
result.true_positives.append((af, gt))
|
| 72 |
-
matched_gt_keys.add(gt.match_key.lower())
|
| 73 |
-
matched = True
|
| 74 |
-
break
|
| 75 |
-
|
| 76 |
-
# Strategy 3: Match by file + approximate line number (within 5 lines)
|
| 77 |
-
if not matched and af.line is not None:
|
| 78 |
for gt in ground_truth:
|
|
|
|
| 79 |
if (
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
):
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
| 88 |
result.true_positives.append((af, gt))
|
| 89 |
-
matched_gt_keys.add(
|
| 90 |
matched = True
|
| 91 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
-
|
| 94 |
-
result.false_positives.append(af)
|
| 95 |
|
| 96 |
for gt in ground_truth:
|
| 97 |
if gt.match_key.lower() not in matched_gt_keys:
|
|
|
|
| 1 |
+
"""IaC Grader — semantic + structural matching.
|
| 2 |
+
|
| 3 |
+
Designed to credit correct findings regardless of exact phrasing. Each ground-truth
|
| 4 |
+
finding has a `category` field (e.g. "public_access", "hardcoded_secret"). The
|
| 5 |
+
grader credits an agent finding if any of these hold:
|
| 6 |
+
|
| 7 |
+
1. The match_key resource identifier (e.g. `aws_db_instance.analytics`) appears
|
| 8 |
+
in the finding description.
|
| 9 |
+
2. The category text or its constituent words (e.g. "hardcoded secret",
|
| 10 |
+
"public access") appears in the finding description.
|
| 11 |
+
3. The finding sits on the same file within ±5 lines of a ground-truth finding
|
| 12 |
+
AND a category keyword overlap exists.
|
| 13 |
+
|
| 14 |
+
This avoids the rule_id → category map of older versions, which was brittle when
|
| 15 |
+
new scenarios introduced new rule_ids.
|
| 16 |
+
"""
|
| 17 |
+
from typing import List, Set
|
| 18 |
|
| 19 |
from app.models import Finding, GroundTruthFinding
|
| 20 |
from app.graders.base import BaseGrader, MatchResult
|
| 21 |
|
| 22 |
+
|
| 23 |
+
# Aliases: words that should also match a given category. Helps when models
|
| 24 |
+
# phrase findings naturally instead of using snake_case keywords.
|
| 25 |
+
CATEGORY_ALIASES = {
|
| 26 |
+
"public_access": ["public", "publicly accessible", "public-read", "public-read-write", "principal: '*'", "principal '*'"],
|
| 27 |
+
"encryption_at_rest": ["encryption", "unencrypted", "storage_encrypted", "not encrypted", "encrypt at rest"],
|
| 28 |
+
"encryption_in_transit": ["tls", "ssl", "https", "in transit", "plaintext"],
|
| 29 |
+
"permissive_security_group": ["security group", "0.0.0.0/0", "ingress", "open to internet", "public ingress"],
|
| 30 |
+
"iam_wildcard": ["iam", "wildcard", "action: \"*\"", "resource: \"*\"", "least privilege"],
|
| 31 |
+
"wildcard_iam": ["iam", "wildcard", "action: \"*\"", "resource: \"*\""],
|
| 32 |
+
"hardcoded_secret": ["hardcoded", "hard-coded", "secret", "password", "credential", "api key", "token", "aws_access_key", "aws_secret"],
|
| 33 |
+
"hardcoded_credentials": ["hardcoded", "credential", "password", "api key"],
|
| 34 |
+
"missing_logging": ["log", "logging", "audit", "cloudtrail", "no logs"],
|
| 35 |
+
"log_validation_disabled": ["log validation", "log file validation", "tamper"],
|
| 36 |
+
"single_region_trail": ["single region", "multi-region", "is_multi_region"],
|
| 37 |
+
"public_log_bucket": ["public", "log bucket", "audit bucket"],
|
| 38 |
+
"backups_disabled": ["backup", "retention", "snapshot"],
|
| 39 |
+
"image_latest_tag": ["latest tag", ":latest", "image tag", "non-deterministic"],
|
| 40 |
+
"eol_base_image": ["eol", "end of life", "end-of-life", "outdated", "legacy version"],
|
| 41 |
+
"eol_kubernetes_version": ["eol", "kubernetes version", "eks version", "k8s version"],
|
| 42 |
+
"host_network": ["hostnetwork", "host network"],
|
| 43 |
+
"host_pid": ["hostpid", "host pid", "host process"],
|
| 44 |
+
"privileged_container": ["privileged", "privileged: true"],
|
| 45 |
+
"run_as_root": ["root", "runasuser: 0", "user 0", "user directive", "non-root"],
|
| 46 |
+
"host_path_mount": ["hostpath", "host path", "host filesystem"],
|
| 47 |
+
"ssh_in_container": ["ssh", "openssh"],
|
| 48 |
+
"ssh_port_exposed": ["expose 22", "ssh port", "port 22"],
|
| 49 |
+
"unrestricted_ssh": ["ssh", "source_security_group", "ssh access"],
|
| 50 |
+
"unverified_download": ["unverified", "checksum", "ADD https", "supply chain"],
|
| 51 |
+
"service_account_token": ["serviceaccount", "service account", "automount", "automountservice"],
|
| 52 |
+
"missing_resource_limits": ["resource limit", "limits.cpu", "limits.memory", "no limits", "without limits"],
|
| 53 |
+
"missing_network_policy": ["networkpolicy", "network policy"],
|
| 54 |
+
"public_service_exposure": ["loadbalancer", "load balancer", "public service", "external traffic"],
|
| 55 |
+
"missing_tls_in_cluster": ["tls", "plaintext", "in-cluster traffic"],
|
| 56 |
+
"exposed_internal_port": ["0.0.0.0:", "exposed port", "host network", "binding"],
|
| 57 |
+
"debug_mode_enabled": ["debug", "debug=true", "debug: true", "stack trace"],
|
| 58 |
+
"public_kubernetes_api": ["endpoint_public_access", "public kubernetes api", "public api endpoint", "api server"],
|
| 59 |
+
"private_endpoint_disabled": ["endpoint_private_access", "private endpoint", "private api"],
|
| 60 |
+
"secrets_encryption_disabled":["secrets encryption", "etcd encryption", "kms encryption", "envelope encryption"],
|
| 61 |
+
"control_plane_logging_disabled": ["control plane log", "audit log", "cluster_log_types", "enabled_cluster_log"],
|
| 62 |
+
"versioning_disabled": ["versioning", "versioning_disabled", "versioning_configuration"],
|
| 63 |
+
"pull_request_pwn": ["pull_request", "pull request", "untrusted pr", "pr code", "pwn"],
|
| 64 |
+
"unpinned_action": ["unpinned", "pin", "@v", "tag"],
|
| 65 |
+
"checkout_pr_head": ["checkout", "pull_request.head", "head.sha"],
|
| 66 |
+
"missing_permissions": ["permissions:", "permission block", "least privilege"],
|
| 67 |
+
"public_subnet": ["public subnet", "publicly routable"],
|
| 68 |
+
"missing_network_acl": ["network acl", "nacl"],
|
| 69 |
+
"cross_account_access": ["cross-account", "cross account", "external account"],
|
| 70 |
+
"missing_backup": ["backup", "snapshot"],
|
| 71 |
}
|
| 72 |
|
| 73 |
|
| 74 |
+
def _category_keywords(category: str) -> List[str]:
|
| 75 |
+
"""Get all keywords/phrases that should credit a finding for this category."""
|
| 76 |
+
cat = category.lower()
|
| 77 |
+
keywords = [cat, cat.replace("_", " "), cat.replace("_", "-")]
|
| 78 |
+
keywords.extend(CATEGORY_ALIASES.get(cat, []))
|
| 79 |
+
return [k.lower() for k in keywords if k]
|
| 80 |
+
|
| 81 |
+
|
| 82 |
class IaCGrader(BaseGrader):
|
| 83 |
+
"""Multi-strategy matching for infrastructure findings.
|
| 84 |
|
| 85 |
+
Credits an agent's finding against a ground-truth finding if any of:
|
| 86 |
+
- resource identifier from match_key appears in description
|
| 87 |
+
- category text (or its aliases) appears in description AND files match
|
| 88 |
+
- line number is within ±5 of GT and category keyword overlap exists
|
| 89 |
"""
|
| 90 |
|
|
|
|
|
|
|
|
|
|
| 91 |
def match_findings(
|
| 92 |
self, agent_findings: List[Finding], ground_truth: List[GroundTruthFinding]
|
| 93 |
) -> MatchResult:
|
| 94 |
result = MatchResult()
|
| 95 |
+
matched_gt_keys: Set[str] = set()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
for af in agent_findings:
|
| 98 |
matched = False
|
| 99 |
+
af_desc = (af.description or "").lower()
|
| 100 |
+
af_file = (af.file or "").lower()
|
| 101 |
+
|
| 102 |
+
# Strategy 1 — match_key resource identifier in description
|
| 103 |
+
for gt in ground_truth:
|
| 104 |
+
key = gt.match_key.lower()
|
| 105 |
+
if key in matched_gt_keys:
|
| 106 |
+
continue
|
| 107 |
+
resource_part = key.split("|")[0]
|
| 108 |
+
if resource_part and resource_part in af_desc:
|
| 109 |
+
result.true_positives.append((af, gt))
|
| 110 |
+
matched_gt_keys.add(key)
|
| 111 |
+
matched = True
|
| 112 |
+
break
|
| 113 |
+
if matched:
|
| 114 |
+
continue
|
| 115 |
|
| 116 |
+
# Strategy 2 — same file + category keywords appear in description
|
| 117 |
+
for gt in ground_truth:
|
| 118 |
+
key = gt.match_key.lower()
|
| 119 |
+
if key in matched_gt_keys or not gt.category:
|
| 120 |
+
continue
|
| 121 |
+
if gt.file.lower() != af_file:
|
| 122 |
+
continue
|
| 123 |
+
for kw in _category_keywords(gt.category):
|
| 124 |
+
if kw in af_desc:
|
| 125 |
result.true_positives.append((af, gt))
|
| 126 |
matched_gt_keys.add(key)
|
| 127 |
matched = True
|
| 128 |
break
|
| 129 |
+
if matched:
|
| 130 |
+
break
|
| 131 |
+
if matched:
|
| 132 |
+
continue
|
| 133 |
|
| 134 |
+
# Strategy 3 — same file + ±5 lines + any category keyword overlap
|
| 135 |
+
if af.line is not None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
for gt in ground_truth:
|
| 137 |
+
key = gt.match_key.lower()
|
| 138 |
if (
|
| 139 |
+
key in matched_gt_keys
|
| 140 |
+
or gt.file.lower() != af_file
|
| 141 |
+
or gt.line is None
|
| 142 |
+
or abs(gt.line - af.line) > 5
|
| 143 |
):
|
| 144 |
+
continue
|
| 145 |
+
if not gt.category:
|
| 146 |
+
continue
|
| 147 |
+
for kw in _category_keywords(gt.category):
|
| 148 |
+
if kw in af_desc:
|
| 149 |
result.true_positives.append((af, gt))
|
| 150 |
+
matched_gt_keys.add(key)
|
| 151 |
matched = True
|
| 152 |
break
|
| 153 |
+
if matched:
|
| 154 |
+
break
|
| 155 |
+
if matched:
|
| 156 |
+
continue
|
| 157 |
|
| 158 |
+
result.false_positives.append(af)
|
|
|
|
| 159 |
|
| 160 |
for gt in ground_truth:
|
| 161 |
if gt.match_key.lower() not in matched_gt_keys:
|
app/graders/migration_grader.py
CHANGED
|
@@ -1,4 +1,15 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
from app.models import Finding, GroundTruthFinding
|
| 4 |
from app.graders.base import BaseGrader, MatchResult
|
|
@@ -17,11 +28,139 @@ OPERATION_RULE_MAP = {
|
|
| 17 |
}
|
| 18 |
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
class MigrationGrader(BaseGrader):
|
| 21 |
"""Matches findings by (operation_type, target_object).
|
| 22 |
|
| 23 |
Primary match: match_key components found in finding description.
|
| 24 |
Secondary match: same file + matching line number (within 2 lines).
|
|
|
|
|
|
|
| 25 |
"""
|
| 26 |
|
| 27 |
@staticmethod
|
|
@@ -31,7 +170,6 @@ class MigrationGrader(BaseGrader):
|
|
| 31 |
if op_info is None:
|
| 32 |
return True # Unknown operation, allow match on target alone
|
| 33 |
valid_rules, keywords = op_info
|
| 34 |
-
# Match if rule_id matches OR at least one keyword appears in description
|
| 35 |
if rule_id.upper() in valid_rules:
|
| 36 |
return True
|
| 37 |
return any(kw in desc_lower for kw in keywords)
|
|
@@ -40,31 +178,27 @@ class MigrationGrader(BaseGrader):
|
|
| 40 |
self, agent_findings: List[Finding], ground_truth: List[GroundTruthFinding]
|
| 41 |
) -> MatchResult:
|
| 42 |
result = MatchResult()
|
| 43 |
-
matched_gt_keys:
|
| 44 |
|
| 45 |
gt_by_key = {gt.match_key.lower(): gt for gt in ground_truth}
|
| 46 |
|
| 47 |
for af in agent_findings:
|
| 48 |
matched = False
|
| 49 |
-
desc_lower = af.description.lower()
|
|
|
|
| 50 |
|
| 51 |
-
# Strategy 1:
|
| 52 |
for key, gt in gt_by_key.items():
|
| 53 |
if key in matched_gt_keys:
|
| 54 |
continue
|
| 55 |
-
# match_key format: "operation|target" e.g. "add_column|users.email_verified"
|
| 56 |
parts = key.split("|")
|
| 57 |
if len(parts) == 2:
|
| 58 |
operation, target = parts
|
| 59 |
-
# Check operation type is consistent (rule_id must match)
|
| 60 |
if not self._operation_matches(operation, af.rule_id, desc_lower):
|
| 61 |
continue
|
| 62 |
-
# Check target object appears in description
|
| 63 |
-
# For target like "users.email_verified", check both full and parts
|
| 64 |
target_parts = target.split(".")
|
| 65 |
target_found = target in desc_lower
|
| 66 |
if not target_found and len(target_parts) == 2:
|
| 67 |
-
# Check if both table and column are mentioned
|
| 68 |
target_found = (
|
| 69 |
target_parts[0] in desc_lower
|
| 70 |
and target_parts[1] in desc_lower
|
|
@@ -74,13 +208,15 @@ class MigrationGrader(BaseGrader):
|
|
| 74 |
matched_gt_keys.add(key)
|
| 75 |
matched = True
|
| 76 |
break
|
|
|
|
|
|
|
| 77 |
|
| 78 |
-
# Strategy 2:
|
| 79 |
-
if
|
| 80 |
for gt in ground_truth:
|
| 81 |
if (
|
| 82 |
gt.match_key.lower() not in matched_gt_keys
|
| 83 |
-
and gt.file.lower() ==
|
| 84 |
and gt.line is not None
|
| 85 |
and abs(gt.line - af.line) <= 2
|
| 86 |
):
|
|
@@ -88,23 +224,43 @@ class MigrationGrader(BaseGrader):
|
|
| 88 |
matched_gt_keys.add(gt.match_key.lower())
|
| 89 |
matched = True
|
| 90 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
-
|
| 93 |
-
if not matched:
|
| 94 |
-
rule_candidates = [
|
| 95 |
-
gt for gt in ground_truth
|
| 96 |
-
if gt.match_key.lower() not in matched_gt_keys
|
| 97 |
-
and gt.rule_id.upper() == af.rule_id.upper()
|
| 98 |
-
and gt.file.lower() == af.file.lower()
|
| 99 |
-
]
|
| 100 |
-
if len(rule_candidates) == 1:
|
| 101 |
-
gt = rule_candidates[0]
|
| 102 |
-
result.true_positives.append((af, gt))
|
| 103 |
-
matched_gt_keys.add(gt.match_key.lower())
|
| 104 |
-
matched = True
|
| 105 |
-
|
| 106 |
-
if not matched:
|
| 107 |
-
result.false_positives.append(af)
|
| 108 |
|
| 109 |
for gt in ground_truth:
|
| 110 |
if gt.match_key.lower() not in matched_gt_keys:
|
|
|
|
| 1 |
+
"""Migration Grader — operation-aware matching with semantic category aliases.
|
| 2 |
+
|
| 3 |
+
Original behavior preserved:
|
| 4 |
+
- Strategy 1: match by (operation, target) extracted from match_key
|
| 5 |
+
- Strategy 2: file + line proximity (±2 lines)
|
| 6 |
+
- Strategy 3: unique rule_id match within a file
|
| 7 |
+
|
| 8 |
+
New strategy added (Strategy 4): file + category-keyword overlap. This credits
|
| 9 |
+
findings that are correct but phrased naturally (e.g. the model says
|
| 10 |
+
"single-row update bottleneck" without naming the exact resource).
|
| 11 |
+
"""
|
| 12 |
+
from typing import List, Set
|
| 13 |
|
| 14 |
from app.models import Finding, GroundTruthFinding
|
| 15 |
from app.graders.base import BaseGrader, MatchResult
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
|
| 31 |
+
# Category → list of phrases the model is likely to use for that issue.
|
| 32 |
+
# Same alias-dictionary approach as the iac_grader fix.
|
| 33 |
+
CATEGORY_ALIASES = {
|
| 34 |
+
"hot_row_contention": ["hot row", "single-row update", "row-level lock", "row contention", "global counter", "serialize"],
|
| 35 |
+
"lock_duration": ["lock duration", "lock held", "row lock", "select for update", "long-running lock"],
|
| 36 |
+
"destructive_cascade": ["cascade", "drop sequence", "destructive", "silent drop"],
|
| 37 |
+
"default_serialization": ["default expression", "default function", "serializ", "per-row"],
|
| 38 |
+
"architecture_anti_pattern": ["architecture", "anti-pattern", "snowflake", "ulid", "shared counter"],
|
| 39 |
+
"non_concurrent_index": ["concurrently", "create index concurrently", "share lock", "blocks writes", "access exclusive"],
|
| 40 |
+
"index_type_choice": ["brin", "btree", "wrong index", "index type", "block range"],
|
| 41 |
+
"architectural_partitioning": ["partition", "partitioning", "partition by range"],
|
| 42 |
+
"non_immutable_default": ["non-immutable", "immutable default", "table rewrite", "rewrite", "metadata-only"],
|
| 43 |
+
"schema_design": ["denormalization", "schema design", "denormalized", "generated column"],
|
| 44 |
+
"operational_blocking": ["vacuum", "analyze", "blocking", "operational"],
|
| 45 |
+
"consistency_replication": ["read-after-write", "replication lag", "replica lag", "stale read", "replica"],
|
| 46 |
+
"idempotency": ["idempot", "retry", "double-credit", "double credit"],
|
| 47 |
+
"destructive_constraint_drop": ["check constraint", "drop constraint", "invariant", "constraint dropped"],
|
| 48 |
+
"lock_serialization": ["row lock", "serialize", "hot account", "lock serialization"],
|
| 49 |
+
"architecture_data_derivation": ["event sourcing", "derived data", "ledger", "trigger-based"],
|
| 50 |
+
"gin_options": ["gin", "jsonb_path_ops", "gin index"],
|
| 51 |
+
"unbatched_update": ["unbatched", "without batching", "single transaction", "wal volume", "batch in"],
|
| 52 |
+
"schema_design_jsonb_abuse": ["jsonb", "json column", "queryable", "schemaless", "promote to columns"],
|
| 53 |
+
"denormalization_consistency": ["denormalized", "stale", "drift", "generated column"],
|
| 54 |
+
"redundant_indexes": ["redundant", "duplicate index", "overlapping index"],
|
| 55 |
+
"soft_delete_uniqueness": ["soft delete", "soft-delete", "deleted_at", "tombstone", "unique constraint"],
|
| 56 |
+
"partial_index_missing": ["partial index", "where clause", "where deleted_at"],
|
| 57 |
+
"tombstone_scan_overhead": ["tombstone", "deleted rows", "soft-deleted"],
|
| 58 |
+
"schema_design_redundancy": ["redundant", "two columns", "drift", "duplicate state"],
|
| 59 |
+
"schema_constraint_missing": ["check constraint", "enum", "constraint missing", "free text"],
|
| 60 |
+
"blocking_check_constraint": ["not valid", "validate constraint", "check constraint", "access exclusive"],
|
| 61 |
+
"blocking_check_with_data_violations": ["existing rows", "data violations", "validate", "not valid"],
|
| 62 |
+
"non_concurrent_drop": ["drop index", "concurrently", "access exclusive lock"],
|
| 63 |
+
"covering_index": ["include", "covering index", "index-only scan", "heap fetch"],
|
| 64 |
+
"redundant_existing_index": ["redundant", "drop index", "duplicate"],
|
| 65 |
+
"polymorphic_association": ["polymorphic", "target_type", "foreign key enforcement", "no fk"],
|
| 66 |
+
"polymorphic_id_ambiguity": ["polymorphic", "ambiguous", "target_id"],
|
| 67 |
+
"missing_indexes": ["missing index", "no index", "seq scan", "sequential scan"],
|
| 68 |
+
"blocking_fk_validation": ["foreign key", "fk validation", "not valid", "validate constraint", "access exclusive"],
|
| 69 |
+
"fk_no_supporting_index": ["supporting index", "no index on", "fk on", "missing index on referencing"],
|
| 70 |
+
"cascade_financial_risk": ["cascade", "on delete", "audit", "financial"],
|
| 71 |
+
"fk_nullable_no_index": ["nullable", "fk", "no index"],
|
| 72 |
+
"blocking_alter_type": ["alter column type", "table rewrite", "access exclusive", "full rewrite"],
|
| 73 |
+
"incomplete_int_to_bigint_migration": ["sequence", "alter sequence", "integer", "bigint", "overflow"],
|
| 74 |
+
"capacity_planning_pk": ["bigint", "integer overflow", "primary key", "serial"],
|
| 75 |
+
"wal_replication_risk": ["wal", "replication slot", "wal volume"],
|
| 76 |
+
"alter_table_metadata_only": ["metadata-only", "metadata only", "fast path", "default", "pg 11"],
|
| 77 |
+
"view_drop_dependency_break": ["drop view", "matview", "dependent objects", "consumers"],
|
| 78 |
+
"blocking_matview_refresh": ["refresh materialized view", "concurrently", "access exclusive"],
|
| 79 |
+
"matview_unique_index": ["unique index", "matview", "concurrently"],
|
| 80 |
+
"matview_incremental_strategy": ["incremental", "pg_ivm", "continuous aggregate"],
|
| 81 |
+
"schema_type_choice": ["char", "varchar", "padding", "type choice"],
|
| 82 |
+
"hot_update_breaking": ["hot update", "heap-only", "fillfactor", "non-hot"],
|
| 83 |
+
"fillfactor_missing": ["fillfactor", "page headroom", "heap-only", "in-page"],
|
| 84 |
+
"alter_storage_unnecessary": ["alter storage", "rewrite", "no-op", "default storage"],
|
| 85 |
+
"autovacuum_tuning": ["autovacuum", "vacuum_scale_factor", "vacuum_threshold", "dead tuples"],
|
| 86 |
+
"schema_design_high_update_column": ["update frequency", "high update", "non-hot"],
|
| 87 |
+
"architecture_wrong_tool": ["postgres", "queue", "redis", "wrong tool", "throughput"],
|
| 88 |
+
"pgbouncer_transaction_pooling": ["pgbouncer", "transaction pooling", "set local", "session"],
|
| 89 |
+
"session_setting_scope": ["set local", "session-level", "set local statement_timeout"],
|
| 90 |
+
"alter_table_pattern": ["add column", "default", "metadata-only"],
|
| 91 |
+
"deadlock_lock_order": ["deadlock", "lock order", "lock ordering"],
|
| 92 |
+
"transaction_scope_too_large": ["transaction scope", "multi-table", "all in one transaction"],
|
| 93 |
+
"create_index_in_txn": ["create index", "transaction", "cannot run inside"],
|
| 94 |
+
"premature_index": ["all-null", "before backfill", "premature"],
|
| 95 |
+
"architecture_audit_pattern": ["audit log", "audit pattern", "centralized audit"],
|
| 96 |
+
"partition_key_choice": ["partition key", "partition by", "tenant_id"],
|
| 97 |
+
"partition_strategy_pareto": ["pareto", "skew", "hash partition", "list partition"],
|
| 98 |
+
"partition_pk_breaks_fks": ["primary key", "partitioning", "fk", "foreign key"],
|
| 99 |
+
"partitioned_index_pruning": ["partition pruning", "global index", "local index"],
|
| 100 |
+
"partition_rollover_missing": ["pg_partman", "rollover", "future partitions"],
|
| 101 |
+
"incomplete_migration": ["data move", "backfill", "cutover", "incomplete"],
|
| 102 |
+
"architecture_multi_tenancy": ["cell-based", "tenant", "dedicated cluster"],
|
| 103 |
+
"replication_pii_leak": ["pii", "all tables", "publication", "column-list"],
|
| 104 |
+
"replica_identity_choice": ["replica identity", "wal volume", "old image", "full image"],
|
| 105 |
+
"slot_disk_fill_risk": ["replication slot", "wal disk", "fill"],
|
| 106 |
+
"redundant_statement": ["redundant", "no-op", "already set"],
|
| 107 |
+
"architecture_data_pipeline": ["debezium", "kafka", "cdc", "change data capture"],
|
| 108 |
+
"partial_index_for_skewed_data": ["partial index", "where status", "skewed"],
|
| 109 |
+
"statistics_target_misuse": ["statistics", "set statistics", "low cardinality"],
|
| 110 |
+
"extended_statistics": ["extended statistics", "create statistics", "correlation"],
|
| 111 |
+
"vacuum_blocking_migration": ["vacuum analyze", "vacuum", "blocking"],
|
| 112 |
+
"redundant_index_skewed": ["useless index", "redundant", "skewed"],
|
| 113 |
+
"toast_compression_misuse": ["toast", "storage external", "compression"],
|
| 114 |
+
"toast_strategy_correct": ["toast", "external", "compression"],
|
| 115 |
+
"alter_storage_blocking": ["alter storage", "rewrite", "access exclusive"],
|
| 116 |
+
"generated_column_opportunity": ["generated column", "generated always as", "stored"],
|
| 117 |
+
"column_ordering": ["column order", "padding", "alignment"],
|
| 118 |
+
"rls_denormalization_required": ["row level security", "rls", "denormaliz", "tenant_id"],
|
| 119 |
+
"rls_with_check_missing": ["with check", "rls policy", "insert"],
|
| 120 |
+
"rls_setting_safety": ["current_setting", "missing_ok", "guc"],
|
| 121 |
+
"rls_role_audit": ["bypassrls", "force row level security", "owner"],
|
| 122 |
+
"rls_command_scope": ["for all", "for select", "policy command"],
|
| 123 |
+
"architecture_isolation_strategy": ["cell architecture", "rls", "tenant isolation"],
|
| 124 |
+
"connection_architecture": ["max_connections", "pgbouncer", "connection pool"],
|
| 125 |
+
"config_requires_restart": ["restart", "pg_reload_conf", "shared_buffers"],
|
| 126 |
+
"memory_sizing": ["work_mem", "shared_buffers", "memory budget"],
|
| 127 |
+
"lock_table_sizing": ["max_locks", "lock table", "shared memory"],
|
| 128 |
+
"architecture_pooling_strategy": ["connection pool", "hikari", "asyncpg", "pgbouncer"],
|
| 129 |
+
"concurrency_check_then_act": ["check-then-act", "race condition", "for update", "concurrent"],
|
| 130 |
+
"isolation_level": ["isolation", "read committed", "serializable", "snapshot"],
|
| 131 |
+
"hot_row_for_update": ["for update", "hot row", "skip locked", "queue"],
|
| 132 |
+
"function_observability": ["return value", "boolean", "function returns"],
|
| 133 |
+
"concurrency_pattern_choice": ["optimistic", "version column", "compare-and-swap"],
|
| 134 |
+
"schema_sharding_for_hot_data": ["shard", "bucket", "hot data", "pareto"],
|
| 135 |
+
"enum_alter_constraints": ["alter type", "add value", "transaction", "enum"],
|
| 136 |
+
"rolling_deploy_enum_rename": ["rename value", "rolling deploy", "enum"],
|
| 137 |
+
"enum_drop_value_unsupported": ["drop value", "alter type", "not supported"],
|
| 138 |
+
"data_migration_correctness": ["semantic", "data migration", "wrong mapping"],
|
| 139 |
+
"architecture_enum_rigidity": ["enum", "lookup table", "reference table"],
|
| 140 |
+
"audit_log_enum_safety": ["audit log", "enum", "historical"],
|
| 141 |
+
"online_index_swap_order": ["concurrently", "drop before create", "atomic swap"],
|
| 142 |
+
"architecture_partitioning_time_series": ["partition by range", "time series", "brin"],
|
| 143 |
+
"architecture_tiered_storage": ["tiered", "redis", "clickhouse", "hot storage"],
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def _category_keywords(category: str) -> List[str]:
|
| 148 |
+
"""Get all phrases that should credit a finding for this category."""
|
| 149 |
+
cat = (category or "").lower()
|
| 150 |
+
if not cat:
|
| 151 |
+
return []
|
| 152 |
+
keywords = [cat, cat.replace("_", " "), cat.replace("_", "-")]
|
| 153 |
+
keywords.extend(CATEGORY_ALIASES.get(cat, []))
|
| 154 |
+
return [k.lower() for k in keywords if k]
|
| 155 |
+
|
| 156 |
+
|
| 157 |
class MigrationGrader(BaseGrader):
|
| 158 |
"""Matches findings by (operation_type, target_object).
|
| 159 |
|
| 160 |
Primary match: match_key components found in finding description.
|
| 161 |
Secondary match: same file + matching line number (within 2 lines).
|
| 162 |
+
Tertiary match: unique rule_id within file.
|
| 163 |
+
Quaternary match: same file + category-keyword overlap (semantic alias).
|
| 164 |
"""
|
| 165 |
|
| 166 |
@staticmethod
|
|
|
|
| 170 |
if op_info is None:
|
| 171 |
return True # Unknown operation, allow match on target alone
|
| 172 |
valid_rules, keywords = op_info
|
|
|
|
| 173 |
if rule_id.upper() in valid_rules:
|
| 174 |
return True
|
| 175 |
return any(kw in desc_lower for kw in keywords)
|
|
|
|
| 178 |
self, agent_findings: List[Finding], ground_truth: List[GroundTruthFinding]
|
| 179 |
) -> MatchResult:
|
| 180 |
result = MatchResult()
|
| 181 |
+
matched_gt_keys: Set[str] = set()
|
| 182 |
|
| 183 |
gt_by_key = {gt.match_key.lower(): gt for gt in ground_truth}
|
| 184 |
|
| 185 |
for af in agent_findings:
|
| 186 |
matched = False
|
| 187 |
+
desc_lower = (af.description or "").lower()
|
| 188 |
+
af_file = (af.file or "").lower()
|
| 189 |
|
| 190 |
+
# Strategy 1: match_key (operation|target) components in description
|
| 191 |
for key, gt in gt_by_key.items():
|
| 192 |
if key in matched_gt_keys:
|
| 193 |
continue
|
|
|
|
| 194 |
parts = key.split("|")
|
| 195 |
if len(parts) == 2:
|
| 196 |
operation, target = parts
|
|
|
|
| 197 |
if not self._operation_matches(operation, af.rule_id, desc_lower):
|
| 198 |
continue
|
|
|
|
|
|
|
| 199 |
target_parts = target.split(".")
|
| 200 |
target_found = target in desc_lower
|
| 201 |
if not target_found and len(target_parts) == 2:
|
|
|
|
| 202 |
target_found = (
|
| 203 |
target_parts[0] in desc_lower
|
| 204 |
and target_parts[1] in desc_lower
|
|
|
|
| 208 |
matched_gt_keys.add(key)
|
| 209 |
matched = True
|
| 210 |
break
|
| 211 |
+
if matched:
|
| 212 |
+
continue
|
| 213 |
|
| 214 |
+
# Strategy 2: file + line proximity (±2 lines)
|
| 215 |
+
if af.line is not None:
|
| 216 |
for gt in ground_truth:
|
| 217 |
if (
|
| 218 |
gt.match_key.lower() not in matched_gt_keys
|
| 219 |
+
and gt.file.lower() == af_file
|
| 220 |
and gt.line is not None
|
| 221 |
and abs(gt.line - af.line) <= 2
|
| 222 |
):
|
|
|
|
| 224 |
matched_gt_keys.add(gt.match_key.lower())
|
| 225 |
matched = True
|
| 226 |
break
|
| 227 |
+
if matched:
|
| 228 |
+
continue
|
| 229 |
+
|
| 230 |
+
# Strategy 3: unique rule_id within file
|
| 231 |
+
rule_candidates = [
|
| 232 |
+
gt for gt in ground_truth
|
| 233 |
+
if gt.match_key.lower() not in matched_gt_keys
|
| 234 |
+
and gt.rule_id.upper() == af.rule_id.upper()
|
| 235 |
+
and gt.file.lower() == af_file
|
| 236 |
+
]
|
| 237 |
+
if len(rule_candidates) == 1:
|
| 238 |
+
gt = rule_candidates[0]
|
| 239 |
+
result.true_positives.append((af, gt))
|
| 240 |
+
matched_gt_keys.add(gt.match_key.lower())
|
| 241 |
+
matched = True
|
| 242 |
+
if matched:
|
| 243 |
+
continue
|
| 244 |
+
|
| 245 |
+
# Strategy 4 (NEW): file + category-keyword overlap (semantic alias)
|
| 246 |
+
for gt in ground_truth:
|
| 247 |
+
key = gt.match_key.lower()
|
| 248 |
+
if key in matched_gt_keys or not gt.category:
|
| 249 |
+
continue
|
| 250 |
+
if gt.file.lower() != af_file:
|
| 251 |
+
continue
|
| 252 |
+
for kw in _category_keywords(gt.category):
|
| 253 |
+
if kw in desc_lower:
|
| 254 |
+
result.true_positives.append((af, gt))
|
| 255 |
+
matched_gt_keys.add(key)
|
| 256 |
+
matched = True
|
| 257 |
+
break
|
| 258 |
+
if matched:
|
| 259 |
+
break
|
| 260 |
+
if matched:
|
| 261 |
+
continue
|
| 262 |
|
| 263 |
+
result.false_positives.append(af)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
|
| 265 |
for gt in ground_truth:
|
| 266 |
if gt.match_key.lower() not in matched_gt_keys:
|
app/tasks/scenarios/dependency/scenario_007/ground_truth.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_007",
|
| 3 |
+
"description": "ML training pipeline requirements with multiple typosquats targeting common ML libraries",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Check each package name against authoritative PyPI listings",
|
| 6 |
+
"Look for character substitution patterns (ll for l, double letters)",
|
| 7 |
+
"Verify combined package names that don't follow PyPI naming"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 2,
|
| 13 |
+
"rule_id": "DEP-001",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Typosquat: 'matplotllib' (extra 'l') misspells the canonical 'matplotlib'. The genuine package is one of the most-downloaded scientific libraries; typosquats targeting it are a high-value attack vector.",
|
| 16 |
+
"match_key": "matplotllib",
|
| 17 |
+
"category": "typosquat"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 3,
|
| 22 |
+
"rule_id": "DEP-001",
|
| 23 |
+
"severity": "critical",
|
| 24 |
+
"description": "Typosquat: 'numpyy' (double y) misspells 'numpy'. NumPy is foundational to the ML ecosystem; typosquatting it gives an attacker code execution in nearly every ML pipeline.",
|
| 25 |
+
"match_key": "numpyy",
|
| 26 |
+
"category": "typosquat"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 4,
|
| 31 |
+
"rule_id": "DEP-001",
|
| 32 |
+
"severity": "critical",
|
| 33 |
+
"description": "Typosquat: 'pandass' misspells 'pandas'.",
|
| 34 |
+
"match_key": "pandass",
|
| 35 |
+
"category": "typosquat"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 5,
|
| 40 |
+
"rule_id": "DEP-001",
|
| 41 |
+
"severity": "critical",
|
| 42 |
+
"description": "Typosquat: 'scikit-lern' misspells 'scikit-learn' (note: PyPI canonical name uses dash; package import name uses underscore).",
|
| 43 |
+
"match_key": "scikit-lern",
|
| 44 |
+
"category": "typosquat"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "requirements.txt",
|
| 48 |
+
"line": 6,
|
| 49 |
+
"rule_id": "DEP-001",
|
| 50 |
+
"severity": "critical",
|
| 51 |
+
"description": "Typosquat: 'seaborrn' (double r) misspells 'seaborn'.",
|
| 52 |
+
"match_key": "seaborrn",
|
| 53 |
+
"category": "typosquat"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"file": "requirements.txt",
|
| 57 |
+
"line": 7,
|
| 58 |
+
"rule_id": "DEP-002",
|
| 59 |
+
"severity": "high",
|
| 60 |
+
"description": "Suspicious package: 'tensorflowgpu' is not the canonical name. Real PyPI packages are 'tensorflow' (auto-detects GPU since 2.0) and historical 'tensorflow-gpu' (deprecated, with hyphen). 'tensorflowgpu' without hyphen is likely typosquat or hallucinated.",
|
| 61 |
+
"match_key": "tensorflowgpu",
|
| 62 |
+
"category": "hallucinated_or_typosquat"
|
| 63 |
+
}
|
| 64 |
+
],
|
| 65 |
+
"initial_files": [
|
| 66 |
+
"requirements.txt"
|
| 67 |
+
],
|
| 68 |
+
"available_files": []
|
| 69 |
+
}
|
app/tasks/scenarios/dependency/scenario_007/requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ML training pipeline dependencies
|
| 2 |
+
matplotllib==3.7.1
|
| 3 |
+
numpyy==1.24.3
|
| 4 |
+
pandass==2.0.3
|
| 5 |
+
scikit-lern==1.3.0
|
| 6 |
+
seaborrn==0.12.2
|
| 7 |
+
tensorflowgpu==2.13.0
|
| 8 |
+
keras==2.13.1
|
app/tasks/scenarios/dependency/scenario_008/ground_truth.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_008",
|
| 3 |
+
"description": "Python API service requirements \u2014 subtle typosquats and confusable package names",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Distinguish near-identical legitimate vs squatted package names",
|
| 6 |
+
"Check confusable / homograph-style names",
|
| 7 |
+
"Verify each package on PyPI authoritative metadata"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 4,
|
| 13 |
+
"rule_id": "DEP-001",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Typosquat: 'python-dateutils' (plural). Genuine package is 'python-dateutil' (singular). The plural form is a known squatted package with a long history of credential harvesting payloads.",
|
| 16 |
+
"match_key": "python-dateutils",
|
| 17 |
+
"category": "typosquat_subtle"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 7,
|
| 22 |
+
"rule_id": "DEP-003",
|
| 23 |
+
"severity": "critical",
|
| 24 |
+
"description": "Confusable package: 'crypto' on PyPI is a known malware redirector. The real cryptography library is 'cryptography' (PyCA). The 'crypto' package and 'pycrypto' (also unmaintained) should never be installed in production.",
|
| 25 |
+
"match_key": "crypto",
|
| 26 |
+
"category": "malicious_package"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 8,
|
| 31 |
+
"rule_id": "DEP-001",
|
| 32 |
+
"severity": "high",
|
| 33 |
+
"description": "Typosquat: 'beautifulsoup' (no version suffix). Genuine package on PyPI is 'beautifulsoup4'. Bare 'beautifulsoup' has been used for malicious uploads in the past.",
|
| 34 |
+
"match_key": "beautifulsoup ",
|
| 35 |
+
"category": "typosquat_legacy_name"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 6,
|
| 40 |
+
"rule_id": "DEP-004",
|
| 41 |
+
"severity": "high",
|
| 42 |
+
"description": "Pydantic 1.x reaches end-of-life June 2024; 1.10.13 is the last 1.x release. While not a direct CVE, EOL frameworks accumulate unpatched issues. Plan migration to pydantic 2.x.",
|
| 43 |
+
"match_key": "pydantic==1.10",
|
| 44 |
+
"category": "eol_dependency"
|
| 45 |
+
}
|
| 46 |
+
],
|
| 47 |
+
"initial_files": [
|
| 48 |
+
"requirements.txt"
|
| 49 |
+
],
|
| 50 |
+
"available_files": []
|
| 51 |
+
}
|
app/tasks/scenarios/dependency/scenario_008/requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# API service stack
|
| 2 |
+
fastapi==0.103.0
|
| 3 |
+
uvicorn[standard]==0.23.2
|
| 4 |
+
python-dateutils==2.8.2
|
| 5 |
+
sqlalchemy==2.0.20
|
| 6 |
+
pydantic==1.10.13
|
| 7 |
+
crypto==1.4.1
|
| 8 |
+
beautifulsoup==4.12.2
|
app/tasks/scenarios/dependency/scenario_009/ground_truth.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_009",
|
| 3 |
+
"description": "Multiple chained CVEs in a Django stack \u2014 review the full vulnerability landscape, not just individual packages",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Map each pinned version to known CVEs and severity",
|
| 6 |
+
"Identify vulnerability chains where one package's CVE depends on another",
|
| 7 |
+
"Plan coordinated upgrade path"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 2,
|
| 13 |
+
"rule_id": "DEP-005",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Django 2.2.28 reached end-of-life April 2022. 2.2.x has multiple unpatched CVEs including CVE-2022-22818 (template HTML injection in {% debug %}), CVE-2022-23833 (DoS in RegexValidator). Upgrade to LTS 4.2 or 5.0.",
|
| 16 |
+
"match_key": "django==2.2",
|
| 17 |
+
"category": "eol_with_cves"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 3,
|
| 22 |
+
"rule_id": "DEP-005",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "DRF 3.11.0 has CVE-2024-21520 (XSS via break_long_headers helper). Patched in 3.15.2.",
|
| 25 |
+
"match_key": "djangorestframework==3.11",
|
| 26 |
+
"category": "known_cve"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 4,
|
| 31 |
+
"rule_id": "DEP-005",
|
| 32 |
+
"severity": "critical",
|
| 33 |
+
"description": "PyYAML 5.3 has CVE-2020-14343 (arbitrary code execution via FullLoader). Default loader was unsafe before 5.4. Patched in 5.4+.",
|
| 34 |
+
"match_key": "pyyaml==5.3",
|
| 35 |
+
"category": "known_cve"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 5,
|
| 40 |
+
"rule_id": "DEP-005",
|
| 41 |
+
"severity": "critical",
|
| 42 |
+
"description": "Jinja2 2.10.1 has CVE-2019-10906 (sandbox escape via str.format_map). Patched in 2.10.1+ \u2014 but 2.10.1 also has CVE-2020-28493 ReDoS in urlize filter. Upgrade to 3.1.4+.",
|
| 43 |
+
"match_key": "jinja2==2.10.1",
|
| 44 |
+
"category": "known_cve"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "requirements.txt",
|
| 48 |
+
"line": 6,
|
| 49 |
+
"rule_id": "DEP-005",
|
| 50 |
+
"severity": "high",
|
| 51 |
+
"description": "sqlparse 0.3.0 has CVE-2021-32839 (ReDoS). Used by Django for SQL formatting. Patched in 0.4.2+.",
|
| 52 |
+
"match_key": "sqlparse==0.3",
|
| 53 |
+
"category": "known_cve"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"file": "requirements.txt",
|
| 57 |
+
"line": 7,
|
| 58 |
+
"rule_id": "DEP-005",
|
| 59 |
+
"severity": "critical",
|
| 60 |
+
"description": "urllib3 1.25.8 has multiple CVEs including CVE-2023-43804 (cookie request smuggling), CVE-2023-45803 (request body leak on redirect). Patched in 1.26.18+.",
|
| 61 |
+
"match_key": "urllib3==1.25",
|
| 62 |
+
"category": "known_cve"
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"file": "requirements.txt",
|
| 66 |
+
"line": 8,
|
| 67 |
+
"rule_id": "DEP-005",
|
| 68 |
+
"severity": "critical",
|
| 69 |
+
"description": "cryptography 2.8 has CVE-2020-25659 (Bleichenbacher attack on RSA decryption). Multiple subsequent CVEs in 2.x and 3.x. Upgrade to 42.0+ for current security.",
|
| 70 |
+
"match_key": "cryptography==2.8",
|
| 71 |
+
"category": "known_cve"
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"file": "requirements.txt",
|
| 75 |
+
"line": 9,
|
| 76 |
+
"rule_id": "DEP-005",
|
| 77 |
+
"severity": "high",
|
| 78 |
+
"description": "requests 2.21.0 has CVE-2018-18074 (proxy authorization leak) and CVE-2023-32681 (proxy authorization not stripped on cross-origin redirects). Patched in 2.32.0+.",
|
| 79 |
+
"match_key": "requests==2.21",
|
| 80 |
+
"category": "known_cve"
|
| 81 |
+
}
|
| 82 |
+
],
|
| 83 |
+
"initial_files": [
|
| 84 |
+
"requirements.txt"
|
| 85 |
+
],
|
| 86 |
+
"available_files": []
|
| 87 |
+
}
|
app/tasks/scenarios/dependency/scenario_009/requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Web service with multi-CVE chain
|
| 2 |
+
django==2.2.28
|
| 3 |
+
djangorestframework==3.11.0
|
| 4 |
+
pyyaml==5.3
|
| 5 |
+
jinja2==2.10.1
|
| 6 |
+
sqlparse==0.3.0
|
| 7 |
+
urllib3==1.25.8
|
| 8 |
+
cryptography==2.8
|
| 9 |
+
requests==2.21.0
|
app/tasks/scenarios/dependency/scenario_010/ground_truth.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_010",
|
| 3 |
+
"description": "LLM-suggested package list for an AI app. Several are hallucinated (do not exist on PyPI) \u2014 a known LLM coding failure mode that creates supply-chain attack opportunities.",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Verify each package exists on PyPI",
|
| 6 |
+
"Check for naming patterns that match LLM hallucination styles (real-prefix + generic-suffix)",
|
| 7 |
+
"Identify supply-chain hijack risk (squatters can register hallucinated names)"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 3,
|
| 13 |
+
"rule_id": "DEP-006",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Hallucinated package: 'langchain-utils' does not exist on PyPI. This is a 'slopsquat' opportunity \u2014 an attacker can register the package name and any LLM-generated requirements.txt referencing it will install attacker-controlled code.",
|
| 16 |
+
"match_key": "langchain-utils",
|
| 17 |
+
"category": "hallucinated_slopsquat"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 4,
|
| 22 |
+
"rule_id": "DEP-006",
|
| 23 |
+
"severity": "critical",
|
| 24 |
+
"description": "Hallucinated: 'streamlit-helpers' does not exist on PyPI. LLMs frequently hallucinate '<library>-helpers' or '<library>-utils' packages because such names are pattern-frequent in their training data.",
|
| 25 |
+
"match_key": "streamlit-helpers",
|
| 26 |
+
"category": "hallucinated_slopsquat"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 5,
|
| 31 |
+
"rule_id": "DEP-006",
|
| 32 |
+
"severity": "critical",
|
| 33 |
+
"description": "Hallucinated: 'torch-helpers' does not exist. The genuine package is just 'torch'.",
|
| 34 |
+
"match_key": "torch-helpers",
|
| 35 |
+
"category": "hallucinated_slopsquat"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 6,
|
| 40 |
+
"rule_id": "DEP-006",
|
| 41 |
+
"severity": "high",
|
| 42 |
+
"description": "Hallucinated: 'chromadb-client' does not exist on PyPI. The real package is just 'chromadb'.",
|
| 43 |
+
"match_key": "chromadb-client",
|
| 44 |
+
"category": "hallucinated_slopsquat"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "requirements.txt",
|
| 48 |
+
"line": 7,
|
| 49 |
+
"rule_id": "DEP-006",
|
| 50 |
+
"severity": "critical",
|
| 51 |
+
"description": "Hallucinated: 'embedding-models' is generic enough to be plausible but does not correspond to any canonical package. Likely LLM hallucination based on the conceptual term.",
|
| 52 |
+
"match_key": "embedding-models",
|
| 53 |
+
"category": "hallucinated_slopsquat"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"file": "requirements.txt",
|
| 57 |
+
"line": 8,
|
| 58 |
+
"rule_id": "DEP-006",
|
| 59 |
+
"severity": "critical",
|
| 60 |
+
"description": "Hallucinated: 'vector-store' is a generic concept name; no canonical PyPI package by that name. Likely LLM hallucination.",
|
| 61 |
+
"match_key": "vector-store",
|
| 62 |
+
"category": "hallucinated_slopsquat"
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"file": "requirements.txt",
|
| 66 |
+
"line": 9,
|
| 67 |
+
"rule_id": "DEP-006",
|
| 68 |
+
"severity": "critical",
|
| 69 |
+
"description": "Hallucinated: 'ai-toolkit' is generic and does not correspond to a canonical package. LLM hallucination pattern.",
|
| 70 |
+
"match_key": "ai-toolkit",
|
| 71 |
+
"category": "hallucinated_slopsquat"
|
| 72 |
+
}
|
| 73 |
+
],
|
| 74 |
+
"initial_files": [
|
| 75 |
+
"requirements.txt"
|
| 76 |
+
],
|
| 77 |
+
"available_files": []
|
| 78 |
+
}
|
app/tasks/scenarios/dependency/scenario_010/requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Suggested by LLM during code generation
|
| 2 |
+
openai==1.3.0
|
| 3 |
+
langchain-utils==0.5.2
|
| 4 |
+
streamlit-helpers==0.3.1
|
| 5 |
+
torch-helpers==1.9.0
|
| 6 |
+
chromadb-client==0.4.5
|
| 7 |
+
embedding-models==2.1.0
|
| 8 |
+
vector-store==1.2.3
|
| 9 |
+
ai-toolkit==0.8.0
|
app/tasks/scenarios/dependency/scenario_011/ground_truth.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_011",
|
| 3 |
+
"description": "A list of packages that were victims of high-profile supply-chain hijacks. Even at recent versions some carry residual risk.",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Recall historical hijacking incidents per package",
|
| 6 |
+
"Verify whether the pinned version pre-dates or post-dates the hijack",
|
| 7 |
+
"Check for protestware / abandoned-and-resurrected packages"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 2,
|
| 13 |
+
"rule_id": "DEP-007",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "event-stream@3.3.6 was hijacked in 2018 (flatmap-stream backdoor). 4.x.y was published post-hijack but the package never recovered trust \u2014 actively avoided in production. Use alternatives like rxjs or readable-stream.",
|
| 16 |
+
"match_key": "event-stream",
|
| 17 |
+
"category": "hijacked_history"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 3,
|
| 22 |
+
"rule_id": "DEP-007",
|
| 23 |
+
"severity": "critical",
|
| 24 |
+
"description": "ua-parser-js@0.7.29 was the version that was hijacked in October 2021 (cryptominer + credential stealer). Even though clean versions followed, this exact pin is the malicious version. Required: upgrade to 0.7.30+ or 1.0.32+.",
|
| 25 |
+
"match_key": "ua-parser-js==0.7.29",
|
| 26 |
+
"category": "hijacked_exact_version"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 4,
|
| 31 |
+
"rule_id": "DEP-007",
|
| 32 |
+
"severity": "critical",
|
| 33 |
+
"description": "colors.js: protestware incident in early 2022 \u2014 the maintainer self-sabotaged 1.4.1+ versions with infinite-loop garbage output. Use older 1.4.0 or fork like ansi-colors.",
|
| 34 |
+
"match_key": "colors.js==1.4.1",
|
| 35 |
+
"category": "protestware"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 5,
|
| 40 |
+
"rule_id": "DEP-007",
|
| 41 |
+
"severity": "high",
|
| 42 |
+
"description": "faker: similar protestware in 2022 (maintainer sabotaged the package). Forks (community-faker, faker-js/faker for npm) are alternatives. Versions 15.x and forward predate the protest but trust is partially restored.",
|
| 43 |
+
"match_key": "faker==15",
|
| 44 |
+
"category": "protestware_history"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "requirements.txt",
|
| 48 |
+
"line": 6,
|
| 49 |
+
"rule_id": "DEP-008",
|
| 50 |
+
"severity": "high",
|
| 51 |
+
"description": "Suspicious: 'fast-furniture' is unusual for a coding context. Verify package legitimacy and provenance \u2014 name doesn't match conventional library naming. Possible squat or unrelated package.",
|
| 52 |
+
"match_key": "fast-furniture",
|
| 53 |
+
"category": "suspicious_package"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"file": "requirements.txt",
|
| 57 |
+
"line": 7,
|
| 58 |
+
"rule_id": "DEP-007",
|
| 59 |
+
"severity": "critical",
|
| 60 |
+
"description": "ctx: hijacked in May 2022 (versions 0.1.2 and later in that release window were malicious). All ctx versions are now considered untrusted unless specifically audited. Replace.",
|
| 61 |
+
"match_key": "ctx==0.1.2",
|
| 62 |
+
"category": "hijacked_exact_version"
|
| 63 |
+
}
|
| 64 |
+
],
|
| 65 |
+
"initial_files": [
|
| 66 |
+
"requirements.txt"
|
| 67 |
+
],
|
| 68 |
+
"available_files": []
|
| 69 |
+
}
|
app/tasks/scenarios/dependency/scenario_011/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Static dependency list — consider whether each was hijacked at any point
|
| 2 |
+
event-stream==4.0.0
|
| 3 |
+
ua-parser-js==0.7.29
|
| 4 |
+
colors.js==1.4.1
|
| 5 |
+
faker==15.0.0
|
| 6 |
+
fast-furniture==2.1.0
|
| 7 |
+
ctx==0.1.2
|
app/tasks/scenarios/dependency/scenario_012/ground_truth.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_012",
|
| 3 |
+
"description": "Closed-source commercial SaaS dependencies \u2014 review license compatibility risks",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Verify license of each dependency (MIT, BSD, Apache, MPL = generally OK)",
|
| 6 |
+
"Identify GPL/AGPL dependencies and their network-distribution implications",
|
| 7 |
+
"Check copyleft contamination scope"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 4,
|
| 13 |
+
"rule_id": "DEP-009",
|
| 14 |
+
"severity": "high",
|
| 15 |
+
"description": "mysql-connector-python uses GPL v2 with FOSS exception. The exception only applies to redistributing under another approved FOSS license \u2014 does NOT cover commercial closed-source. Use the MIT-licensed alternative PyMySQL or pay for Oracle's commercial license.",
|
| 16 |
+
"match_key": "mysql-connector-python",
|
| 17 |
+
"category": "gpl_license_risk"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 6,
|
| 22 |
+
"rule_id": "DEP-009",
|
| 23 |
+
"severity": "critical",
|
| 24 |
+
"description": "Package name signals GPL-only license. In a closed-source SaaS, GPL dependencies create copyleft contamination risk for any code that statically links or significantly integrates. Verify license and either replace or escalate to legal review.",
|
| 25 |
+
"match_key": "gpl-only-pkg",
|
| 26 |
+
"category": "gpl_contamination"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 7,
|
| 31 |
+
"rule_id": "DEP-009",
|
| 32 |
+
"severity": "critical",
|
| 33 |
+
"description": "AGPL packages trigger copyleft for SaaS products specifically \u2014 even network use counts as 'distribution' under AGPL. For a SaaS, this is the most aggressive copyleft trigger and likely violates the product's commercial license model. Replace immediately.",
|
| 34 |
+
"match_key": "agpl-billing-utils",
|
| 35 |
+
"category": "agpl_saas_violation"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 8,
|
| 40 |
+
"rule_id": "DEP-001",
|
| 41 |
+
"severity": "high",
|
| 42 |
+
"description": "Suspicious package: 'readline' as a top-level Python package on PyPI is unusual \u2014 readline is part of the Python standard library on most platforms. The PyPI 'readline' has been used as typosquat/malware in the past. Likely should not be a direct dependency.",
|
| 43 |
+
"match_key": "readline==6",
|
| 44 |
+
"category": "suspicious_package"
|
| 45 |
+
}
|
| 46 |
+
],
|
| 47 |
+
"initial_files": [
|
| 48 |
+
"requirements.txt"
|
| 49 |
+
],
|
| 50 |
+
"available_files": []
|
| 51 |
+
}
|
app/tasks/scenarios/dependency/scenario_012/requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Commercial SaaS product (closed-source) - license compatibility matters
|
| 2 |
+
pyjwt==2.8.0
|
| 3 |
+
flask==3.0.0
|
| 4 |
+
mysql-connector-python==8.2.0
|
| 5 |
+
psycopg2-binary==2.9.9
|
| 6 |
+
gpl-only-pkg==1.0.0
|
| 7 |
+
agpl-billing-utils==2.3.1
|
| 8 |
+
readline==6.2.4.1
|
app/tasks/scenarios/dependency/scenario_013/ground_truth.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_013",
|
| 3 |
+
"description": "Node.js application package.json \u2014 review caret/wildcard versions, lockfile dependence, and postinstall hooks",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Identify version specifiers that allow drift (^, ~, *)",
|
| 6 |
+
"Check for known historical hijack/sabotage incidents per package",
|
| 7 |
+
"Verify postinstall scripts and supply-chain risk"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "package.json",
|
| 12 |
+
"line": 11,
|
| 13 |
+
"rule_id": "DEP-007",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "node-ipc was sabotaged in 2022 \u2014 versions 10.1.1, 10.1.2, 11.0.0 were modified to corrupt files on disk based on geo-IP. ^9.2.1 allows minor upgrades up to <10.0 so escapes the directly-malicious range BUT trust is permanently broken; the maintainer demonstrated willingness to weaponize the package. Replace with an alternative.",
|
| 16 |
+
"match_key": "node-ipc",
|
| 17 |
+
"category": "protestware_known_actor"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "package.json",
|
| 21 |
+
"line": 12,
|
| 22 |
+
"rule_id": "DEP-007",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "is-promise was hijacked in April 2020 \u2014 the malicious version used dynamic require() to bypass detection. ^2.2.2 allows updates within 2.x; the hijacked version is in this range historically. Pin to a known-clean exact version, or remove (functionality is trivial).",
|
| 25 |
+
"match_key": "is-promise",
|
| 26 |
+
"category": "hijacked_within_range"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "package.json",
|
| 30 |
+
"line": 13,
|
| 31 |
+
"rule_id": "DEP-010",
|
| 32 |
+
"severity": "critical",
|
| 33 |
+
"description": "@types/node: \"*\" is an unbounded wildcard \u2014 every npm install can pull a different version. Wildcard versions are explicitly forbidden by most enterprise dependency policies; pin to a version range like ^20.0.0.",
|
| 34 |
+
"match_key": "@types/node\":\"*\"",
|
| 35 |
+
"category": "unbounded_version"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "package.json",
|
| 39 |
+
"line": 14,
|
| 40 |
+
"rule_id": "DEP-005",
|
| 41 |
+
"severity": "critical",
|
| 42 |
+
"description": "axios 0.21.1 has CVE-2021-3749 (ReDoS in trim function) and CVE-2020-28168 (SSRF). ^0.21.1 only allows patches within 0.21.x. Bump to 1.7.0+ for current security.",
|
| 43 |
+
"match_key": "axios",
|
| 44 |
+
"category": "known_cve"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "package.json",
|
| 48 |
+
"line": 15,
|
| 49 |
+
"rule_id": "DEP-005",
|
| 50 |
+
"severity": "critical",
|
| 51 |
+
"description": "minimist 1.2.5 has CVE-2021-44906 (prototype pollution). ^1.2.5 allows up to <2.0 so 1.2.6+ resolves this; verify lockfile actually pulls a patched version.",
|
| 52 |
+
"match_key": "minimist",
|
| 53 |
+
"category": "known_cve"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"file": "package.json",
|
| 57 |
+
"line": 16,
|
| 58 |
+
"rule_id": "DEP-011",
|
| 59 |
+
"severity": "high",
|
| 60 |
+
"description": "Suspicious package name 'node-postinstall-script' suggests it executes scripts during install. Postinstall hooks are a primary supply-chain attack vector \u2014 npm install runs arbitrary code from any dependency by default. Audit the package or use --ignore-scripts.",
|
| 61 |
+
"match_key": "node-postinstall-script",
|
| 62 |
+
"category": "postinstall_risk"
|
| 63 |
+
}
|
| 64 |
+
],
|
| 65 |
+
"initial_files": [
|
| 66 |
+
"package.json"
|
| 67 |
+
],
|
| 68 |
+
"available_files": []
|
| 69 |
+
}
|
app/tasks/scenarios/dependency/scenario_013/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "company-internal-app",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"scripts": {
|
| 5 |
+
"build": "webpack",
|
| 6 |
+
"test": "jest"
|
| 7 |
+
},
|
| 8 |
+
"dependencies": {
|
| 9 |
+
"express": "^4.18.2",
|
| 10 |
+
"lodash": "^4.17.20",
|
| 11 |
+
"node-ipc": "^9.2.1",
|
| 12 |
+
"is-promise": "^2.2.2",
|
| 13 |
+
"@types/node": "*",
|
| 14 |
+
"axios": "^0.21.1",
|
| 15 |
+
"minimist": "^1.2.5",
|
| 16 |
+
"node-postinstall-script": "^1.0.0"
|
| 17 |
+
}
|
| 18 |
+
}
|
app/tasks/scenarios/dependency/scenario_014/Pipfile
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[[source]]
|
| 2 |
+
url = "https://pypi.org/simple"
|
| 3 |
+
|
| 4 |
+
[packages]
|
| 5 |
+
flask = "==3.0.0"
|
| 6 |
+
gunicorn = "*"
|
| 7 |
+
psycopg2-binary = "==2.9.9"
|
| 8 |
+
celery = "==5.3.4"
|
| 9 |
+
redis = "==5.0.1"
|
app/tasks/scenarios/dependency/scenario_014/ground_truth.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_014",
|
| 3 |
+
"description": "Mixed Python package management \u2014 three tools coexist with conflicting versions and ranges",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Identify which manifest is authoritative",
|
| 6 |
+
"Detect version conflicts across manifests",
|
| 7 |
+
"Audit binary vs source variants of equivalent packages"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 1,
|
| 13 |
+
"rule_id": "DEP-012",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Mixed package managers: requirements.txt (pip), Pipfile (pipenv), and pyproject.toml (Poetry) all coexist with DIFFERENT version constraints for the same packages. Flask is 2.3.3 / 3.0.0 / ^3.1 across the three. Whichever tool runs last 'wins' \u2014 this is a guaranteed dev/prod divergence. Choose one tool, delete the others.",
|
| 16 |
+
"match_key": "mixed_managers",
|
| 17 |
+
"category": "manifest_drift"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 4,
|
| 22 |
+
"rule_id": "DEP-013",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "psycopg2 (source build) requires PostgreSQL development headers and a C compiler \u2014 fails in production containers without those installed. The Pipfile correctly uses psycopg2-binary; align both files to the binary variant for production.",
|
| 25 |
+
"match_key": "psycopg2==",
|
| 26 |
+
"category": "wrong_package_variant"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "Pipfile",
|
| 30 |
+
"line": 6,
|
| 31 |
+
"rule_id": "DEP-010",
|
| 32 |
+
"severity": "high",
|
| 33 |
+
"description": "Pipfile gunicorn = \"*\" allows any version. In production, this means installs are not reproducible; a new release can break the deploy. Pin to an exact version or version range.",
|
| 34 |
+
"match_key": "gunicorn = \"*\"",
|
| 35 |
+
"category": "unbounded_version"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "pyproject.toml",
|
| 39 |
+
"line": 4,
|
| 40 |
+
"rule_id": "DEP-014",
|
| 41 |
+
"severity": "medium",
|
| 42 |
+
"description": "Caret range ^3.1 for Flask allows 3.1.0 through <4.0.0. Combined with the other manifests' tighter pins, the installed version depends on which tool the deployer ran. Drift inevitable.",
|
| 43 |
+
"match_key": "flask = \"^3.1\"",
|
| 44 |
+
"category": "caret_drift"
|
| 45 |
+
}
|
| 46 |
+
],
|
| 47 |
+
"initial_files": [
|
| 48 |
+
"requirements.txt",
|
| 49 |
+
"Pipfile",
|
| 50 |
+
"pyproject.toml"
|
| 51 |
+
],
|
| 52 |
+
"available_files": []
|
| 53 |
+
}
|
app/tasks/scenarios/dependency/scenario_014/pyproject.toml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[tool.poetry.dependencies]
|
| 2 |
+
python = "^3.11"
|
| 3 |
+
flask = "^3.1"
|
| 4 |
+
sqlalchemy = "^2.0"
|
app/tasks/scenarios/dependency/scenario_014/requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# pip-installable
|
| 2 |
+
flask==2.3.3
|
| 3 |
+
gunicorn==21.2.0
|
| 4 |
+
psycopg2==2.9.9
|
| 5 |
+
celery==5.3.4
|
app/tasks/scenarios/dependency/scenario_015/ground_truth.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_015",
|
| 3 |
+
"description": "Production requirements pinning pre-release versions (alpha, beta, rc, dev). These are not stable release artifacts.",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Identify each pre-release version specifier (a, b, rc, dev, post)",
|
| 6 |
+
"Audit whether stable releases exist for the targeted version",
|
| 7 |
+
"Check for known issues with pre-release pins"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 2,
|
| 13 |
+
"rule_id": "DEP-015",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Django 5.0a1 is an alpha release. Alpha releases have known bugs and may have backwards-incompatible API changes before stable. Use the stable Django 5.0+.",
|
| 16 |
+
"match_key": "django==5.0a",
|
| 17 |
+
"category": "alpha_in_prod"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 3,
|
| 22 |
+
"rule_id": "DEP-015",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "FastAPI 0.100.0rc1 is a release-candidate version. RCs are not for production; the corresponding stable 0.100.0 was released shortly after.",
|
| 25 |
+
"match_key": "fastapi==0.100.0rc",
|
| 26 |
+
"category": "rc_in_prod"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 4,
|
| 31 |
+
"rule_id": "DEP-015",
|
| 32 |
+
"severity": "high",
|
| 33 |
+
"description": "SQLAlchemy 2.0.0b3 is beta. SQLAlchemy 2.0+ stable is available; betas should not be in production manifests.",
|
| 34 |
+
"match_key": "sqlalchemy==2.0.0b",
|
| 35 |
+
"category": "beta_in_prod"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 5,
|
| 40 |
+
"rule_id": "DEP-015",
|
| 41 |
+
"severity": "critical",
|
| 42 |
+
"description": "Pydantic 2.0.0.dev1 is a development release \u2014 explicitly not for any non-development use. dev versions are frequently broken.",
|
| 43 |
+
"match_key": "pydantic==2.0.0.dev",
|
| 44 |
+
"category": "dev_in_prod"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "requirements.txt",
|
| 48 |
+
"line": 6,
|
| 49 |
+
"rule_id": "DEP-015",
|
| 50 |
+
"severity": "high",
|
| 51 |
+
"description": "Celery 5.4.0rc2 \u2014 release candidate.",
|
| 52 |
+
"match_key": "celery==5.4.0rc",
|
| 53 |
+
"category": "rc_in_prod"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"file": "requirements.txt",
|
| 57 |
+
"line": 7,
|
| 58 |
+
"rule_id": "DEP-015",
|
| 59 |
+
"severity": "high",
|
| 60 |
+
"description": "redis 5.1.0b1 \u2014 beta version.",
|
| 61 |
+
"match_key": "redis==5.1.0b",
|
| 62 |
+
"category": "beta_in_prod"
|
| 63 |
+
}
|
| 64 |
+
],
|
| 65 |
+
"initial_files": [
|
| 66 |
+
"requirements.txt"
|
| 67 |
+
],
|
| 68 |
+
"available_files": []
|
| 69 |
+
}
|
app/tasks/scenarios/dependency/scenario_015/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Production requirements
|
| 2 |
+
django==5.0a1
|
| 3 |
+
fastapi==0.100.0rc1
|
| 4 |
+
sqlalchemy==2.0.0b3
|
| 5 |
+
pydantic==2.0.0.dev1
|
| 6 |
+
celery==5.4.0rc2
|
| 7 |
+
redis==5.1.0b1
|
app/tasks/scenarios/dependency/scenario_016/ground_truth.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_016",
|
| 3 |
+
"description": "Long-abandoned packages \u2014 no commits in years, no security maintenance",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Identify packages with no recent releases (5+ years)",
|
| 6 |
+
"Check whether maintainership is active or abandoned",
|
| 7 |
+
"Find modern replacements for abandoned tooling"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 2,
|
| 13 |
+
"rule_id": "DEP-016",
|
| 14 |
+
"severity": "high",
|
| 15 |
+
"description": "Flask-Bcrypt last released 2017 (0.7.1). Functionally simple but unmaintained \u2014 switch to bcrypt directly or use passlib for password hashing. Abandonment in security-critical code is a long-term liability.",
|
| 16 |
+
"match_key": "flask-bcrypt",
|
| 17 |
+
"category": "abandoned"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 3,
|
| 22 |
+
"rule_id": "DEP-017",
|
| 23 |
+
"severity": "critical",
|
| 24 |
+
"description": "python2-secrets is a Python 2 backport of the secrets module. Python 2 reached EOL January 2020. Any Python 2 dependency in a current codebase is a major maintenance flag.",
|
| 25 |
+
"match_key": "python2-secrets",
|
| 26 |
+
"category": "python2_eol"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 4,
|
| 31 |
+
"rule_id": "DEP-016",
|
| 32 |
+
"severity": "medium",
|
| 33 |
+
"description": "ipython 5.x is the last Python 2 compatible series \u2014 frozen since 2017. For Python 3, use ipython 8.x. Pinning 5.10.0 indicates the project may still target Python 2.",
|
| 34 |
+
"match_key": "ipython==5.10",
|
| 35 |
+
"category": "abandoned_branch"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 5,
|
| 40 |
+
"rule_id": "DEP-016",
|
| 41 |
+
"severity": "high",
|
| 42 |
+
"description": "MongoKit last released 2014 \u2014 10+ years abandoned. Replace with PyMongo + custom validation, or motor for async, or beanie/odmantic for typed models.",
|
| 43 |
+
"match_key": "mongokit",
|
| 44 |
+
"category": "abandoned_severe"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "requirements.txt",
|
| 48 |
+
"line": 6,
|
| 49 |
+
"rule_id": "DEP-016",
|
| 50 |
+
"severity": "high",
|
| 51 |
+
"description": "Flask-Genshi: Genshi templating itself is abandoned (last release 2014). The Flask integration follows. Migrate templates to Jinja2 (Flask's default).",
|
| 52 |
+
"match_key": "flask-genshi",
|
| 53 |
+
"category": "abandoned_dependency_chain"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"file": "requirements.txt",
|
| 57 |
+
"line": 7,
|
| 58 |
+
"rule_id": "DEP-016",
|
| 59 |
+
"severity": "high",
|
| 60 |
+
"description": "suds (the original SOAP client): last release 2010. Replaced long ago by suds-jurko (also abandoned ~2017) and currently zeep is the maintained alternative.",
|
| 61 |
+
"match_key": "suds==0.4",
|
| 62 |
+
"category": "abandoned_severe"
|
| 63 |
+
}
|
| 64 |
+
],
|
| 65 |
+
"initial_files": [
|
| 66 |
+
"requirements.txt"
|
| 67 |
+
],
|
| 68 |
+
"available_files": []
|
| 69 |
+
}
|
app/tasks/scenarios/dependency/scenario_016/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Legacy app — last touched 2 years ago
|
| 2 |
+
flask-bcrypt==0.7.1
|
| 3 |
+
python2-secrets==1.0.5
|
| 4 |
+
ipython==5.10.0
|
| 5 |
+
mongokit==0.9.1.1
|
| 6 |
+
flask-genshi==0.8
|
| 7 |
+
suds==0.4
|
app/tasks/scenarios/dependency/scenario_017/constraints.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Pinned transitive dependencies
|
| 2 |
+
starlette==0.36.0
|
| 3 |
+
anyio==3.7.1
|
| 4 |
+
typing-extensions==4.5.0
|
| 5 |
+
pydantic==1.10.7
|
| 6 |
+
greenlet==2.0.0
|
app/tasks/scenarios/dependency/scenario_017/ground_truth.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_017",
|
| 3 |
+
"description": "Direct dependencies are recent, but pinned transitive dependencies are vulnerable and incompatible",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Audit constraints.txt for pinned transitive versions",
|
| 6 |
+
"Identify version mismatch with direct dependency requirements",
|
| 7 |
+
"Check for transitive CVEs"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "constraints.txt",
|
| 12 |
+
"line": 5,
|
| 13 |
+
"rule_id": "DEP-018",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Pinned pydantic==1.10.7 conflicts with FastAPI 0.110.0 which requires pydantic>=2.0. The constraint will either fail to resolve or downgrade FastAPI silently. Direct vs transitive constraint mismatch.",
|
| 16 |
+
"match_key": "pydantic==1.10",
|
| 17 |
+
"category": "transitive_conflict"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "constraints.txt",
|
| 21 |
+
"line": 2,
|
| 22 |
+
"rule_id": "DEP-005",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "starlette 0.36.0 has CVE-2024-47874 (multipart DoS) \u2014 patched in 0.40.0. Direct dependency FastAPI 0.110 pulls starlette transitively; this constraint pins it to a vulnerable version.",
|
| 25 |
+
"match_key": "starlette==0.36",
|
| 26 |
+
"category": "transitive_cve"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "constraints.txt",
|
| 30 |
+
"line": 3,
|
| 31 |
+
"rule_id": "DEP-005",
|
| 32 |
+
"severity": "medium",
|
| 33 |
+
"description": "anyio 3.7.1 \u2014 anyio 4.x is required for asyncio.timeout compatibility with Python 3.11+ and has performance fixes. Pinning to 3.x in a constraints file forces older code paths.",
|
| 34 |
+
"match_key": "anyio==3.7",
|
| 35 |
+
"category": "transitive_outdated"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "constraints.txt",
|
| 39 |
+
"line": 6,
|
| 40 |
+
"rule_id": "DEP-005",
|
| 41 |
+
"severity": "high",
|
| 42 |
+
"description": "greenlet 2.0.0 has multiple memory-corruption issues fixed in 3.0.x. Required by sqlalchemy for its async support.",
|
| 43 |
+
"match_key": "greenlet==2.0",
|
| 44 |
+
"category": "transitive_cve"
|
| 45 |
+
}
|
| 46 |
+
],
|
| 47 |
+
"initial_files": [
|
| 48 |
+
"requirements.txt",
|
| 49 |
+
"constraints.txt"
|
| 50 |
+
],
|
| 51 |
+
"available_files": []
|
| 52 |
+
}
|
app/tasks/scenarios/dependency/scenario_017/requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Direct deps look OK but transitive issues lurk
|
| 2 |
+
fastapi==0.110.0
|
| 3 |
+
sqlalchemy==2.0.25
|
| 4 |
+
pandas==2.2.0
|
| 5 |
+
playwright==1.41.0
|
| 6 |
+
boto3==1.34.0
|
app/tasks/scenarios/dependency/scenario_018/ground_truth.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_018",
|
| 3 |
+
"description": "Node.js project with deprecated packages, known CVEs, and pre/postinstall hooks",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Identify deprecated packages and their replacements",
|
| 6 |
+
"Audit lifecycle scripts (pre/postinstall) for supply-chain exposure",
|
| 7 |
+
"Map versions to known CVEs"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "package.json",
|
| 12 |
+
"line": 9,
|
| 13 |
+
"rule_id": "DEP-019",
|
| 14 |
+
"severity": "medium",
|
| 15 |
+
"description": "left-pad: famously the package whose 2016 unpublishing broke half of npm. Functionality is trivially replaceable with String.prototype.padStart. Continued use is a code smell.",
|
| 16 |
+
"match_key": "left-pad",
|
| 17 |
+
"category": "trivial_dependency"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "package.json",
|
| 21 |
+
"line": 10,
|
| 22 |
+
"rule_id": "DEP-020",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "request: officially deprecated in February 2020. No further updates including for security. Use undici, axios, or native fetch.",
|
| 25 |
+
"match_key": "\"request\":",
|
| 26 |
+
"category": "deprecated_package"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "package.json",
|
| 30 |
+
"line": 11,
|
| 31 |
+
"rule_id": "DEP-005",
|
| 32 |
+
"severity": "critical",
|
| 33 |
+
"description": "node-fetch 2.6.1 has CVE-2022-0235 (exfiltration of secure headers in cross-domain redirects) \u2014 patched in 2.6.7. Also node-fetch 2.x is in maintenance-only mode; 3.x is ESM-only.",
|
| 34 |
+
"match_key": "node-fetch",
|
| 35 |
+
"category": "known_cve"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "package.json",
|
| 39 |
+
"line": 13,
|
| 40 |
+
"rule_id": "DEP-005",
|
| 41 |
+
"severity": "critical",
|
| 42 |
+
"description": "tar 4.4.0 has CVE-2021-32803 (arbitrary file write via symlink) and CVE-2021-37701 (arbitrary file overwrite). Patched in 4.4.18+, 5.0.10+, 6.1.9+.",
|
| 43 |
+
"match_key": "\"tar\": \"4.4.0\"",
|
| 44 |
+
"category": "known_cve"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "package.json",
|
| 48 |
+
"line": 14,
|
| 49 |
+
"rule_id": "DEP-005",
|
| 50 |
+
"severity": "high",
|
| 51 |
+
"description": "ws 6.2.0 has CVE-2024-37890 (DoS via prototype pollution). Patched in 8.17.1, 7.5.10. 6.x is no longer maintained.",
|
| 52 |
+
"match_key": "\"ws\":",
|
| 53 |
+
"category": "known_cve"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"file": "package.json",
|
| 57 |
+
"line": 15,
|
| 58 |
+
"rule_id": "DEP-005",
|
| 59 |
+
"severity": "high",
|
| 60 |
+
"description": "minimatch 3.0.4 has CVE-2022-3517 (ReDoS). Patched in 3.0.5+ and 5.0+. The 3.x version is widely depended-on but vulnerable to crafted patterns.",
|
| 61 |
+
"match_key": "minimatch",
|
| 62 |
+
"category": "known_cve"
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"file": "package.json",
|
| 66 |
+
"line": 4,
|
| 67 |
+
"rule_id": "DEP-011",
|
| 68 |
+
"severity": "high",
|
| 69 |
+
"description": "preinstall and postinstall scripts execute on every npm install. They run with the developer's full filesystem access. Audit ./scripts/setup.js and ./scripts/finalize.js \u2014 these are common locations for supply-chain compromise. Consider running with --ignore-scripts in CI.",
|
| 70 |
+
"match_key": "preinstall",
|
| 71 |
+
"category": "lifecycle_scripts_audit"
|
| 72 |
+
}
|
| 73 |
+
],
|
| 74 |
+
"initial_files": [
|
| 75 |
+
"package.json"
|
| 76 |
+
],
|
| 77 |
+
"available_files": []
|
| 78 |
+
}
|
app/tasks/scenarios/dependency/scenario_018/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "build-tool",
|
| 3 |
+
"scripts": {
|
| 4 |
+
"preinstall": "node ./scripts/setup.js",
|
| 5 |
+
"postinstall": "node ./scripts/finalize.js",
|
| 6 |
+
"test": "mocha"
|
| 7 |
+
},
|
| 8 |
+
"dependencies": {
|
| 9 |
+
"left-pad": "1.3.0",
|
| 10 |
+
"request": "2.88.2",
|
| 11 |
+
"node-fetch": "2.6.1",
|
| 12 |
+
"lodash.merge": "4.6.1",
|
| 13 |
+
"tar": "4.4.0",
|
| 14 |
+
"ws": "6.2.0",
|
| 15 |
+
"minimatch": "3.0.4"
|
| 16 |
+
}
|
| 17 |
+
}
|
app/tasks/scenarios/dependency/scenario_019/ground_truth.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_019",
|
| 3 |
+
"description": "Repository contains packages whose names match recent malicious-package campaigns (cryptominers, credential stealers)",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Spot generic 'helpers/utils/tools' suffixes \u2014 common malware-package naming",
|
| 6 |
+
"Cross-reference package names against malicious-package databases (Sonatype, Snyk advisories)",
|
| 7 |
+
"Check for crypto/wallet keywords"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 2,
|
| 13 |
+
"rule_id": "DEP-021",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "discord-utils-tools matches a documented Sonatype malicious-package report \u2014 discord token-stealer payload disguised as a utility library. Fake discord-* packages are a long-running campaign.",
|
| 16 |
+
"match_key": "discord-utils-tools",
|
| 17 |
+
"category": "malicious_known"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 4,
|
| 22 |
+
"rule_id": "DEP-021",
|
| 23 |
+
"severity": "critical",
|
| 24 |
+
"description": "async-tools-py: matches PyPI malicious-package patterns reported in 2023 \u2014 wallet-stealing payload via ctypes, targeting MetaMask and crypto extensions. Generic 'async-tools' name is a pattern.",
|
| 25 |
+
"match_key": "async-tools-py",
|
| 26 |
+
"category": "malicious_known"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 5,
|
| 31 |
+
"rule_id": "DEP-021",
|
| 32 |
+
"severity": "critical",
|
| 33 |
+
"description": "crypto-mining-helpers: package name is itself a red flag \u2014 legitimate libraries are not named after their malicious payload. This and similar 'crypto-mining-*' packages have been removed from PyPI as malicious.",
|
| 34 |
+
"match_key": "crypto-mining-helpers",
|
| 35 |
+
"category": "malicious_explicit"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 7,
|
| 40 |
+
"rule_id": "DEP-021",
|
| 41 |
+
"severity": "critical",
|
| 42 |
+
"description": "slack-py-tools: matches the 'slack-utility' malicious-package campaign \u2014 webhook URL exfiltration. Generic Slack-tooling names are abused for token harvesting.",
|
| 43 |
+
"match_key": "slack-py-tools",
|
| 44 |
+
"category": "malicious_known"
|
| 45 |
+
}
|
| 46 |
+
],
|
| 47 |
+
"initial_files": [
|
| 48 |
+
"requirements.txt"
|
| 49 |
+
],
|
| 50 |
+
"available_files": []
|
| 51 |
+
}
|
app/tasks/scenarios/dependency/scenario_019/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Application dependencies
|
| 2 |
+
discord-utils-tools==1.4.2
|
| 3 |
+
flask==3.0.0
|
| 4 |
+
async-tools-py==2.1.0
|
| 5 |
+
crypto-mining-helpers==0.5.0
|
| 6 |
+
requests==2.31.0
|
| 7 |
+
slack-py-tools==1.0.5
|
app/tasks/scenarios/dependency/scenario_020/ground_truth.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_020",
|
| 3 |
+
"description": "Development tooling pinned to old versions \u2014 CI uses these, so vulns matter",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Map dev tools to known CVEs",
|
| 6 |
+
"Check for compatibility with modern Python versions",
|
| 7 |
+
"Identify dev tools that execute downloaded code"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements-dev.txt",
|
| 12 |
+
"line": 2,
|
| 13 |
+
"rule_id": "DEP-005",
|
| 14 |
+
"severity": "medium",
|
| 15 |
+
"description": "pytest 5.4.3 is from 2020. pytest 8.x is current. While dev-only, pytest plugins can have remote-code-execution implications during test discovery \u2014 not as critical as runtime deps but still worth updating.",
|
| 16 |
+
"match_key": "pytest==5.4",
|
| 17 |
+
"category": "outdated_devtool"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements-dev.txt",
|
| 21 |
+
"line": 4,
|
| 22 |
+
"rule_id": "DEP-015",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "black==19.10b0 is a beta release. Stable black has been available since 21.0+. Beta in a dev requirements file is a code smell but rarely a security issue.",
|
| 25 |
+
"match_key": "black==19.10b",
|
| 26 |
+
"category": "beta_in_prod"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements-dev.txt",
|
| 30 |
+
"line": 5,
|
| 31 |
+
"rule_id": "DEP-005",
|
| 32 |
+
"severity": "low",
|
| 33 |
+
"description": "flake8 3.7.9 (2019) \u2014 current is 7.x. Mostly cosmetic but pyflakes/pycodestyle rules have evolved.",
|
| 34 |
+
"match_key": "flake8==3.7",
|
| 35 |
+
"category": "outdated_devtool"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements-dev.txt",
|
| 39 |
+
"line": 7,
|
| 40 |
+
"rule_id": "DEP-005",
|
| 41 |
+
"severity": "medium",
|
| 42 |
+
"description": "tox 3.14.6 has CVE-2018-19919 (path traversal). Patched long ago; upgrade to 4.x.",
|
| 43 |
+
"match_key": "tox==3.14",
|
| 44 |
+
"category": "known_cve"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "requirements-dev.txt",
|
| 48 |
+
"line": 10,
|
| 49 |
+
"rule_id": "DEP-005",
|
| 50 |
+
"severity": "high",
|
| 51 |
+
"description": "Sphinx 2.4.4 has multiple historical CVEs and supports old Python only. Sphinx 7.x is current. Documentation builders that fetch external resources can be a supply-chain entry point.",
|
| 52 |
+
"match_key": "sphinx==2.4",
|
| 53 |
+
"category": "outdated_devtool"
|
| 54 |
+
}
|
| 55 |
+
],
|
| 56 |
+
"initial_files": [
|
| 57 |
+
"requirements-dev.txt"
|
| 58 |
+
],
|
| 59 |
+
"available_files": []
|
| 60 |
+
}
|
app/tasks/scenarios/dependency/scenario_020/requirements-dev.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Development and test dependencies
|
| 2 |
+
pytest==5.4.3
|
| 3 |
+
pytest-django==3.10.0
|
| 4 |
+
black==19.10b0
|
| 5 |
+
flake8==3.7.9
|
| 6 |
+
coverage==4.5.4
|
| 7 |
+
tox==3.14.6
|
| 8 |
+
mypy==0.770
|
| 9 |
+
isort==4.3.21
|
| 10 |
+
sphinx==2.4.4
|
app/tasks/scenarios/dependency/scenario_021/environment.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: ml-env
|
| 2 |
+
channels:
|
| 3 |
+
- defaults
|
| 4 |
+
- pytorch
|
| 5 |
+
- nvidia
|
| 6 |
+
dependencies:
|
| 7 |
+
- python=3.11
|
| 8 |
+
- numpy=1.24
|
| 9 |
+
- scipy=1.10
|
| 10 |
+
- pytorch=2.0
|
| 11 |
+
- cudatoolkit=11.8
|
| 12 |
+
- pip:
|
| 13 |
+
- tensorflow==2.13.0
|
| 14 |
+
- keras-cv==0.7.0
|
app/tasks/scenarios/dependency/scenario_021/ground_truth.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_021",
|
| 3 |
+
"description": "Mixed pip + conda environment with conflicting versions of the same packages",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Check for conflicting versions across environments",
|
| 6 |
+
"Verify package availability in declared channels",
|
| 7 |
+
"Spot pip/conda mixing pitfalls (one overrides the other)"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 1,
|
| 13 |
+
"rule_id": "DEP-012",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Mixed pip (requirements.txt) and conda (environment.yml) declare DIFFERENT versions of numpy (1.26 vs 1.24), scipy (1.11 vs 1.10), pytorch (2.1 vs 2.0). Whichever runs second 'wins' but creates fragile installs. Standardize on one tool.",
|
| 16 |
+
"match_key": "mixed_pip_conda",
|
| 17 |
+
"category": "manifest_drift"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 3,
|
| 22 |
+
"rule_id": "DEP-022",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "PyPI package 'pytorch' is NOT the canonical PyTorch \u2014 that's installed via 'torch' on PyPI ('pytorch' the PyPI name has been a placeholder/different project). On conda the channel name is 'pytorch'. Mixing these conventions causes the wrong package to install.",
|
| 25 |
+
"match_key": "pytorch==2.1",
|
| 26 |
+
"category": "wrong_canonical_name"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "environment.yml",
|
| 30 |
+
"line": 11,
|
| 31 |
+
"rule_id": "DEP-023",
|
| 32 |
+
"severity": "medium",
|
| 33 |
+
"description": "cudatoolkit=11.8 in conda environment file. CUDA versions must align with PyTorch and TensorFlow build constraints. Mismatch silently degrades to CPU mode at runtime \u2014 wastes GPU.",
|
| 34 |
+
"match_key": "cudatoolkit=11.8",
|
| 35 |
+
"category": "cuda_mismatch_risk"
|
| 36 |
+
}
|
| 37 |
+
],
|
| 38 |
+
"initial_files": [
|
| 39 |
+
"requirements.txt",
|
| 40 |
+
"environment.yml"
|
| 41 |
+
],
|
| 42 |
+
"available_files": []
|
| 43 |
+
}
|
app/tasks/scenarios/dependency/scenario_021/requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
numpy==1.26.0
|
| 2 |
+
scipy==1.11.0
|
| 3 |
+
pytorch==2.1.0
|
app/tasks/scenarios/dependency/scenario_022/ground_truth.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_022",
|
| 3 |
+
"description": "Frontend project with multiple JS-ecosystem CVEs",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Check each version for known CVEs",
|
| 6 |
+
"Identify deprecated packages with no current security maintenance",
|
| 7 |
+
"Audit dev/build dependencies that affect distributed code"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "package.json",
|
| 12 |
+
"line": 4,
|
| 13 |
+
"rule_id": "DEP-005",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Next.js 12.0.0 has CVE-2024-34351 (SSRF in Server Actions), CVE-2023-46298 (cache poisoning), CVE-2024-46982 (cache poisoning). Multiple critical issues fixed in 14.2.10+. Major version upgrade required.",
|
| 16 |
+
"match_key": "\"next\": \"12.0.0\"",
|
| 17 |
+
"category": "known_cve"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "package.json",
|
| 21 |
+
"line": 7,
|
| 22 |
+
"rule_id": "DEP-005",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "lodash 4.17.15 has CVE-2019-10744 (prototype pollution in defaultsDeep) \u2014 patched in 4.17.12+, but additional issues followed. 4.17.21+ resolves all known prototype-pollution CVEs.",
|
| 25 |
+
"match_key": "lodash",
|
| 26 |
+
"category": "known_cve"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "package.json",
|
| 30 |
+
"line": 8,
|
| 31 |
+
"rule_id": "DEP-005",
|
| 32 |
+
"severity": "critical",
|
| 33 |
+
"description": "axios 0.18.0 has CVE-2019-10742 (DoS via large response body) and CVE-2020-28168 (SSRF). Patched in 0.21.1 and 1.x.",
|
| 34 |
+
"match_key": "\"axios\": \"0.18.0\"",
|
| 35 |
+
"category": "known_cve"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "package.json",
|
| 39 |
+
"line": 9,
|
| 40 |
+
"rule_id": "DEP-020",
|
| 41 |
+
"severity": "high",
|
| 42 |
+
"description": "moment.js: officially in maintenance mode since Sep 2020 \u2014 'consider alternatives' per maintainers. Use date-fns, dayjs, or Temporal API. Bundle size and lack of features motivated deprecation.",
|
| 43 |
+
"match_key": "\"moment\":",
|
| 44 |
+
"category": "deprecated_package"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "package.json",
|
| 48 |
+
"line": 10,
|
| 49 |
+
"rule_id": "DEP-020",
|
| 50 |
+
"severity": "critical",
|
| 51 |
+
"description": "node-sass: officially deprecated in October 2020 in favor of dart-sass ('sass' on npm). Old node-sass has unmaintained C++ build dependencies that frequently break on new Node.js versions.",
|
| 52 |
+
"match_key": "node-sass",
|
| 53 |
+
"category": "deprecated_package"
|
| 54 |
+
}
|
| 55 |
+
],
|
| 56 |
+
"initial_files": [
|
| 57 |
+
"package.json"
|
| 58 |
+
],
|
| 59 |
+
"available_files": []
|
| 60 |
+
}
|
app/tasks/scenarios/dependency/scenario_022/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend-app",
|
| 3 |
+
"dependencies": {
|
| 4 |
+
"next": "12.0.0",
|
| 5 |
+
"react": "17.0.2",
|
| 6 |
+
"react-dom": "17.0.2",
|
| 7 |
+
"lodash": "4.17.15",
|
| 8 |
+
"axios": "0.18.0",
|
| 9 |
+
"moment": "2.29.0",
|
| 10 |
+
"node-sass": "4.13.0",
|
| 11 |
+
"browserslist": "4.16.0"
|
| 12 |
+
}
|
| 13 |
+
}
|
app/tasks/scenarios/dependency/scenario_023/ground_truth.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_023",
|
| 3 |
+
"description": "Mix of private (internal) and public package names without explicit index restriction \u2014 classic dependency confusion attack vector",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Identify packages that should be private and verify index configuration",
|
| 6 |
+
"Check for index-url vs extra-index-url priority",
|
| 7 |
+
"Audit naming conventions for private packages (scoped vs unscoped)"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "pip.conf",
|
| 12 |
+
"line": 2,
|
| 13 |
+
"rule_id": "DEP-024",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "extra-index-url = https://pypi.org/simple combined with private-package names creates dependency-confusion exposure (Birsan-style attack). Pip queries ALL configured indexes for each package; a public package on PyPI with a higher version number than the internal one is preferred. Attackers register internal-looking names with version 99.0.0. Mitigation: use index-url (not extra-index-url) pointing at internal mirror that proxies PyPI for known-public, or use scoped names.",
|
| 16 |
+
"match_key": "extra-index-url",
|
| 17 |
+
"category": "dependency_confusion"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 2,
|
| 22 |
+
"rule_id": "DEP-024",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "'acme-internal-utils' is named to suggest internal use but uses the public PyPI namespace. Combined with extra-index-url (line in pip.conf), an attacker can publish a package with this exact name on PyPI at version 99.0.0; pip will install the attacker's package preferentially. Use distinct prefix like '@acme/internal-utils' or pin internal index explicitly.",
|
| 25 |
+
"match_key": "acme-internal-utils",
|
| 26 |
+
"category": "naming_collision_risk"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 3,
|
| 31 |
+
"rule_id": "DEP-024",
|
| 32 |
+
"severity": "high",
|
| 33 |
+
"description": "Same issue: 'internal-billing-helpers' is in PyPI namespace and exposes dependency confusion.",
|
| 34 |
+
"match_key": "internal-billing-helpers",
|
| 35 |
+
"category": "naming_collision_risk"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 5,
|
| 40 |
+
"rule_id": "DEP-024",
|
| 41 |
+
"severity": "high",
|
| 42 |
+
"description": "'private-data-pipeline' \u2014 same dependency confusion exposure.",
|
| 43 |
+
"match_key": "private-data-pipeline",
|
| 44 |
+
"category": "naming_collision_risk"
|
| 45 |
+
}
|
| 46 |
+
],
|
| 47 |
+
"initial_files": [
|
| 48 |
+
"requirements.txt",
|
| 49 |
+
"pip.conf"
|
| 50 |
+
],
|
| 51 |
+
"available_files": []
|
| 52 |
+
}
|
app/tasks/scenarios/dependency/scenario_023/pip.conf
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[global]
|
| 2 |
+
extra-index-url = https://pypi.org/simple
|
app/tasks/scenarios/dependency/scenario_023/requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Mixed private and public dependencies — confusion attack risk
|
| 2 |
+
acme-internal-utils==2.5.0
|
| 3 |
+
internal-billing-helpers==1.3.0
|
| 4 |
+
acme-ml-tools==0.4.0
|
| 5 |
+
private-data-pipeline==3.0.0
|
| 6 |
+
flask==3.0.0
|
app/tasks/scenarios/dependency/scenario_024/ground_truth.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "dep_024",
|
| 3 |
+
"description": "Stale pinned dependencies \u2014 every one has multiple security releases since",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Identify each package's release recency vs current",
|
| 6 |
+
"Spot the cumulative security debt",
|
| 7 |
+
"Recommend automated update strategy (Dependabot, Renovate)"
|
| 8 |
+
],
|
| 9 |
+
"ground_truth": [
|
| 10 |
+
{
|
| 11 |
+
"file": "requirements.txt",
|
| 12 |
+
"line": 2,
|
| 13 |
+
"rule_id": "DEP-005",
|
| 14 |
+
"severity": "critical",
|
| 15 |
+
"description": "Django 3.2.0 has many CVEs since 2021: CVE-2021-44420 (SQL injection via QuerySet.annotate), CVE-2022-22818 (template injection), CVE-2023-43665 (DoS via Truncator). Patched in subsequent 3.2.x releases \u2014 but 3.2.x reached EOL April 2024. Upgrade to 4.2 LTS or 5.x.",
|
| 16 |
+
"match_key": "django==3.2.0",
|
| 17 |
+
"category": "known_cve"
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"file": "requirements.txt",
|
| 21 |
+
"line": 5,
|
| 22 |
+
"rule_id": "DEP-005",
|
| 23 |
+
"severity": "high",
|
| 24 |
+
"description": "psycopg2 2.8.0 has multiple CVEs and lacks support for newer PG versions. Use psycopg2-binary 2.9.9+ or migrate to psycopg 3.x.",
|
| 25 |
+
"match_key": "psycopg2-binary==2.8",
|
| 26 |
+
"category": "known_cve"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"file": "requirements.txt",
|
| 30 |
+
"line": 6,
|
| 31 |
+
"rule_id": "DEP-005",
|
| 32 |
+
"severity": "high",
|
| 33 |
+
"description": "gunicorn 20.0.0 has CVE-2024-1135 (HTTP request smuggling). Patched in 22.0.0+.",
|
| 34 |
+
"match_key": "gunicorn==20.0",
|
| 35 |
+
"category": "known_cve"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"file": "requirements.txt",
|
| 39 |
+
"line": 7,
|
| 40 |
+
"rule_id": "DEP-005",
|
| 41 |
+
"severity": "high",
|
| 42 |
+
"description": "gevent 20.6.0 has known issues with TLS handling on modern Python versions. Upgrade to 24.x.",
|
| 43 |
+
"match_key": "gevent==20.6",
|
| 44 |
+
"category": "outdated_severe"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"file": "requirements.txt",
|
| 48 |
+
"line": 8,
|
| 49 |
+
"rule_id": "DEP-005",
|
| 50 |
+
"severity": "medium",
|
| 51 |
+
"description": "sentry-sdk 0.19.0 (2020) \u2014 modern is 2.x. Old SDK miss security context fixes and have known issues with redaction. Sentry's own deprecation notices explicitly call out 1.x and below.",
|
| 52 |
+
"match_key": "sentry-sdk==0.19",
|
| 53 |
+
"category": "outdated_severe"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"file": "requirements.txt",
|
| 57 |
+
"line": 9,
|
| 58 |
+
"rule_id": "DEP-005",
|
| 59 |
+
"severity": "low",
|
| 60 |
+
"description": "prometheus-client 0.9.0 \u2014 current is 0.20+. Mostly cosmetic but newer releases improve label-key handling and async behavior.",
|
| 61 |
+
"match_key": "prometheus-client==0.9",
|
| 62 |
+
"category": "outdated"
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"file": "requirements.txt",
|
| 66 |
+
"line": 1,
|
| 67 |
+
"rule_id": "DEP-025",
|
| 68 |
+
"severity": "high",
|
| 69 |
+
"description": "Architectural: pin-and-forget pattern with no automated update process is the root cause. Adopt Dependabot, Renovate, or pip-audit in CI; review and merge security PRs weekly. The cumulative security debt of stale dependencies dwarfs individual CVE fixes.",
|
| 70 |
+
"match_key": "no_update_strategy",
|
| 71 |
+
"category": "process_anti_pattern"
|
| 72 |
+
}
|
| 73 |
+
],
|
| 74 |
+
"initial_files": [
|
| 75 |
+
"requirements.txt"
|
| 76 |
+
],
|
| 77 |
+
"available_files": []
|
| 78 |
+
}
|
app/tasks/scenarios/dependency/scenario_024/requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Last touched 3 years ago, no automated update process
|
| 2 |
+
django==3.2.0
|
| 3 |
+
celery==5.0.0
|
| 4 |
+
redis==3.5.0
|
| 5 |
+
psycopg2-binary==2.8.0
|
| 6 |
+
gunicorn==20.0.0
|
| 7 |
+
gevent==20.6.0
|
| 8 |
+
sentry-sdk==0.19.0
|
| 9 |
+
prometheus-client==0.9.0
|
app/tasks/scenarios/iac/scenario_006/deployment.yaml
CHANGED
|
@@ -33,7 +33,7 @@ spec:
|
|
| 33 |
- name: DB_PASSWORD
|
| 34 |
value: "s3cureP@ssw0rd!2024"
|
| 35 |
- name: STRIPE_API_KEY
|
| 36 |
-
value: "
|
| 37 |
- name: JWT_SECRET
|
| 38 |
value: "myapp-jwt-signing-key-prod-7f8a9b"
|
| 39 |
- name: ENVIRONMENT
|
|
|
|
| 33 |
- name: DB_PASSWORD
|
| 34 |
value: "s3cureP@ssw0rd!2024"
|
| 35 |
- name: STRIPE_API_KEY
|
| 36 |
+
value: "sk_test_EXAMPLE_FAKE_KEY_FOR_TESTING"
|
| 37 |
- name: JWT_SECRET
|
| 38 |
value: "myapp-jwt-signing-key-prod-7f8a9b"
|
| 39 |
- name: ENVIRONMENT
|
app/tasks/scenarios/iac/scenario_007/ground_truth.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"scenario_id": "iac_007",
|
| 3 |
+
"description": "RDS PostgreSQL + EBS data volume \u2014 review production database posture",
|
| 4 |
+
"review_checklist": [
|
| 5 |
+
"Check for publicly accessible databases",
|
| 6 |
+
"Verify encryption at rest on all storage",
|
| 7 |
+
"Check security group ingress rules",
|
| 8 |
+
"Verify backup retention policies",
|
| 9 |
+
"Check for hardcoded credentials"
|
| 10 |
+
],
|
| 11 |
+
"ground_truth": [
|
| 12 |
+
{
|
| 13 |
+
"file": "main.tf",
|
| 14 |
+
"line": 11,
|
| 15 |
+
"rule_id": "IAC-005",
|
| 16 |
+
"severity": "critical",
|
| 17 |
+
"description": "RDS instance hardcodes admin password in source \u2014 use AWS Secrets Manager or terraform variables",
|
| 18 |
+
"match_key": "aws_db_instance.analytics|hardcoded_secret",
|
| 19 |
+
"category": "hardcoded_secret"
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"file": "main.tf",
|
| 23 |
+
"line": 12,
|
| 24 |
+
"rule_id": "IAC-001",
|
| 25 |
+
"severity": "critical",
|
| 26 |
+
"description": "RDS instance is publicly_accessible=true \u2014 exposes database directly to internet",
|
| 27 |
+
"match_key": "aws_db_instance.analytics|public_access",
|
| 28 |
+
"category": "public_access"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"file": "main.tf",
|
| 32 |
+
"line": 13,
|
| 33 |
+
"rule_id": "IAC-002",
|
| 34 |
+
"severity": "high",
|
| 35 |
+
"description": "RDS storage_encrypted=false \u2014 data at rest unencrypted, fails most compliance frameworks",
|
| 36 |
+
"match_key": "aws_db_instance.analytics|encryption_at_rest",
|
| 37 |
+
"category": "encryption_at_rest"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"file": "main.tf",
|
| 41 |
+
"line": 14,
|
| 42 |
+
"rule_id": "IAC-006",
|
| 43 |
+
"severity": "high",
|
| 44 |
+
"description": "RDS backup_retention_period=0 disables automated backups \u2014 no recovery on failure",
|
| 45 |
+
"match_key": "aws_db_instance.analytics|backups_disabled",
|
| 46 |
+
"category": "backups_disabled"
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"file": "main.tf",
|
| 50 |
+
"line": 25,
|
| 51 |
+
"rule_id": "IAC-004",
|
| 52 |
+
"severity": "critical",
|
| 53 |
+
"description": "Security group allows PostgreSQL (5432) from 0.0.0.0/0 \u2014 entire internet has database access",
|
| 54 |
+
"match_key": "aws_security_group.db_sg|permissive_security_group",
|
| 55 |
+
"category": "permissive_security_group"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"file": "main.tf",
|
| 59 |
+
"line": 33,
|
| 60 |
+
"rule_id": "IAC-002",
|
| 61 |
+
"severity": "high",
|
| 62 |
+
"description": "EBS volume encrypted=false \u2014 disk-level data unencrypted on EC2 instance",
|
| 63 |
+
"match_key": "aws_ebs_volume.data|encryption_at_rest",
|
| 64 |
+
"category": "encryption_at_rest"
|
| 65 |
+
}
|
| 66 |
+
],
|
| 67 |
+
"initial_files": [
|
| 68 |
+
"main.tf"
|
| 69 |
+
],
|
| 70 |
+
"available_files": []
|
| 71 |
+
}
|
app/tasks/scenarios/iac/scenario_007/main.tf
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
provider "aws" { region = "us-east-1" }
|
| 2 |
+
|
| 3 |
+
resource "aws_db_instance" "analytics" {
|
| 4 |
+
identifier = "analytics-prod"
|
| 5 |
+
engine = "postgres"
|
| 6 |
+
engine_version = "13.7"
|
| 7 |
+
instance_class = "db.t3.medium"
|
| 8 |
+
allocated_storage = 100
|
| 9 |
+
username = "admin"
|
| 10 |
+
password = "changeme123"
|
| 11 |
+
publicly_accessible = true
|
| 12 |
+
storage_encrypted = false
|
| 13 |
+
backup_retention_period = 0
|
| 14 |
+
skip_final_snapshot = true
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
resource "aws_security_group" "db_sg" {
|
| 18 |
+
name = "db-sg"
|
| 19 |
+
description = "DB security group"
|
| 20 |
+
|
| 21 |
+
ingress {
|
| 22 |
+
from_port = 5432
|
| 23 |
+
to_port = 5432
|
| 24 |
+
protocol = "tcp"
|
| 25 |
+
cidr_blocks = ["0.0.0.0/0"]
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
resource "aws_ebs_volume" "data" {
|
| 30 |
+
availability_zone = "us-east-1a"
|
| 31 |
+
size = 200
|
| 32 |
+
encrypted = false
|
| 33 |
+
type = "gp3"
|
| 34 |
+
}
|
app/tasks/scenarios/iac/scenario_008/deployment.yaml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
apiVersion: apps/v1
|
| 2 |
+
kind: Deployment
|
| 3 |
+
metadata:
|
| 4 |
+
name: webapp
|
| 5 |
+
namespace: default
|
| 6 |
+
spec:
|
| 7 |
+
replicas: 3
|
| 8 |
+
selector:
|
| 9 |
+
matchLabels:
|
| 10 |
+
app: webapp
|
| 11 |
+
template:
|
| 12 |
+
metadata:
|
| 13 |
+
labels:
|
| 14 |
+
app: webapp
|
| 15 |
+
spec:
|
| 16 |
+
hostNetwork: true
|
| 17 |
+
hostPID: true
|
| 18 |
+
containers:
|
| 19 |
+
- name: app
|
| 20 |
+
image: company/webapp:latest
|
| 21 |
+
securityContext:
|
| 22 |
+
privileged: true
|
| 23 |
+
runAsUser: 0
|
| 24 |
+
allowPrivilegeEscalation: true
|
| 25 |
+
env:
|
| 26 |
+
- name: DATABASE_PASSWORD
|
| 27 |
+
value: "supersecret123"
|
| 28 |
+
- name: API_KEY
|
| 29 |
+
value: "sk-prod-abc123def456"
|
| 30 |
+
volumeMounts:
|
| 31 |
+
- name: host-root
|
| 32 |
+
mountPath: /host
|
| 33 |
+
ports:
|
| 34 |
+
- containerPort: 8080
|
| 35 |
+
volumes:
|
| 36 |
+
- name: host-root
|
| 37 |
+
hostPath:
|
| 38 |
+
path: /
|
| 39 |
+
type: Directory
|