diff --git a/dsgai_scanner_tool/CHANGES_v0.3.md b/dsgai_scanner_tool/CHANGES_v0.3.md index 68e1850..520f9d0 100644 --- a/dsgai_scanner_tool/CHANGES_v0.3.md +++ b/dsgai_scanner_tool/CHANGES_v0.3.md @@ -9,6 +9,14 @@ dates are ISO-8601. The previous line is recorded in [`CHANGES_v0.2.md`](CHANGES ## [Unreleased] ### Added +- **Ecosystem expansion + rule-pack export** (PR-15). + - **C# / Rust / Ruby**: detection signals (Semantic Kernel/Azure.AI.OpenAI, async-openai, + ruby-openai); CVE manifest parsing for **NuGet** (`*.csproj`), **crates.io** + (`Cargo.lock`), **RubyGems** (`Gemfile.lock`) via OSV; credential coverage extended to + `*.cs`/`*.rs`/`*.rb`/`*.csproj` (13 DSGAI02/13 rules), with C#/Rust/Ruby fixture cases. + - **`build/export_semgrep.py` → `dist/dsgai.semgrep.yaml`**: exports the 85 STRUCTURAL + rules as a Semgrep pack (value-bearing excluded by design) so incumbent toolchains + carry the DSGAI framework. Generated from the rules YAML; drift is a CI failure. - **Templated report, single-sourced prompt variant, static ATLAS map** (PR-14). - `cli/dsgai_report.py` + `templates/report.css`: the HTML report is now rendered **by code** from the checkpoint (deterministic, testable), with a golden structural diff --git a/dsgai_scanner_tool/build/export_semgrep.py b/dsgai_scanner_tool/build/export_semgrep.py new file mode 100644 index 0000000..7824f91 --- /dev/null +++ b/dsgai_scanner_tool/build/export_semgrep.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Export the STRUCTURAL DSGAI rules as a Semgrep pack. + +Strategic point: this makes incumbent toolchains carriers of the DSGAI framework +— distribution, not competition. Generated from rules/dsgai-rules.yaml so it +can't drift (CI runs `--check`). Value-bearing rules are intentionally excluded +(their whole point is that the match content must never be surfaced, which a +generic Semgrep pack cannot guarantee). + + python build/export_semgrep.py # write dist/dsgai.semgrep.yaml + python build/export_semgrep.py --check # CI: fail if the pack is stale +""" +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +RULES = ROOT / "rules" / "dsgai-rules.yaml" +OUT = ROOT / "dist" / "dsgai.semgrep.yaml" +SEVERITY = {"fail": "ERROR", "warn": "WARNING"} + + +def yq(s): + return "'" + s.replace("'", "''") + "'" + + +def render(): + import yaml + data = yaml.safe_load(RULES.read_text(encoding="utf-8")) + out = [ + "# DSGAI STRUCTURAL rules as a Semgrep pack.", + "# GENERATED from rules/dsgai-rules.yaml by build/export_semgrep.py — do not edit.", + "# Value-bearing rules are excluded by design (their matches must never surface).", + "rules:", + ] + for r in data["rules"]: + if r["classification"] != "structural": + continue + sev = SEVERITY.get(r["signal"], "INFO") + includes = ", ".join(yq(g) for g in r["file_globs"]) + out += [ + f" - id: dsgai-{r['id']}", + f" languages: [generic]", + f" severity: {sev}", + f" message: {yq(r['control'] + ' ' + r['id'] + ': ' + r['description'])}", + f" patterns:", + f" - pattern-regex: {yq(r['pcre'])}", + f" paths:", + f" include: [{includes}]", + f" metadata:", + f" dsgai_control: {r['control']}", + f" dsgai_rule: {r['id']}", + f" confidence: {r['confidence']}", + f" framework: {yq(r['framework'])}", + ] + return "\n".join(out) + "\n" + + +def main(argv): + rendered = render() + if "--check" in argv: + current = OUT.read_text(encoding="utf-8") if OUT.exists() else "" + if current != rendered: + sys.stderr.write("dist/dsgai.semgrep.yaml is out of date. Run: " + "python build/export_semgrep.py\n") + return 1 + print("dist/dsgai.semgrep.yaml is up to date.") + return 0 + OUT.parent.mkdir(exist_ok=True) + OUT.write_text(rendered, encoding="utf-8", newline="\n") + n = rendered.count(" - id: dsgai-") + print(f"wrote {OUT} ({n} structural rules)") + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/dsgai_scanner_tool/cli/dsgai_cve.py b/dsgai_scanner_tool/cli/dsgai_cve.py index c081aa2..4113ad0 100644 --- a/dsgai_scanner_tool/cli/dsgai_cve.py +++ b/dsgai_scanner_tool/cli/dsgai_cve.py @@ -33,6 +33,20 @@ } _REQ_RE = re.compile(r'^\s*([A-Za-z0-9_.\-]+)\s*==\s*([A-Za-z0-9_.\-]+)') +_CSPROJ_RE = re.compile(r'=|~=|\^|>|<|<=|\*|latest)' + paths: + include: ['*.py', '*.sh', '*.yml', '*.yaml', 'Dockerfile', 'requirements*.txt', 'pyproject.toml', 'setup.py'] + metadata: + dsgai_control: DSGAI04 + dsgai_rule: P04.4 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P04.5 + languages: [generic] + severity: INFO + message: 'DSGAI04 P04.5: SBOM in CI (PASS)' + patterns: + - pattern-regex: '(syft|cyclonedx|spdx|sbom)' + paths: + include: ['*.py', '*.sh', '*.yml', '*.yaml', 'Dockerfile', 'requirements*.txt', 'pyproject.toml', 'setup.py'] + metadata: + dsgai_control: DSGAI04 + dsgai_rule: P04.5 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P04.6 + languages: [generic] + severity: INFO + message: 'DSGAI04 P04.6: Trusted registry (PASS)' + patterns: + - pattern-regex: '(index-url|extra-index-url|artifactory|jfrog|verdaccio)' + paths: + include: ['*.py', '*.sh', '*.yml', '*.yaml', 'Dockerfile', 'requirements*.txt', 'pyproject.toml', 'setup.py'] + metadata: + dsgai_control: DSGAI04 + dsgai_rule: P04.6 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P04.7 + languages: [generic] + severity: INFO + message: 'DSGAI04 P04.7: Hash-pinned install (PASS)' + patterns: + - pattern-regex: '(--require-hashes|integrity\s*:\s*sha)' + paths: + include: ['*.py', '*.sh', '*.yml', '*.yaml', 'Dockerfile', 'requirements*.txt', 'pyproject.toml', 'setup.py'] + metadata: + dsgai_control: DSGAI04 + dsgai_rule: P04.7 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P05.1 + languages: [generic] + severity: INFO + message: 'DSGAI05 P05.1: Document ingestion calls' + patterns: + - pattern-regex: '(loader\.load\(|ingest_document\(|add_documents\(|index_document\(|UnstructuredFileLoader|PyPDFLoader|DirectoryLoader)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI05 + dsgai_rule: P05.1 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P05.2 + languages: [generic] + severity: INFO + message: 'DSGAI05 P05.2: Access control (PASS)' + patterns: + - pattern-regex: '(acl_filter|access_check|permitted_docs|filter\s*=.*tenant|namespace\s*=.*tenant)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI05 + dsgai_rule: P05.2 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P05.3 + languages: [generic] + severity: INFO + message: 'DSGAI05 P05.3: Tenant filter on search (PASS)' + patterns: + - pattern-regex: '(similarity_search|vector_search)[^)]{0,100}(filter|namespace|where)[^)]{0,40}tenant' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI05 + dsgai_rule: P05.3 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P05.4 + languages: [generic] + severity: INFO + message: 'DSGAI05 P05.4: Integrity check on docs (PASS)' + patterns: + - pattern-regex: '(hashlib|sha256[^\n]{0,40}doc|integrity[._-]check|verify[._-]document)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI05 + dsgai_rule: P05.4 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P05.5 + languages: [generic] + severity: INFO + message: 'DSGAI05 P05.5: Chunk size limits (PASS)' + patterns: + - pattern-regex: '(max_chunk_size|chunk_size\s*=|max_doc_size|content_limit)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI05 + dsgai_rule: P05.5 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P05.6 + languages: [generic] + severity: INFO + message: 'DSGAI05 P05.6: Path traversal risk' + patterns: + - pattern-regex: '(open\(.*\.\.|os\.path\.join\(.*request\.|Path\(.*request\.)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI05 + dsgai_rule: P05.6 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P06.1 + languages: [generic] + severity: ERROR + message: 'DSGAI06 P06.1: Insecure MCP transport (FAIL)' + patterns: + - pattern-regex: '("url"\s*:\s*"http://|transport[^\n]{0,20}http://|"command"[^}]{0,200}--http(?!s))' + paths: + include: ['*.json', '*.yaml', '*.toml', '*.py', '*.ts', 'mcp.json', 'claude_desktop_config.json'] + metadata: + dsgai_control: DSGAI06 + dsgai_rule: P06.1 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P06.2 + languages: [generic] + severity: INFO + message: 'DSGAI06 P06.2: MCP auth (PASS)' + patterns: + - pattern-regex: '(mcp[^\n]{0,30}(api_key|auth|bearer)|x-api-key[^\n]{0,20}mcp)' + paths: + include: ['*.json', '*.yaml', '*.toml', '*.py', '*.ts', 'mcp.json', 'claude_desktop_config.json'] + metadata: + dsgai_control: DSGAI06 + dsgai_rule: P06.2 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P06.3 + languages: [generic] + severity: INFO + message: 'DSGAI06 P06.3: Tool schema validation (PASS)' + patterns: + - pattern-regex: '(jsonschema|pydantic[^\n]{0,30}validate|zod|schema[^\n]{0,20}tool|input_schema)' + paths: + include: ['*.json', '*.yaml', '*.toml', '*.py', '*.ts', 'mcp.json', 'claude_desktop_config.json'] + metadata: + dsgai_control: DSGAI06 + dsgai_rule: P06.3 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P06.4 + languages: [generic] + severity: WARNING + message: 'DSGAI06 P06.4: Wildcard tool perms (WARN)' + patterns: + - pattern-regex: '(tools\s*[:=]\s*\*|"tools"\s*:\s*"\*"|allow_all_tools)' + paths: + include: ['*.json', '*.yaml', '*.toml', '*.py', '*.ts', 'mcp.json', 'claude_desktop_config.json'] + metadata: + dsgai_control: DSGAI06 + dsgai_rule: P06.4 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P06.5 + languages: [generic] + severity: INFO + message: 'DSGAI06 P06.5: uvicorn bind-all + no auth' + patterns: + - pattern-regex: 'uvicorn\.run\([^)]*host\s*=\s*["'']0\.0\.0\.0' + paths: + include: ['*.json', '*.yaml', '*.toml', '*.py', '*.ts', 'mcp.json', 'claude_desktop_config.json'] + metadata: + dsgai_control: DSGAI06 + dsgai_rule: P06.5 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P07.1 + languages: [generic] + severity: INFO + message: 'DSGAI07 P07.1: TTL config' + patterns: + - pattern-regex: '(ttl\s*=|expires_in\s*=|max_age\s*=|RETENTION_DAYS|DATA_TTL|HISTORY_TTL)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json'] + metadata: + dsgai_control: DSGAI07 + dsgai_rule: P07.1 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P07.2 + languages: [generic] + severity: INFO + message: 'DSGAI07 P07.2: Session cleanup (PASS)' + patterns: + - pattern-regex: '(delete_session|clear_history|purge_conversation|clear_memory|delete_conversation)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json'] + metadata: + dsgai_control: DSGAI07 + dsgai_rule: P07.2 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P07.3 + languages: [generic] + severity: INFO + message: 'DSGAI07 P07.3: Vector delete (PASS)' + patterns: + - pattern-regex: '(delete_namespace|delete_collection|drop_index|delete_index|reset_collection)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json'] + metadata: + dsgai_control: DSGAI07 + dsgai_rule: P07.3 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P07.4 + languages: [generic] + severity: INFO + message: 'DSGAI07 P07.4: Right-to-erasure (PASS)' + patterns: + - pattern-regex: '(gdpr_delete|erase_user_data|handle_deletion|right_to_erasure|forget_user)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json'] + metadata: + dsgai_control: DSGAI07 + dsgai_rule: P07.4 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P08.1 + languages: [generic] + severity: INFO + message: 'DSGAI08 P08.1: DPIA / privacy assessment doc' + patterns: + - pattern-regex: '(DPIA|PIA|privacy_assessment|privacy[._-]impact)' + paths: + include: ['*.md', '*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json', 'docs/**', 'appsec/**', 'security-review/**'] + metadata: + dsgai_control: DSGAI08 + dsgai_rule: P08.1 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P08.2 + languages: [generic] + severity: INFO + message: 'DSGAI08 P08.2: Data processing agreement ref' + patterns: + - pattern-regex: '(data_processing_agreement|DPA|sub_processor)' + paths: + include: ['*.md', '*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json', 'docs/**', 'appsec/**', 'security-review/**'] + metadata: + dsgai_control: DSGAI08 + dsgai_rule: P08.2 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P08.3 + languages: [generic] + severity: INFO + message: 'DSGAI08 P08.3: Consent capture' + patterns: + - pattern-regex: '(consent_capture|capture_consent|consent_record|lawful_basis)' + paths: + include: ['*.md', '*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json', 'docs/**', 'appsec/**', 'security-review/**'] + metadata: + dsgai_control: DSGAI08 + dsgai_rule: P08.3 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P08.4 + languages: [generic] + severity: INFO + message: 'DSGAI08 P08.4: EU AI Act annotation' + patterns: + - pattern-regex: '(ai_act_risk_level|high_risk_ai|limited_risk|minimal_risk)' + paths: + include: ['*.md', '*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json', 'docs/**', 'appsec/**', 'security-review/**'] + metadata: + dsgai_control: DSGAI08 + dsgai_rule: P08.4 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P08.5 + languages: [generic] + severity: INFO + message: 'DSGAI08 P08.5: Audit logging' + patterns: + - pattern-regex: '(audit_log|decision_log|audit_trail)' + paths: + include: ['*.md', '*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json', 'docs/**', 'appsec/**', 'security-review/**'] + metadata: + dsgai_control: DSGAI08 + dsgai_rule: P08.5 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P08.6 + languages: [generic] + severity: INFO + message: 'DSGAI08 P08.6: Do-not-track honored' + patterns: + - pattern-regex: '(do_not_track|opt_out|DNT)' + paths: + include: ['*.md', '*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.json', 'docs/**', 'appsec/**', 'security-review/**'] + metadata: + dsgai_control: DSGAI08 + dsgai_rule: P08.6 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P09.1 + languages: [generic] + severity: INFO + message: 'DSGAI09 P09.1: EXIF / metadata strip' + patterns: + - pattern-regex: '(strip_exif|remove_metadata|PIL\.Image|exifread|piexif)' + paths: + include: ['*.py', '*.ts', '*.java'] + metadata: + dsgai_control: DSGAI09 + dsgai_rule: P09.1 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P09.2 + languages: [generic] + severity: INFO + message: 'DSGAI09 P09.2: Image PII / moderation' + patterns: + - pattern-regex: '(detect_pii_in_image|content_filter|nsfw_detect|moderate_image)' + paths: + include: ['*.py', '*.ts', '*.java'] + metadata: + dsgai_control: DSGAI09 + dsgai_rule: P09.2 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P09.3 + languages: [generic] + severity: INFO + message: 'DSGAI09 P09.3: MIME / file type validation' + patterns: + - pattern-regex: '(allowed_types|mime_type_check|magic\.from_buffer|filetype\.guess)' + paths: + include: ['*.py', '*.ts', '*.java'] + metadata: + dsgai_control: DSGAI09 + dsgai_rule: P09.3 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P09.4 + languages: [generic] + severity: INFO + message: 'DSGAI09 P09.4: Size limit on upload' + patterns: + - pattern-regex: '(max_upload_size|MAX_FILE_SIZE|content[._-]length[._-]limit)' + paths: + include: ['*.py', '*.ts', '*.java'] + metadata: + dsgai_control: DSGAI09 + dsgai_rule: P09.4 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P09.5 + languages: [generic] + severity: INFO + message: 'DSGAI09 P09.5: Steganography detection' + patterns: + - pattern-regex: '(stego|steganography|stegano|lsb_detect)' + paths: + include: ['*.py', '*.ts', '*.java'] + metadata: + dsgai_control: DSGAI09 + dsgai_rule: P09.5 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P10.1 + languages: [generic] + severity: INFO + message: 'DSGAI10 P10.1: Membership inference test' + patterns: + - pattern-regex: '(membership_inference|mia_test|mia_attack)' + paths: + include: ['*.py', '*.ipynb', '*.r'] + metadata: + dsgai_control: DSGAI10 + dsgai_rule: P10.1 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P10.2 + languages: [generic] + severity: INFO + message: 'DSGAI10 P10.2: Anonymity validation' + patterns: + - pattern-regex: '(k_anonymity|l_diversity|t_closeness|anonymization_check)' + paths: + include: ['*.py', '*.ipynb', '*.r'] + metadata: + dsgai_control: DSGAI10 + dsgai_rule: P10.2 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P10.3 + languages: [generic] + severity: INFO + message: 'DSGAI10 P10.3: Privacy-preserving lib' + patterns: + - pattern-regex: '(SDV|gretel|synthetic_data_vault|smartnoise)' + paths: + include: ['*.py', '*.ipynb', '*.r'] + metadata: + dsgai_control: DSGAI10 + dsgai_rule: P10.3 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P10.4 + languages: [generic] + severity: INFO + message: 'DSGAI10 P10.4: DP noise in generation' + patterns: + - pattern-regex: '(epsilon\s*=|noise_multiplier\s*=|dp_noise|laplace_noise|gaussian_noise)' + paths: + include: ['*.py', '*.ipynb', '*.r'] + metadata: + dsgai_control: DSGAI10 + dsgai_rule: P10.4 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P10.5 + languages: [generic] + severity: INFO + message: 'DSGAI10 P10.5: Re-identification risk note' + patterns: + - pattern-regex: '(reidentification_risk|reid_check|disclosure_risk)' + paths: + include: ['*.py', '*.ipynb', '*.r'] + metadata: + dsgai_control: DSGAI10 + dsgai_rule: P10.5 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P11.1 + languages: [generic] + severity: INFO + message: 'DSGAI11 P11.1: Vector query call sites' + patterns: + - pattern-regex: '(similarity_search|max_marginal_relevance_search|as_retriever|(vectorstore|vector_store|retriever|index|collection|client)\.(query|search)\()' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI11 + dsgai_rule: P11.1 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P11.2 + languages: [generic] + severity: INFO + message: 'DSGAI11 P11.2: Tenant filter present (PASS)' + patterns: + - pattern-regex: '(namespace\s*=[^,)]{0,40}tenant|collection[^=]{0,20}=\s*f?["''][^"'']*\{?tenant|filter\s*=\s*\{[^}]*tenant)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI11 + dsgai_rule: P11.2 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P11.3 + languages: [generic] + severity: INFO + message: 'DSGAI11 P11.3: Tenant verification (PASS)' + patterns: + - pattern-regex: '(tenant_id\s*in\s*session|verify_tenant|assert_tenant|check_tenant|require_tenant)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI11 + dsgai_rule: P11.3 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P11.4 + languages: [generic] + severity: INFO + message: 'DSGAI11 P11.4: Cross-tenant cache risk' + patterns: + - pattern-regex: '(global[._-]cache|shared[._-]prompt[._-]cache|@cache(?!\s*\(.*tenant))' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI11 + dsgai_rule: P11.4 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P12.1 + languages: [generic] + severity: ERROR + message: 'DSGAI12 P12.1: Raw LLM-output execution (FAIL)' + patterns: + - pattern-regex: '(?:\.execute|\.run_query|executemany)\(\s*(?:f["'']|[^),]*\b(llm_\w*|completion\w*|generated_sql|generated_query|\w*\.content)\b)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI12 + dsgai_rule: P12.1 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P12.2 + languages: [generic] + severity: ERROR + message: 'DSGAI12 P12.2: LangChain SQL agent (FAIL signal)' + patterns: + - pattern-regex: '(SQLDatabaseChain|create_sql_agent|SQLDatabaseToolkit)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI12 + dsgai_rule: P12.2 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P12.3 + languages: [generic] + severity: INFO + message: 'DSGAI12 P12.3: Parameterized query (PASS)' + patterns: + - pattern-regex: '(cursor\.execute\([^)]+,\s*[\[\(]|prepareStatement|bindValue|:param)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI12 + dsgai_rule: P12.3 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P12.4 + languages: [generic] + severity: INFO + message: 'DSGAI12 P12.4: Read-only conn (PASS)' + patterns: + - pattern-regex: '(read_only\s*=\s*True|readonly\s*=\s*True|read_only_connection|default_transaction_read_only)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI12 + dsgai_rule: P12.4 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P12.5 + languages: [generic] + severity: INFO + message: 'DSGAI12 P12.5: Query validator (PASS)' + patterns: + - pattern-regex: '(validate_query|query_validator|safe_sql|sql_guard|sanitize_sql)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI12 + dsgai_rule: P12.5 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P12.6 + languages: [generic] + severity: ERROR + message: 'DSGAI12 P12.6: DDL from agent (FAIL)' + patterns: + - pattern-regex: '\b(DROP\s+TABLE|TRUNCATE|ALTER\s+TABLE|DELETE\s+FROM)\b' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI12 + dsgai_rule: P12.6 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P12.7 + languages: [generic] + severity: INFO + message: 'DSGAI12 P12.7: Row limit (PASS)' + patterns: + - pattern-regex: '(LIMIT\s+\d+|max_rows\s*=|fetch_limit\s*=)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI12 + dsgai_rule: P12.7 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P16.1 + languages: [generic] + severity: INFO + message: 'DSGAI16 P16.1: AI-ignore file present (PASS)' + patterns: + - pattern-regex: '(\.(copilot|ai|cursor|continue|codeium)ignore)' + paths: + include: ['.copilotignore', '.aiignore', '.cursorignore', '.continueignore', '.codeiumignore', '.vscode/settings.json', 'cursor.json', 'continue.json'] + metadata: + dsgai_control: DSGAI16 + dsgai_rule: P16.1 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P16.2 + languages: [generic] + severity: INFO + message: 'DSGAI16 P16.2: Sensitive paths excluded' + patterns: + - pattern-regex: '(\.env|secrets|credentials|\.aws|\.ssh|private_key)' + paths: + include: ['.copilotignore', '.aiignore', '.cursorignore', '.continueignore', '.codeiumignore', '.vscode/settings.json', 'cursor.json', 'continue.json'] + metadata: + dsgai_control: DSGAI16 + dsgai_rule: P16.2 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P16.3 + languages: [generic] + severity: INFO + message: 'DSGAI16 P16.3: Telemetry off (PASS)' + patterns: + - pattern-regex: '(telemetry[^\n]{0,10}(off|false|disabled)|share_data\s*[:=]\s*false)' + paths: + include: ['.copilotignore', '.aiignore', '.cursorignore', '.continueignore', '.codeiumignore', '.vscode/settings.json', 'cursor.json', 'continue.json'] + metadata: + dsgai_control: DSGAI16 + dsgai_rule: P16.3 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P16.4 + languages: [generic] + severity: INFO + message: 'DSGAI16 P16.4: Context scope limits' + patterns: + - pattern-regex: '(contextWindow|ignorePaths|excludeFiles|maxContextLines)' + paths: + include: ['.copilotignore', '.aiignore', '.cursorignore', '.continueignore', '.codeiumignore', '.vscode/settings.json', 'cursor.json', 'continue.json'] + metadata: + dsgai_control: DSGAI16 + dsgai_rule: P16.4 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P17.1 + languages: [generic] + severity: INFO + message: 'DSGAI17 P17.1: Circuit breaker (PASS)' + patterns: + - pattern-regex: '(CircuitBreaker|@circuit|tenacity|resilience4j|pybreaker)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI17 + dsgai_rule: P17.1 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P17.2 + languages: [generic] + severity: INFO + message: 'DSGAI17 P17.2: Retry with backoff (PASS)' + patterns: + - pattern-regex: '(retry\s*\(|@retry|exponential_backoff|max_retries\s*=|backoff_factor\s*=|retry_with_backoff)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI17 + dsgai_rule: P17.2 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P17.3 + languages: [generic] + severity: INFO + message: 'DSGAI17 P17.3: Timeout on LLM call (PASS)' + patterns: + - pattern-regex: '(timeout\s*=\s*\d|request_timeout\s*=|httpx\.[^\n]{0,30}timeout)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI17 + dsgai_rule: P17.3 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P17.4 + languages: [generic] + severity: INFO + message: 'DSGAI17 P17.4: Rate limit incoming (PASS)' + patterns: + - pattern-regex: '(rate_limit|@throttle|RateLimiter|slowapi|flask_limiter|express-rate-limit)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI17 + dsgai_rule: P17.4 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P17.5 + languages: [generic] + severity: INFO + message: 'DSGAI17 P17.5: Fallback response (PASS)' + patterns: + - pattern-regex: '(fallback_response|default_response|graceful_degradation|on_failure_return)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI17 + dsgai_rule: P17.5 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P17.6 + languages: [generic] + severity: ERROR + message: 'DSGAI17 P17.6: Unbounded retry (FAIL)' + patterns: + - pattern-regex: 'while\s+True[^}]{0,200}(generate|complete|chat)|retry\s*\(\s*\)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI17 + dsgai_rule: P17.6 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P18.1 + languages: [generic] + severity: INFO + message: 'DSGAI18 P18.1: Output guardrails (PASS)' + patterns: + - pattern-regex: '(guardrails|nemo[._-]guardrails|llm_guard|output_guard|filter_output|sanitize_response|moderation)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI18 + dsgai_rule: P18.1 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P18.2 + languages: [generic] + severity: INFO + message: 'DSGAI18 P18.2: PII detection on output' + patterns: + - pattern-regex: '(detect_pii[^\n]{0,20}output|output[^\n]{0,20}detect_pii|presidio[^\n]{0,20}output|scan_output)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI18 + dsgai_rule: P18.2 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P18.3 + languages: [generic] + severity: WARNING + message: 'DSGAI18 P18.3: Logprobs exposed (WARN)' + patterns: + - pattern-regex: '(logprobs\s*=\s*True|return_logprobs\s*=\s*True|include_logprobs\s*=\s*True|top_logprobs\s*=\s*\d)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI18 + dsgai_rule: P18.3 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P18.4 + languages: [generic] + severity: INFO + message: 'DSGAI18 P18.4: LLM call sites (count)' + patterns: + - pattern-regex: '(\.chat\.completions\.create|client\.messages\.create|openai\.ChatCompletion|anthropic\.messages|generate_content|chat\.complete)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI18 + dsgai_rule: P18.4 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P18.5 + languages: [generic] + severity: INFO + message: 'DSGAI18 P18.5: max_tokens set (PASS)' + patterns: + - pattern-regex: 'max_tokens\s*=\s*\d+' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI18 + dsgai_rule: P18.5 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P19.1 + languages: [generic] + severity: INFO + message: 'DSGAI19 P19.1: Anonymization before export' + patterns: + - pattern-regex: '(anonymize_for_labeling|pseudonymize|redact_before_export|mask_for_labeling)' + paths: + include: ['*.py', '*.ipynb', '*.ts', '*.yaml'] + metadata: + dsgai_control: DSGAI19 + dsgai_rule: P19.1 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P19.2 + languages: [generic] + severity: INFO + message: 'DSGAI19 P19.2: Labeling SDK auth from vault' + patterns: + - pattern-regex: '(label_studio[^\n]{0,30}vault|labelbox[^\n]{0,30}secret|scale_api_key[^\n]{0,30}(vault|env))' + paths: + include: ['*.py', '*.ipynb', '*.ts', '*.yaml'] + metadata: + dsgai_control: DSGAI19 + dsgai_rule: P19.2 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P19.3 + languages: [generic] + severity: INFO + message: 'DSGAI19 P19.3: Labeling access audit' + patterns: + - pattern-regex: '(label[._-]audit|labeling_access_log|annotator_audit)' + paths: + include: ['*.py', '*.ipynb', '*.ts', '*.yaml'] + metadata: + dsgai_control: DSGAI19 + dsgai_rule: P19.3 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P19.4 + languages: [generic] + severity: INFO + message: 'DSGAI19 P19.4: Re-identification post-label' + patterns: + - pattern-regex: '(reid_check_post_label|labeling_reidentification|post_label_validation)' + paths: + include: ['*.py', '*.ipynb', '*.ts', '*.yaml'] + metadata: + dsgai_control: DSGAI19 + dsgai_rule: P19.4 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P20.1 + languages: [generic] + severity: INFO + message: 'DSGAI20 P20.1: API auth required (PASS)' + patterns: + - pattern-regex: '(api_key[^\n]{0,15}(verify|validate)|bearer_token[^\n]{0,15}validate|Authorization[^\n]{0,15}required|@require_auth|auth_middleware|Depends\(.*auth)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI20 + dsgai_rule: P20.1 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P20.2 + languages: [generic] + severity: INFO + message: 'DSGAI20 P20.2: Rate limit (PASS)' + patterns: + - pattern-regex: '(rate_limit|RateLimiter|@throttle|slowapi|flask_limiter|express-rate-limit|@limiter\.limit)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI20 + dsgai_rule: P20.2 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P20.3 + languages: [generic] + severity: INFO + message: 'DSGAI20 P20.3: Input length validation (PASS)' + patterns: + - pattern-regex: '(max_input_length\s*=|max_input_tokens\s*=|input[._-]truncat|validate[._-]length)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI20 + dsgai_rule: P20.3 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P20.4 + languages: [generic] + severity: INFO + message: 'DSGAI20 P20.4: Prompt injection detect (PASS)' + patterns: + - pattern-regex: '(prompt_injection_detect|detect_injection|llm_guard|rebuff|input_guard|nemo[._-]guard)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI20 + dsgai_rule: P20.4 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P20.5 + languages: [generic] + severity: INFO + message: 'DSGAI20 P20.5: Inference endpoint (count)' + patterns: + - pattern-regex: '(@app\.(post|get)\(["''][^"'']*(chat|generate|completion|infer|predict)|@router\.(post|get)\(["''][^"'']*(chat|generate|completion|infer|predict)|app\.(post|get)\(["''][^"'']*(chat|generate|completion|infer|predict))' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt', '*.go'] + metadata: + dsgai_control: DSGAI20 + dsgai_rule: P20.5 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P21.1 + languages: [generic] + severity: INFO + message: 'DSGAI21 P21.1: Read-only retrieval (PASS)' + patterns: + - pattern-regex: '(read_only\s*=\s*True|readonly\s*=\s*True|HttpMethod\.GET|http_method[^\n]{0,10}get)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI21 + dsgai_rule: P21.1 + confidence: medium + framework: 'dsgai-2026-v1.0' + - id: dsgai-P21.2 + languages: [generic] + severity: WARNING + message: 'DSGAI21 P21.2: KB write from agent (WARN)' + patterns: + - pattern-regex: '(chromadb|qdrant|pinecone|weaviate|knowledge_base|kb_client)[^\n]{0,40}\.(upsert|insert|update|delete|add_documents|write)\(' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI21 + dsgai_rule: P21.2 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P21.3 + languages: [generic] + severity: INFO + message: 'DSGAI21 P21.3: Write content validation' + patterns: + - pattern-regex: '(validate_content|sanitize[._-]write|verify[._-]knowledge|content[._-]check[._-]write)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI21 + dsgai_rule: P21.3 + confidence: low + framework: 'dsgai-2026-v1.0' + - id: dsgai-P21.4 + languages: [generic] + severity: INFO + message: 'DSGAI21 P21.4: KB versioning (PASS)' + patterns: + - pattern-regex: '(versioned\s*=\s*True|audit_writes|version_id|kb_audit|knowledge[._-]audit)' + paths: + include: ['*.py', '*.ts', '*.java', '*.kt'] + metadata: + dsgai_control: DSGAI21 + dsgai_rule: P21.4 + confidence: medium + framework: 'dsgai-2026-v1.0' diff --git a/dsgai_scanner_tool/rules/dsgai-rules.json b/dsgai_scanner_tool/rules/dsgai-rules.json index 7271c5c..8446de7 100644 --- a/dsgai_scanner_tool/rules/dsgai-rules.json +++ b/dsgai_scanner_tool/rules/dsgai-rules.json @@ -129,7 +129,11 @@ "*.yml", "*.json", "*.toml", - "*.cfg" + "*.cfg", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P02.1", @@ -155,7 +159,11 @@ "*.yml", "*.json", "*.toml", - "*.cfg" + "*.cfg", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P02.2", @@ -181,7 +189,11 @@ "*.yml", "*.json", "*.toml", - "*.cfg" + "*.cfg", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P02.3", @@ -207,7 +219,11 @@ "*.yml", "*.json", "*.toml", - "*.cfg" + "*.cfg", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P02.4", @@ -233,7 +249,11 @@ "*.yml", "*.json", "*.toml", - "*.cfg" + "*.cfg", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P02.5", @@ -259,7 +279,11 @@ "*.yml", "*.json", "*.toml", - "*.cfg" + "*.cfg", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P02.6", @@ -285,7 +309,11 @@ "*.yml", "*.json", "*.toml", - "*.cfg" + "*.cfg", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P02.7", @@ -311,7 +339,11 @@ "*.yml", "*.json", "*.toml", - "*.cfg" + "*.cfg", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P02.8", @@ -343,7 +375,11 @@ "*.yml", "*.json", "*.toml", - "*.cfg" + "*.cfg", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P02.9", @@ -1451,7 +1487,11 @@ "*.kt", "*.go", "*.yaml", - "*.env*" + "*.env*", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P13.1", @@ -1472,7 +1512,11 @@ "*.kt", "*.go", "*.yaml", - "*.env*" + "*.env*", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P13.2", @@ -1493,7 +1537,11 @@ "*.kt", "*.go", "*.yaml", - "*.env*" + "*.env*", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P13.3", @@ -1514,7 +1562,11 @@ "*.kt", "*.go", "*.yaml", - "*.env*" + "*.env*", + "*.cs", + "*.rs", + "*.rb", + "*.csproj" ], "framework": "dsgai-2026-v1.0", "id": "P13.4", diff --git a/dsgai_scanner_tool/rules/dsgai-rules.yaml b/dsgai_scanner_tool/rules/dsgai-rules.yaml index 2639284..2ddd68d 100644 --- a/dsgai_scanner_tool/rules/dsgai-rules.yaml +++ b/dsgai_scanner_tool/rules/dsgai-rules.yaml @@ -66,7 +66,7 @@ rules: signal: fail confidence: high pcre: '(?i)(OPENAI_API_KEY|openai[._-]?api[._-]?key)\s*[:=]\s*["'']?sk-[A-Za-z0-9_\-]{20,}' - file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg'] + file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Hardcoded LLM API key (OpenAI)' @@ -77,7 +77,7 @@ rules: signal: fail confidence: high pcre: '(?i)(ANTHROPIC_API_KEY|anthropic[._-]?api[._-]?key)\s*[:=]\s*["'']?sk-ant-[A-Za-z0-9_\-]{20,}' - file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg'] + file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Hardcoded LLM API key (Anthropic)' @@ -88,7 +88,7 @@ rules: signal: fail confidence: high pcre: '(?i)(COHERE_API_KEY|GOOGLE_API_KEY|HF_TOKEN|HUGGINGFACE_TOKEN)\s*[:=]\s*["'']?[A-Za-z0-9_\-]{20,}' - file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg'] + file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Hardcoded Cohere/Google/HF tokens' @@ -99,7 +99,7 @@ rules: signal: fail confidence: high pcre: '(AWS_ACCESS_KEY_ID|aws_access_key_id)\s*[:=]\s*["'']?[A-Z0-9]{16,}' - file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg'] + file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Hardcoded AWS creds' @@ -110,7 +110,7 @@ rules: signal: fail confidence: high pcre: '(AZURE_OPENAI_KEY|GCP_SERVICE_ACCOUNT_KEY)\s*[:=]\s*["'']?[A-Za-z0-9_\-]{16,}' - file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg'] + file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Hardcoded Azure/GCP cred' @@ -121,7 +121,7 @@ rules: signal: warn confidence: low pcre: '("scope"\s*:\s*"\*|permissions[^\n]{0,30}\*|scope[^\n]{0,20}admin)' - file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg'] + file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Wildcard token scope (warn)' @@ -132,7 +132,7 @@ rules: signal: pass_signal confidence: medium pcre: '(hvac\.Client|hashicorp/vault|VAULT_(ADDR|TOKEN|NAMESPACE)|secretsmanager\.|GetSecretValue|SecretManagerServiceClient|azure[._-]keyvault|@aws-sdk/client-secrets-manager)' - file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg'] + file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Vault/secrets-manager (PASS)' @@ -143,7 +143,7 @@ rules: signal: pass_signal confidence: medium pcre: '(hmac|sign_request|verify_signature|tool_auth|mtls|mutual_tls)' - file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg'] + file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Tool-call signing (PASS)' @@ -154,7 +154,7 @@ rules: signal: fail confidence: high pcre: '\b(sk-ant-[A-Za-z0-9_\-]{20,}|sk-proj-[A-Za-z0-9_\-]{20,}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{22,}|xox[baprs]-[A-Za-z0-9-]{10,}|AIza[0-9A-Za-z_\-]{35}|AKIA[0-9A-Z]{16}|eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b' - file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg'] + file_globs: ['*.py', '*.ts', '*.js', '*.java', '*.kt', '*.go', '*.env*', '*.yaml', '*.yml', '*.json', '*.toml', '*.cfg', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: ['*.min.js', 'dist/**', '**/__snapshots__/**', '*.lock', '*.map'] framework: 'dsgai-2026-v1.0' description: 'Raw token literal (Slack/GitHub/Google/AWS/Anthropic/OpenAI/JWT) assigned to any variable' @@ -758,7 +758,7 @@ rules: signal: info confidence: low pcre: '(CHROMA_SERVER_AUTH|QDRANT_API_KEY|PINECONE_API_KEY|WEAVIATE_API_KEY|MILVUS_TOKEN|vectorstore[^\n]{0,30}api_key)' - file_globs: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.env*'] + file_globs: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.env*', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Vector store auth configured' @@ -769,7 +769,7 @@ rules: signal: pass_signal confidence: medium pcre: '(https://[^"'']*?(qdrant|weaviate|pinecone|chroma)|ssl\s*=\s*True|tls\s*=\s*True|grpcs://)' - file_globs: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.env*'] + file_globs: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.env*', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'TLS endpoints (PASS)' @@ -780,7 +780,7 @@ rules: signal: warn confidence: low pcre: '(host[^\n]{0,15}0\.0\.0\.0|ALLOW_RESET\s*=\s*True|chroma[^\n]{0,30}http://localhost|qdrant[^\n]{0,30}http://localhost)' - file_globs: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.env*'] + file_globs: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.env*', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Insecure binding (WARN)' @@ -791,7 +791,7 @@ rules: signal: fail confidence: high pcre: '(QDRANT_API_KEY|PINECONE_API_KEY|WEAVIATE_API_KEY)\s*=\s*["'']?[A-Za-z0-9_\-]{16,}' - file_globs: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.env*'] + file_globs: ['*.py', '*.ts', '*.java', '*.kt', '*.go', '*.yaml', '*.env*', '*.cs', '*.rs', '*.rb', '*.csproj'] exclude_globs: [] framework: 'dsgai-2026-v1.0' description: 'Hardcoded vector token (FAIL)' diff --git a/dsgai_scanner_tool/tests/expected-findings.yaml b/dsgai_scanner_tool/tests/expected-findings.yaml index 63a5bb8..187daa7 100644 --- a/dsgai_scanner_tool/tests/expected-findings.yaml +++ b/dsgai_scanner_tool/tests/expected-findings.yaml @@ -19,6 +19,10 @@ findings: - {control: DSGAI02, rule_id: P02.9, path: config.py, line: 7, status: fail, classification: value_bearing} - {control: DSGAI02, rule_id: P02.7, path: good_config.py, line: 11, status: pass_signal, classification: value_bearing} - {control: DSGAI02, rule_id: P02.9, path: js-service/index.js, line: 11, status: fail, classification: value_bearing} + # Multi-language credential coverage (PR-15): P02.9 now scans *.cs/*.rs/*.rb + - {control: DSGAI02, rule_id: P02.9, path: polyglot/Config.cs, line: 5, status: fail, classification: value_bearing} + - {control: DSGAI02, rule_id: P02.9, path: polyglot/config.rb, line: 3, status: fail, classification: value_bearing} + - {control: DSGAI02, rule_id: P02.9, path: polyglot/config.rs, line: 3, status: fail, classification: value_bearing} # DSGAI04 — supply chain - {control: DSGAI04, rule_id: P04.1, path: loader.py, line: 10, status: fail} - {control: DSGAI04, rule_id: P04.4, path: requirements.txt, line: 6, status: warn} diff --git a/dsgai_scanner_tool/tests/fixtures/vulnerable-app/polyglot/Config.cs b/dsgai_scanner_tool/tests/fixtures/vulnerable-app/polyglot/Config.cs new file mode 100644 index 0000000..c37fcec --- /dev/null +++ b/dsgai_scanner_tool/tests/fixtures/vulnerable-app/polyglot/Config.cs @@ -0,0 +1,6 @@ +// Fixture C# config — INTENTIONALLY VULNERABLE. Fake value. Never deploy. +using Microsoft.SemanticKernel; +public static class Config { + // DSGAI02 FAIL — hardcoded raw token (P02.9). Fake value. + public const string ApiKey = "sk-proj-FAKE00000000000000000000000000"; +} diff --git a/dsgai_scanner_tool/tests/fixtures/vulnerable-app/polyglot/config.rb b/dsgai_scanner_tool/tests/fixtures/vulnerable-app/polyglot/config.rb new file mode 100644 index 0000000..a2dc298 --- /dev/null +++ b/dsgai_scanner_tool/tests/fixtures/vulnerable-app/polyglot/config.rb @@ -0,0 +1,3 @@ +# Fixture Ruby config — INTENTIONALLY VULNERABLE. Fake value. Never deploy. +# Uses ruby-openai. DSGAI02 FAIL — hardcoded raw token (P02.9). Fake value. +API_KEY = "sk-proj-FAKE22222222222222222222222222" diff --git a/dsgai_scanner_tool/tests/fixtures/vulnerable-app/polyglot/config.rs b/dsgai_scanner_tool/tests/fixtures/vulnerable-app/polyglot/config.rs new file mode 100644 index 0000000..b55d97e --- /dev/null +++ b/dsgai_scanner_tool/tests/fixtures/vulnerable-app/polyglot/config.rs @@ -0,0 +1,3 @@ +// Fixture Rust config — INTENTIONALLY VULNERABLE. Fake value. Never deploy. +// Uses async-openai. DSGAI02 FAIL — hardcoded raw token (P02.9). Fake value. +pub const API_KEY: &str = "sk-proj-FAKE11111111111111111111111111"; diff --git a/dsgai_scanner_tool/tests/test_runner.py b/dsgai_scanner_tool/tests/test_runner.py index e6afd9c..16e5b50 100644 --- a/dsgai_scanner_tool/tests/test_runner.py +++ b/dsgai_scanner_tool/tests/test_runner.py @@ -51,7 +51,7 @@ def scan(tmp_path_factory): env = dict(os.environ, SOURCE_DATE_EPOCH="1700000000") proc = subprocess.run( [sys.executable, CLI, "scan", FIXTURE, "--json-out", str(jpath), - "--sarif", str(spath), "--format", "none"], + "--sarif", str(spath), "--format", "none", "--no-cve"], # deterministic (no network) capture_output=True, text=True, env=env) assert proc.returncode in (0, 1), proc.stderr return {"json": json.loads(jpath.read_text(encoding="utf-8")), @@ -281,6 +281,29 @@ def test_prompt_variant_in_sync(): assert r.returncode == 0, r.stderr +def test_semgrep_pack_in_sync(): + r = subprocess.run([sys.executable, + os.path.join(SCANNER, "build", "export_semgrep.py"), "--check"], + capture_output=True, text=True) + assert r.returncode == 0, r.stderr + + +def test_cve_multi_ecosystem_parse(tmp_path): + """Offline: manifest parsing recognises NuGet / crates.io / RubyGems deps.""" + sys.path.insert(0, os.path.join(SCANNER, "cli")) + import dsgai_cve + (tmp_path / "Cargo.lock").write_text( + '[[package]]\nname = "async-openai"\nversion = "0.18.0"\n', encoding="utf-8") + (tmp_path / "Gemfile.lock").write_text( + "GEM\n specs:\n ruby-openai (6.3.1)\n", encoding="utf-8") + (tmp_path / "app.csproj").write_text( + '' + '', encoding="utf-8") + disc = [(str(tmp_path / n), n) for n in ("Cargo.lock", "Gemfile.lock", "app.csproj")] + ecos = {d["ecosystem"] for d in dsgai_cve.parse_dependencies(disc)} + assert {"crates.io", "RubyGems", "NuGet"} <= ecos + + def test_atlas_map_valid(): amap = yaml.safe_load(open(os.path.join(SCANNER, "rules", "atlas-map.yaml"), encoding="utf-8"))