@vigolium/piolium 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/agents/access-auditor.md +300 -0
- package/agents/assumption-breaker.md +154 -0
- package/agents/attack-designer.md +116 -0
- package/agents/code-scanner.md +139 -0
- package/agents/concurrency-auditor.md +238 -0
- package/agents/confirm-writer.md +257 -0
- package/agents/context-reviewer.md +274 -0
- package/agents/cross-verifier.md +165 -0
- package/agents/cve-scout.md +381 -0
- package/agents/env-builder.md +282 -0
- package/agents/env-profiler.md +205 -0
- package/agents/evidence-collector.md +140 -0
- package/agents/finding-grader.md +142 -0
- package/agents/finding-writer.md +148 -0
- package/agents/flow-tracer.md +106 -0
- package/agents/goal-backtracer.md +146 -0
- package/agents/history-miner.md +467 -0
- package/agents/independent-verifier.md +118 -0
- package/agents/intent-mapper.md +183 -0
- package/agents/longshot-collector.md +128 -0
- package/agents/longshot-prober.md +126 -0
- package/agents/patch-auditor.md +73 -0
- package/agents/poc-author.md +124 -0
- package/agents/poc-runner.md +194 -0
- package/agents/probe-lead.md +269 -0
- package/agents/red-challenger.md +101 -0
- package/agents/report-composer.md +208 -0
- package/agents/review-adjudicator.md +216 -0
- package/agents/spec-auditor.md +155 -0
- package/agents/taint-tracer.md +265 -0
- package/agents/test-locator.md +209 -0
- package/agents/threat-modeler.md +132 -0
- package/agents/variant-scanner.md +108 -0
- package/agents/variant-spotter.md +110 -0
- package/bin/piolium.mjs +376 -0
- package/extensions/piolium/_vendor/yaml.bundle.d.mts +6 -0
- package/extensions/piolium/_vendor/yaml.bundle.mjs +139 -0
- package/extensions/piolium/agent-runner.ts +322 -0
- package/extensions/piolium/agents.ts +266 -0
- package/extensions/piolium/audit-state.ts +522 -0
- package/extensions/piolium/bundled-resources.ts +97 -0
- package/extensions/piolium/candidate-scan.ts +966 -0
- package/extensions/piolium/command-target.ts +177 -0
- package/extensions/piolium/console-stream.ts +57 -0
- package/extensions/piolium/export-results.ts +380 -0
- package/extensions/piolium/findings.ts +448 -0
- package/extensions/piolium/heartbeat.ts +182 -0
- package/extensions/piolium/help.ts +234 -0
- package/extensions/piolium/index.ts +1865 -0
- package/extensions/piolium/longshot.ts +530 -0
- package/extensions/piolium/matcher-suggestions.ts +196 -0
- package/extensions/piolium/matcher-utils.ts +83 -0
- package/extensions/piolium/modes/balanced.ts +750 -0
- package/extensions/piolium/modes/confirm-bootstrap.ts +186 -0
- package/extensions/piolium/modes/confirm.ts +697 -0
- package/extensions/piolium/modes/deep.ts +917 -0
- package/extensions/piolium/modes/diff.ts +177 -0
- package/extensions/piolium/modes/lite.ts +540 -0
- package/extensions/piolium/modes/longshot.ts +595 -0
- package/extensions/piolium/modes/merge.ts +204 -0
- package/extensions/piolium/modes/phase-runner.ts +267 -0
- package/extensions/piolium/modes/reinvest.ts +546 -0
- package/extensions/piolium/modes/revisit.ts +279 -0
- package/extensions/piolium/modes.ts +48 -0
- package/extensions/piolium/phase-labels.ts +123 -0
- package/extensions/piolium/phase-status-strip.ts +92 -0
- package/extensions/piolium/prompt-prefix-editor.ts +39 -0
- package/extensions/piolium/providers/anthropic-vertex.ts +836 -0
- package/extensions/piolium/recon.ts +409 -0
- package/extensions/piolium/result-stats.ts +105 -0
- package/extensions/piolium/retry.ts +120 -0
- package/extensions/piolium/scheduler.ts +212 -0
- package/extensions/piolium/secrets.ts +368 -0
- package/extensions/piolium/tools/web-tools.ts +148 -0
- package/package.json +77 -0
- package/skills/agentic-actions-auditor/SKILL.md +327 -0
- package/skills/agentic-actions-auditor/references/action-profiles.md +186 -0
- package/skills/agentic-actions-auditor/references/cross-file-resolution.md +209 -0
- package/skills/agentic-actions-auditor/references/foundations.md +94 -0
- package/skills/agentic-actions-auditor/references/vector-a-env-var-intermediary.md +77 -0
- package/skills/agentic-actions-auditor/references/vector-b-direct-expression-injection.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-c-cli-data-fetch.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-d-pr-target-checkout.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-e-error-log-injection.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-f-subshell-expansion.md +82 -0
- package/skills/agentic-actions-auditor/references/vector-g-eval-of-ai-output.md +91 -0
- package/skills/agentic-actions-auditor/references/vector-h-dangerous-sandbox-configs.md +102 -0
- package/skills/agentic-actions-auditor/references/vector-i-wildcard-allowlists.md +88 -0
- package/skills/audit/SKILL.md +562 -0
- package/skills/audit/assets/icon.svg +7 -0
- package/skills/audit/hooks/scripts/validate_phase_output.py +550 -0
- package/skills/audit/references/adversarial-review.md +148 -0
- package/skills/audit/references/architecture-aware-sast.md +306 -0
- package/skills/audit/references/audit-workflow.md +737 -0
- package/skills/audit/references/chamber-protocol.md +384 -0
- package/skills/audit/references/creative-attack-modes.md +221 -0
- package/skills/audit/references/deep-analysis.md +273 -0
- package/skills/audit/references/domain-attack-playbooks.md +1129 -0
- package/skills/audit/references/knowledge-base-template.md +513 -0
- package/skills/audit/references/real-env-validation.md +191 -0
- package/skills/audit/references/report-templates.md +417 -0
- package/skills/audit/references/triage-and-prereqs.md +134 -0
- package/skills/audit/scripts/consolidate_drafts.py +554 -0
- package/skills/audit/scripts/partition_findings.py +152 -0
- package/skills/audit/scripts/rg-hotspots.sh +121 -0
- package/skills/audit/scripts/stamp_file_state.py +349 -0
- package/skills/code-reviewer/SKILL.md +65 -0
- package/skills/codeql/SKILL.md +281 -0
- package/skills/codeql/references/build-fixes.md +90 -0
- package/skills/codeql/references/diagnostic-query-templates.md +339 -0
- package/skills/codeql/references/extension-yaml-format.md +209 -0
- package/skills/codeql/references/important-only-suite.md +153 -0
- package/skills/codeql/references/language-details.md +207 -0
- package/skills/codeql/references/macos-arm64e-workaround.md +179 -0
- package/skills/codeql/references/performance-tuning.md +111 -0
- package/skills/codeql/references/quality-assessment.md +172 -0
- package/skills/codeql/references/ruleset-catalog.md +63 -0
- package/skills/codeql/references/run-all-suite.md +92 -0
- package/skills/codeql/references/sarif-processing.md +79 -0
- package/skills/codeql/references/threat-models.md +51 -0
- package/skills/codeql/workflows/build-database.md +280 -0
- package/skills/codeql/workflows/create-data-extensions.md +261 -0
- package/skills/codeql/workflows/run-analysis.md +301 -0
- package/skills/differential-review/SKILL.md +220 -0
- package/skills/differential-review/adversarial.md +203 -0
- package/skills/differential-review/methodology.md +234 -0
- package/skills/differential-review/patterns.md +300 -0
- package/skills/differential-review/reporting.md +369 -0
- package/skills/fp-check/SKILL.md +125 -0
- package/skills/fp-check/references/bug-class-verification.md +114 -0
- package/skills/fp-check/references/deep-verification.md +143 -0
- package/skills/fp-check/references/evidence-templates.md +91 -0
- package/skills/fp-check/references/false-positive-patterns.md +115 -0
- package/skills/fp-check/references/gate-reviews.md +27 -0
- package/skills/fp-check/references/standard-verification.md +78 -0
- package/skills/insecure-defaults/SKILL.md +117 -0
- package/skills/insecure-defaults/references/examples.md +409 -0
- package/skills/last30days/SKILL.md +444 -0
- package/skills/sarif-parsing/SKILL.md +483 -0
- package/skills/sarif-parsing/resources/jq-queries.md +162 -0
- package/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
- package/skills/security-threat-model/LICENSE.txt +201 -0
- package/skills/security-threat-model/SKILL.md +81 -0
- package/skills/security-threat-model/agents/openai.yaml +4 -0
- package/skills/security-threat-model/references/prompt-template.md +255 -0
- package/skills/security-threat-model/references/security-controls-and-assets.md +32 -0
- package/skills/semgrep/SKILL.md +212 -0
- package/skills/semgrep/references/rulesets.md +162 -0
- package/skills/semgrep/references/scan-modes.md +110 -0
- package/skills/semgrep/references/scanner-task-prompt.md +140 -0
- package/skills/semgrep/scripts/merge_sarif.py +203 -0
- package/skills/semgrep/workflows/scan-workflow.md +311 -0
- package/skills/semgrep-rule-creator/SKILL.md +168 -0
- package/skills/semgrep-rule-creator/references/quick-reference.md +202 -0
- package/skills/semgrep-rule-creator/references/workflow.md +240 -0
- package/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
- package/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
- package/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
- package/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
- package/skills/sharp-edges/SKILL.md +292 -0
- package/skills/sharp-edges/references/auth-patterns.md +252 -0
- package/skills/sharp-edges/references/case-studies.md +274 -0
- package/skills/sharp-edges/references/config-patterns.md +333 -0
- package/skills/sharp-edges/references/crypto-apis.md +190 -0
- package/skills/sharp-edges/references/lang-c.md +205 -0
- package/skills/sharp-edges/references/lang-csharp.md +285 -0
- package/skills/sharp-edges/references/lang-go.md +270 -0
- package/skills/sharp-edges/references/lang-java.md +263 -0
- package/skills/sharp-edges/references/lang-javascript.md +269 -0
- package/skills/sharp-edges/references/lang-kotlin.md +265 -0
- package/skills/sharp-edges/references/lang-php.md +245 -0
- package/skills/sharp-edges/references/lang-python.md +274 -0
- package/skills/sharp-edges/references/lang-ruby.md +273 -0
- package/skills/sharp-edges/references/lang-rust.md +272 -0
- package/skills/sharp-edges/references/lang-swift.md +287 -0
- package/skills/sharp-edges/references/language-specific.md +588 -0
- package/skills/spec-to-code-compliance/SKILL.md +357 -0
- package/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
- package/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
- package/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
- package/skills/supply-chain-risk-auditor/SKILL.md +67 -0
- package/skills/supply-chain-risk-auditor/resources/results-template.md +41 -0
- package/skills/variant-analysis/METHODOLOGY.md +327 -0
- package/skills/variant-analysis/SKILL.md +142 -0
- package/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
- package/skills/variant-analysis/resources/codeql/go.ql +69 -0
- package/skills/variant-analysis/resources/codeql/java.ql +71 -0
- package/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
- package/skills/variant-analysis/resources/codeql/python.ql +80 -0
- package/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
- package/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
- package/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
- package/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
- package/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
- package/skills/variant-analysis/resources/variant-report-template.md +75 -0
- package/skills/vuln-report/SKILL.md +137 -0
- package/skills/vuln-report/agents/openai.yaml +4 -0
- package/skills/vuln-report/references/report-template.md +135 -0
- package/skills/wooyun-legacy/SKILL.md +367 -0
- package/skills/wooyun-legacy/references/bank-penetration.md +222 -0
- package/skills/wooyun-legacy/references/checklists/command-execution-checklist.md +119 -0
- package/skills/wooyun-legacy/references/checklists/csrf-checklist.md +74 -0
- package/skills/wooyun-legacy/references/checklists/file-upload-checklist.md +108 -0
- package/skills/wooyun-legacy/references/checklists/info-disclosure-checklist.md +114 -0
- package/skills/wooyun-legacy/references/checklists/logic-flaws-checklist.md +95 -0
- package/skills/wooyun-legacy/references/checklists/misconfig-checklist.md +124 -0
- package/skills/wooyun-legacy/references/checklists/path-traversal-checklist.md +87 -0
- package/skills/wooyun-legacy/references/checklists/rce-checklist.md +93 -0
- package/skills/wooyun-legacy/references/checklists/sql-injection-checklist.md +97 -0
- package/skills/wooyun-legacy/references/checklists/ssrf-checklist.md +99 -0
- package/skills/wooyun-legacy/references/checklists/unauthorized-access-checklist.md +89 -0
- package/skills/wooyun-legacy/references/checklists/weak-password-checklist.md +115 -0
- package/skills/wooyun-legacy/references/checklists/xss-checklist.md +103 -0
- package/skills/wooyun-legacy/references/checklists/xxe-checklist.md +130 -0
- package/skills/wooyun-legacy/references/info-disclosure.md +975 -0
- package/skills/wooyun-legacy/references/logic-flaws.md +721 -0
- package/skills/wooyun-legacy/references/path-traversal.md +1191 -0
- package/skills/wooyun-legacy/references/telecom-penetration.md +156 -0
- package/skills/wooyun-legacy/references/unauthorized-access.md +980 -0
- package/skills/wooyun-legacy/references/xss.md +746 -0
- package/skills/zeroize-audit/SKILL.md +371 -0
- package/skills/zeroize-audit/configs/c.yaml +21 -0
- package/skills/zeroize-audit/configs/default.yaml +128 -0
- package/skills/zeroize-audit/configs/rust.yaml +83 -0
- package/skills/zeroize-audit/prompts/report_template.md +238 -0
- package/skills/zeroize-audit/prompts/system.md +163 -0
- package/skills/zeroize-audit/prompts/task.md +97 -0
- package/skills/zeroize-audit/references/compile-commands.md +231 -0
- package/skills/zeroize-audit/references/detection-strategy.md +191 -0
- package/skills/zeroize-audit/references/ir-analysis.md +252 -0
- package/skills/zeroize-audit/references/mcp-analysis.md +221 -0
- package/skills/zeroize-audit/references/poc-generation.md +470 -0
- package/skills/zeroize-audit/references/rust-zeroization-patterns.md +867 -0
- package/skills/zeroize-audit/schemas/input.json +83 -0
- package/skills/zeroize-audit/schemas/output.json +140 -0
- package/skills/zeroize-audit/tools/analyze_asm.sh +202 -0
- package/skills/zeroize-audit/tools/analyze_cfg.py +381 -0
- package/skills/zeroize-audit/tools/analyze_heap.sh +211 -0
- package/skills/zeroize-audit/tools/analyze_ir_semantic.py +429 -0
- package/skills/zeroize-audit/tools/diff_ir.sh +135 -0
- package/skills/zeroize-audit/tools/diff_rust_mir.sh +189 -0
- package/skills/zeroize-audit/tools/emit_asm.sh +67 -0
- package/skills/zeroize-audit/tools/emit_ir.sh +77 -0
- package/skills/zeroize-audit/tools/emit_rust_asm.sh +178 -0
- package/skills/zeroize-audit/tools/emit_rust_ir.sh +150 -0
- package/skills/zeroize-audit/tools/emit_rust_mir.sh +158 -0
- package/skills/zeroize-audit/tools/extract_compile_flags.py +284 -0
- package/skills/zeroize-audit/tools/generate_poc.py +1329 -0
- package/skills/zeroize-audit/tools/mcp/apply_confidence_gates.py +113 -0
- package/skills/zeroize-audit/tools/mcp/check_mcp.sh +68 -0
- package/skills/zeroize-audit/tools/mcp/normalize_mcp_evidence.py +125 -0
- package/skills/zeroize-audit/tools/scripts/check_llvm_patterns.py +481 -0
- package/skills/zeroize-audit/tools/scripts/check_mir_patterns.py +554 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm.py +424 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_aarch64.py +300 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_x86.py +283 -0
- package/skills/zeroize-audit/tools/scripts/find_dangerous_apis.py +375 -0
- package/skills/zeroize-audit/tools/scripts/semantic_audit.py +923 -0
- package/skills/zeroize-audit/tools/track_dataflow.sh +196 -0
- package/skills/zeroize-audit/tools/validate_rust_toolchain.sh +298 -0
- package/skills/zeroize-audit/workflows/phase-0-preflight.md +150 -0
- package/skills/zeroize-audit/workflows/phase-1-source-analysis.md +144 -0
- package/skills/zeroize-audit/workflows/phase-2-compiler-analysis.md +139 -0
- package/skills/zeroize-audit/workflows/phase-3-interim-report.md +46 -0
- package/skills/zeroize-audit/workflows/phase-4-poc-generation.md +46 -0
- package/skills/zeroize-audit/workflows/phase-5-poc-validation.md +136 -0
- package/skills/zeroize-audit/workflows/phase-6-final-report.md +44 -0
- package/skills/zeroize-audit/workflows/phase-7-test-generation.md +42 -0
- package/themes/piolium-srcery.json +94 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
6
|
+
"""
|
|
7
|
+
find_dangerous_apis.py — Token/grep-based scanner for dangerous Rust API patterns.
|
|
8
|
+
|
|
9
|
+
Scans .rs files for API calls that bypass zeroization guarantees (mem::forget,
|
|
10
|
+
Box::leak, ptr::write_bytes, etc.) and async suspension points that expose
|
|
11
|
+
secret-named locals to the heap-allocated Future state machine.
|
|
12
|
+
|
|
13
|
+
Does NOT require compilation — pure source text analysis.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
uv run find_dangerous_apis.py --src <source_dir> --out <findings.json>
|
|
17
|
+
|
|
18
|
+
Exit codes:
|
|
19
|
+
0 — ran successfully (findings may be empty)
|
|
20
|
+
1 — source directory not found
|
|
21
|
+
2 — argument error
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import json
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Sensitive name patterns (used for context filtering)
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
SENSITIVE_NAME_RE = re.compile(
|
|
35
|
+
# PascalCase type names use \b (no underscore in names like SecretKey).
|
|
36
|
+
# Lowercase keywords use (?<![a-zA-Z])...(?![a-zA-Z]) so that snake_case
|
|
37
|
+
# names like 'secret_key', 'private_key', and 'auth_token' are matched
|
|
38
|
+
# while avoiding spurious hits on words like 'monkey' or 'tokenize'.
|
|
39
|
+
r"(?i)(?:\b(Key|PrivateKey|SecretKey|SigningKey|MasterKey|HmacKey|"
|
|
40
|
+
r"Password|Passphrase|Pin|Token|AuthToken|BearerToken|ApiKey|"
|
|
41
|
+
r"Secret|SharedSecret|PreSharedKey|Nonce|Seed|Entropy|"
|
|
42
|
+
r"Credential|SessionKey|DerivedKey)\b"
|
|
43
|
+
r"|(?<![a-zA-Z])(key|secret|password|token|nonce|seed|private|master|credential)(?![a-zA-Z]))"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# Dangerous API patterns: (regex, category, severity, detail)
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
PATTERNS: list[tuple[str, str, str, str]] = [
|
|
51
|
+
(
|
|
52
|
+
r"\bmem::forget\s*\(",
|
|
53
|
+
"MISSING_SOURCE_ZEROIZE",
|
|
54
|
+
"critical",
|
|
55
|
+
"mem::forget() prevents Drop/ZeroizeOnDrop from running — secret never wiped",
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
r"\bManuallyDrop\s*::\s*new\s*\(",
|
|
59
|
+
"MISSING_SOURCE_ZEROIZE",
|
|
60
|
+
"critical",
|
|
61
|
+
"ManuallyDrop::new() suppresses automatic drop — "
|
|
62
|
+
"secret not wiped unless drop() called explicitly",
|
|
63
|
+
),
|
|
64
|
+
(
|
|
65
|
+
r"\bBox\s*::\s*leak\s*\(",
|
|
66
|
+
"MISSING_SOURCE_ZEROIZE",
|
|
67
|
+
"critical",
|
|
68
|
+
"Box::leak() — leaked allocation is never dropped or zeroed",
|
|
69
|
+
),
|
|
70
|
+
(
|
|
71
|
+
r"\bBox\s*::\s*into_raw\s*\(",
|
|
72
|
+
"MISSING_SOURCE_ZEROIZE",
|
|
73
|
+
"high",
|
|
74
|
+
"Box::into_raw() — raw pointer escapes Drop; "
|
|
75
|
+
"must call Box::from_raw() + zeroize to reclaim",
|
|
76
|
+
),
|
|
77
|
+
(
|
|
78
|
+
r"\bptr\s*::\s*write_bytes\s*\(",
|
|
79
|
+
"OPTIMIZED_AWAY_ZEROIZE",
|
|
80
|
+
"high",
|
|
81
|
+
"ptr::write_bytes() is non-volatile — LLVM may eliminate as dead store. "
|
|
82
|
+
"Use zeroize crate or add compiler_fence(SeqCst) after",
|
|
83
|
+
),
|
|
84
|
+
(
|
|
85
|
+
# Matches both turbofish form (transmute::<T, U>(v)) and type-inferred form (transmute(v))
|
|
86
|
+
r"\bmem\s*::\s*transmute\b",
|
|
87
|
+
"SECRET_COPY",
|
|
88
|
+
"high",
|
|
89
|
+
"mem::transmute creates a bitwise copy — original and transmuted value both exist on stack",
|
|
90
|
+
),
|
|
91
|
+
(
|
|
92
|
+
r"\bslice\s*::\s*from_raw_parts\s*\(",
|
|
93
|
+
"SECRET_COPY",
|
|
94
|
+
"medium",
|
|
95
|
+
"slice::from_raw_parts creates a slice alias over raw memory — may alias a secret buffer",
|
|
96
|
+
),
|
|
97
|
+
(
|
|
98
|
+
r"\bmem\s*::\s*take\s*\(",
|
|
99
|
+
"MISSING_SOURCE_ZEROIZE",
|
|
100
|
+
"medium",
|
|
101
|
+
"mem::take() replaces the value in-place without zeroing the original location",
|
|
102
|
+
),
|
|
103
|
+
(
|
|
104
|
+
r"\bmem\s*::\s*uninitialized\s*\(",
|
|
105
|
+
"MISSING_SOURCE_ZEROIZE",
|
|
106
|
+
"critical",
|
|
107
|
+
"mem::uninitialized() is deprecated and unsafe — "
|
|
108
|
+
"may expose prior secret bytes from stack memory",
|
|
109
|
+
),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
# Pre-compile all pattern regexes at module load time (avoids recompiling per file).
|
|
113
|
+
_COMPILED_PATTERNS: list[tuple[re.Pattern, str, str, str]] = [
|
|
114
|
+
(re.compile(pattern), category, severity, detail)
|
|
115
|
+
for pattern, category, severity, detail in PATTERNS
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Finding counter
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
_finding_counter = [0]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def make_finding(
|
|
126
|
+
category: str,
|
|
127
|
+
severity: str,
|
|
128
|
+
detail: str,
|
|
129
|
+
file: str,
|
|
130
|
+
line: int,
|
|
131
|
+
symbol: str = "",
|
|
132
|
+
confidence: str = "likely",
|
|
133
|
+
) -> dict:
|
|
134
|
+
_finding_counter[0] += 1
|
|
135
|
+
fid = f"F-RUST-SRC-{_finding_counter[0]:04d}"
|
|
136
|
+
return {
|
|
137
|
+
"id": fid,
|
|
138
|
+
"language": "rust",
|
|
139
|
+
"category": category,
|
|
140
|
+
"severity": severity,
|
|
141
|
+
"confidence": confidence,
|
|
142
|
+
"detail": detail,
|
|
143
|
+
"symbol": symbol,
|
|
144
|
+
"location": {"file": file, "line": line},
|
|
145
|
+
"evidence": [{"source": "source_grep", "detail": detail}],
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
# Context sensitivity check
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def has_sensitive_context(lines: list[str], center_idx: int, window: int = 15) -> bool:
|
|
155
|
+
"""Return True if any sensitive name appears within `window` lines of `center_idx`.
|
|
156
|
+
|
|
157
|
+
`center_idx` is a 0-based array index (i.e. ``lineno - 1``). Callers must
|
|
158
|
+
NOT pass 1-based line numbers here or the window will be off by one.
|
|
159
|
+
"""
|
|
160
|
+
start = max(0, center_idx - window)
|
|
161
|
+
end = min(len(lines), center_idx + window + 1)
|
|
162
|
+
context = "\n".join(lines[start:end])
|
|
163
|
+
return bool(SENSITIVE_NAME_RE.search(context))
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# Grep-based pattern scanner
|
|
168
|
+
# ---------------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
_BLOCK_COMMENT_START = re.compile(r"/\*")
|
|
171
|
+
_BLOCK_COMMENT_END = re.compile(r"\*/")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _is_commented_out(line: str, in_block_comment: bool) -> tuple[bool, bool]:
|
|
175
|
+
"""Return (skip_this_line, updated_in_block_comment).
|
|
176
|
+
|
|
177
|
+
Handles single-line `//` comments and block `/* ... */` comments. A line
|
|
178
|
+
that merely *contains* a comment start (e.g. `foo(); /* note */`) is NOT
|
|
179
|
+
fully skipped — only lines where the match site is inside the comment region
|
|
180
|
+
would be skipped. For simplicity this implementation skips the entire line
|
|
181
|
+
when it starts with `//` (after stripping) or when we are inside a block
|
|
182
|
+
comment. This is intentionally conservative: it may miss a pattern on the
|
|
183
|
+
same source line as an unrelated comment, but that is a very rare case.
|
|
184
|
+
"""
|
|
185
|
+
stripped = line.strip()
|
|
186
|
+
if in_block_comment:
|
|
187
|
+
if _BLOCK_COMMENT_END.search(line):
|
|
188
|
+
return True, False # end of block comment on this line; skip line
|
|
189
|
+
return True, True # still inside block comment
|
|
190
|
+
if stripped.startswith("//"):
|
|
191
|
+
return True, False # single-line comment
|
|
192
|
+
if stripped.startswith("/*"):
|
|
193
|
+
if _BLOCK_COMMENT_END.search(line):
|
|
194
|
+
return True, False # block comment opens and closes on this line
|
|
195
|
+
return True, True # block comment opens; skip remainder
|
|
196
|
+
# Mid-line block comment: code precedes the /* (e.g. `code(); /* comment ...`).
|
|
197
|
+
# Do not skip this line (the match site may be in the code portion), but mark
|
|
198
|
+
# subsequent lines as inside a block comment.
|
|
199
|
+
if _BLOCK_COMMENT_START.search(stripped) and not _BLOCK_COMMENT_END.search(stripped):
|
|
200
|
+
return False, True
|
|
201
|
+
return False, False
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def scan_file_patterns(path: Path, source: str) -> list[dict]:
|
|
205
|
+
findings: list[dict] = []
|
|
206
|
+
lines = source.splitlines()
|
|
207
|
+
in_block_comment = False
|
|
208
|
+
|
|
209
|
+
for compiled, category, severity, detail in _COMPILED_PATTERNS:
|
|
210
|
+
in_block_comment = False # reset per pattern pass
|
|
211
|
+
for lineno, line in enumerate(lines, start=1):
|
|
212
|
+
skip, in_block_comment = _is_commented_out(line, in_block_comment)
|
|
213
|
+
if skip:
|
|
214
|
+
continue
|
|
215
|
+
if not compiled.search(line):
|
|
216
|
+
continue
|
|
217
|
+
actual_severity = severity
|
|
218
|
+
actual_confidence = "likely"
|
|
219
|
+
if not has_sensitive_context(lines, lineno - 1): # lineno-1 → 0-based
|
|
220
|
+
actual_confidence = "needs_review"
|
|
221
|
+
findings.append(
|
|
222
|
+
make_finding(
|
|
223
|
+
category,
|
|
224
|
+
actual_severity,
|
|
225
|
+
detail,
|
|
226
|
+
str(path),
|
|
227
|
+
lineno,
|
|
228
|
+
confidence=actual_confidence,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return findings
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
# Async secret suspension detector
|
|
237
|
+
# ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def scan_async_suspension(path: Path, source: str) -> list[dict]:
|
|
241
|
+
"""
|
|
242
|
+
Detect: async fn body where a secret-named local is bound before an .await.
|
|
243
|
+
|
|
244
|
+
Heuristic:
|
|
245
|
+
1. Find async fn declarations.
|
|
246
|
+
2. Within each async fn body (between opening { and matching }), find let bindings
|
|
247
|
+
whose variable name matches SENSITIVE_NAME_RE.
|
|
248
|
+
3. Check whether any .await appears after the binding within the same fn body.
|
|
249
|
+
4. If so, emit NOT_ON_ALL_PATHS (high).
|
|
250
|
+
"""
|
|
251
|
+
findings: list[dict] = []
|
|
252
|
+
lines = source.splitlines()
|
|
253
|
+
|
|
254
|
+
# Find all async fn start lines
|
|
255
|
+
async_fn_re = re.compile(r"\basync\s+fn\s+\w+")
|
|
256
|
+
let_binding_re = re.compile(r"\blet\s+(?:mut\s+)?(\w+)\s*[=:]")
|
|
257
|
+
await_re = re.compile(r"\.await\b")
|
|
258
|
+
|
|
259
|
+
i = 0
|
|
260
|
+
while i < len(lines):
|
|
261
|
+
if async_fn_re.search(lines[i]):
|
|
262
|
+
# Find the body: scan for opening brace
|
|
263
|
+
body_lines: list[tuple[int, str]] = []
|
|
264
|
+
depth = 0
|
|
265
|
+
in_body = False
|
|
266
|
+
for j in range(i, min(i + 500, len(lines))):
|
|
267
|
+
# Count braces, skipping string literals and line comments
|
|
268
|
+
in_str = False
|
|
269
|
+
k = 0
|
|
270
|
+
line_text = lines[j]
|
|
271
|
+
while k < len(line_text):
|
|
272
|
+
ch = line_text[k]
|
|
273
|
+
if in_str:
|
|
274
|
+
if ch == "\\" and k + 1 < len(line_text):
|
|
275
|
+
k += 2 # skip escape sequence
|
|
276
|
+
continue
|
|
277
|
+
elif ch == '"':
|
|
278
|
+
in_str = False
|
|
279
|
+
else:
|
|
280
|
+
if ch == '"':
|
|
281
|
+
in_str = True
|
|
282
|
+
elif ch == "/" and k + 1 < len(line_text) and line_text[k + 1] == "/":
|
|
283
|
+
break # rest of line is a comment
|
|
284
|
+
elif ch == "{":
|
|
285
|
+
depth += 1
|
|
286
|
+
in_body = True
|
|
287
|
+
elif ch == "}":
|
|
288
|
+
depth -= 1
|
|
289
|
+
k += 1
|
|
290
|
+
if in_body:
|
|
291
|
+
body_lines.append((j + 1, lines[j])) # 1-based line number
|
|
292
|
+
if in_body and depth == 0:
|
|
293
|
+
i = j + 1
|
|
294
|
+
break
|
|
295
|
+
else:
|
|
296
|
+
i += 1
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
# Within body, find secret-named bindings followed by .await
|
|
300
|
+
secret_bindings: list[tuple[int, str]] = [] # (lineno, varname)
|
|
301
|
+
for lineno, line in body_lines:
|
|
302
|
+
m = let_binding_re.search(line)
|
|
303
|
+
if m and SENSITIVE_NAME_RE.search(m.group(1)):
|
|
304
|
+
secret_bindings.append((lineno, m.group(1)))
|
|
305
|
+
|
|
306
|
+
for bind_line, varname in secret_bindings:
|
|
307
|
+
# Check if .await appears after this binding in the fn body
|
|
308
|
+
for lineno, line in body_lines:
|
|
309
|
+
if lineno > bind_line and await_re.search(line):
|
|
310
|
+
findings.append(
|
|
311
|
+
make_finding(
|
|
312
|
+
"NOT_ON_ALL_PATHS",
|
|
313
|
+
"high",
|
|
314
|
+
f"Secret local '{varname}' is live across an .await suspension "
|
|
315
|
+
"point in an async fn — stored in the heap-allocated Future state "
|
|
316
|
+
"machine; ZeroizeOnDrop covers stack variables only",
|
|
317
|
+
str(path),
|
|
318
|
+
bind_line,
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
break # one finding per binding is enough
|
|
322
|
+
continue
|
|
323
|
+
i += 1
|
|
324
|
+
|
|
325
|
+
return findings
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# ---------------------------------------------------------------------------
|
|
329
|
+
# Main scanner
|
|
330
|
+
# ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def scan_directory(src_dir: Path) -> list[dict]:
|
|
334
|
+
findings: list[dict] = []
|
|
335
|
+
for rs_file in sorted(src_dir.rglob("*.rs")):
|
|
336
|
+
try:
|
|
337
|
+
source = rs_file.read_text(encoding="utf-8", errors="replace")
|
|
338
|
+
except OSError as e:
|
|
339
|
+
print(f"find_dangerous_apis.py: warning: cannot read {rs_file}: {e}", file=sys.stderr)
|
|
340
|
+
continue
|
|
341
|
+
findings.extend(scan_file_patterns(rs_file, source))
|
|
342
|
+
findings.extend(scan_async_suspension(rs_file, source))
|
|
343
|
+
return findings
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# ---------------------------------------------------------------------------
|
|
347
|
+
# CLI
|
|
348
|
+
# ---------------------------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def main() -> int:
|
|
352
|
+
parser = argparse.ArgumentParser(
|
|
353
|
+
description="Token/grep-based scanner for dangerous Rust API patterns"
|
|
354
|
+
)
|
|
355
|
+
parser.add_argument("--src", required=True, help="Source directory to scan (.rs files)")
|
|
356
|
+
parser.add_argument("--out", required=True, help="Output findings JSON path")
|
|
357
|
+
args = parser.parse_args()
|
|
358
|
+
|
|
359
|
+
src_dir = Path(args.src)
|
|
360
|
+
if not src_dir.is_dir():
|
|
361
|
+
print(f"find_dangerous_apis.py: source directory not found: {src_dir}", file=sys.stderr)
|
|
362
|
+
return 1
|
|
363
|
+
|
|
364
|
+
findings = scan_directory(src_dir)
|
|
365
|
+
|
|
366
|
+
out_path = Path(args.out)
|
|
367
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
368
|
+
out_path.write_text(json.dumps(findings, indent=2), encoding="utf-8")
|
|
369
|
+
|
|
370
|
+
print(f"find_dangerous_apis.py: {len(findings)} finding(s) written to {out_path}")
|
|
371
|
+
return 0
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
if __name__ == "__main__":
|
|
375
|
+
sys.exit(main())
|