@quantakrypto/core 0.4.4 → 0.5.0
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/README.md +76 -27
- package/dist/cbom-merge.d.ts.map +1 -1
- package/dist/cbom-merge.js +30 -8
- package/dist/cbom-merge.js.map +1 -1
- package/dist/cbom.d.ts +5 -0
- package/dist/cbom.d.ts.map +1 -1
- package/dist/cbom.js +132 -37
- package/dist/cbom.js.map +1 -1
- package/dist/comments.d.ts +2 -23
- package/dist/comments.d.ts.map +1 -1
- package/dist/comments.js +24 -5
- package/dist/comments.js.map +1 -1
- package/dist/detect-utils.d.ts +55 -21
- package/dist/detect-utils.d.ts.map +1 -1
- package/dist/detect-utils.js +133 -7
- package/dist/detect-utils.js.map +1 -1
- package/dist/detectors/age.d.ts +16 -0
- package/dist/detectors/age.d.ts.map +1 -0
- package/dist/detectors/age.js +40 -0
- package/dist/detectors/age.js.map +1 -0
- package/dist/detectors/ansible.d.ts +13 -0
- package/dist/detectors/ansible.d.ts.map +1 -0
- package/dist/detectors/ansible.js +108 -0
- package/dist/detectors/ansible.js.map +1 -0
- package/dist/detectors/bicep.d.ts +20 -0
- package/dist/detectors/bicep.d.ts.map +1 -0
- package/dist/detectors/bicep.js +74 -0
- package/dist/detectors/bicep.js.map +1 -0
- package/dist/detectors/c.d.ts.map +1 -1
- package/dist/detectors/c.js +30 -3
- package/dist/detectors/c.js.map +1 -1
- package/dist/detectors/cicd.d.ts.map +1 -1
- package/dist/detectors/cicd.js +18 -5
- package/dist/detectors/cicd.js.map +1 -1
- package/dist/detectors/cloud-kms.d.ts +8 -5
- package/dist/detectors/cloud-kms.d.ts.map +1 -1
- package/dist/detectors/cloud-kms.js +62 -20
- package/dist/detectors/cloud-kms.js.map +1 -1
- package/dist/detectors/cloudformation.d.ts +8 -0
- package/dist/detectors/cloudformation.d.ts.map +1 -1
- package/dist/detectors/cloudformation.js +41 -10
- package/dist/detectors/cloudformation.js.map +1 -1
- package/dist/detectors/codesign.d.ts +50 -0
- package/dist/detectors/codesign.d.ts.map +1 -0
- package/dist/detectors/codesign.js +143 -0
- package/dist/detectors/codesign.js.map +1 -0
- package/dist/detectors/csharp.d.ts.map +1 -1
- package/dist/detectors/csharp.js +13 -3
- package/dist/detectors/csharp.js.map +1 -1
- package/dist/detectors/dart.d.ts +35 -0
- package/dist/detectors/dart.d.ts.map +1 -0
- package/dist/detectors/dart.js +177 -0
- package/dist/detectors/dart.js.map +1 -0
- package/dist/detectors/database.d.ts +5 -3
- package/dist/detectors/database.d.ts.map +1 -1
- package/dist/detectors/database.js +45 -8
- package/dist/detectors/database.js.map +1 -1
- package/dist/detectors/dkim.d.ts +54 -0
- package/dist/detectors/dkim.d.ts.map +1 -0
- package/dist/detectors/dkim.js +92 -0
- package/dist/detectors/dkim.js.map +1 -0
- package/dist/detectors/dnssec.d.ts.map +1 -1
- package/dist/detectors/dnssec.js +19 -10
- package/dist/detectors/dnssec.js.map +1 -1
- package/dist/detectors/elixir.d.ts.map +1 -1
- package/dist/detectors/elixir.js +34 -2
- package/dist/detectors/elixir.js.map +1 -1
- package/dist/detectors/java.d.ts.map +1 -1
- package/dist/detectors/java.js +33 -3
- package/dist/detectors/java.js.map +1 -1
- package/dist/detectors/jose.d.ts.map +1 -1
- package/dist/detectors/jose.js +13 -3
- package/dist/detectors/jose.js.map +1 -1
- package/dist/detectors/jwk.d.ts +7 -7
- package/dist/detectors/jwk.d.ts.map +1 -1
- package/dist/detectors/jwk.js +44 -8
- package/dist/detectors/jwk.js.map +1 -1
- package/dist/detectors/k8s.d.ts.map +1 -1
- package/dist/detectors/k8s.js +5 -2
- package/dist/detectors/k8s.js.map +1 -1
- package/dist/detectors/keystore.d.ts +19 -0
- package/dist/detectors/keystore.d.ts.map +1 -0
- package/dist/detectors/keystore.js +76 -0
- package/dist/detectors/keystore.js.map +1 -0
- package/dist/detectors/mesh.d.ts +7 -8
- package/dist/detectors/mesh.d.ts.map +1 -1
- package/dist/detectors/mesh.js +17 -35
- package/dist/detectors/mesh.js.map +1 -1
- package/dist/detectors/messaging.d.ts +2 -2
- package/dist/detectors/messaging.d.ts.map +1 -1
- package/dist/detectors/messaging.js +19 -12
- package/dist/detectors/messaging.js.map +1 -1
- package/dist/detectors/objc.d.ts +41 -0
- package/dist/detectors/objc.d.ts.map +1 -0
- package/dist/detectors/objc.js +146 -0
- package/dist/detectors/objc.js.map +1 -0
- package/dist/detectors/openpgp.d.ts +20 -0
- package/dist/detectors/openpgp.d.ts.map +1 -0
- package/dist/detectors/openpgp.js +151 -0
- package/dist/detectors/openpgp.js.map +1 -0
- package/dist/detectors/pem.d.ts.map +1 -1
- package/dist/detectors/pem.js +32 -0
- package/dist/detectors/pem.js.map +1 -1
- package/dist/detectors/php.d.ts +2 -1
- package/dist/detectors/php.d.ts.map +1 -1
- package/dist/detectors/php.js +7 -3
- package/dist/detectors/php.js.map +1 -1
- package/dist/detectors/pkcs11.d.ts +17 -0
- package/dist/detectors/pkcs11.d.ts.map +1 -0
- package/dist/detectors/pkcs11.js +87 -0
- package/dist/detectors/pkcs11.js.map +1 -0
- package/dist/detectors/proxy.d.ts +52 -0
- package/dist/detectors/proxy.d.ts.map +1 -0
- package/dist/detectors/proxy.js +157 -0
- package/dist/detectors/proxy.js.map +1 -0
- package/dist/detectors/pulumi.d.ts +19 -0
- package/dist/detectors/pulumi.d.ts.map +1 -0
- package/dist/detectors/pulumi.js +80 -0
- package/dist/detectors/pulumi.js.map +1 -0
- package/dist/detectors/rust.d.ts.map +1 -1
- package/dist/detectors/rust.js +5 -0
- package/dist/detectors/rust.js.map +1 -1
- package/dist/detectors/secrets.d.ts +4 -1
- package/dist/detectors/secrets.d.ts.map +1 -1
- package/dist/detectors/secrets.js +5 -21
- package/dist/detectors/secrets.js.map +1 -1
- package/dist/detectors/solidity.d.ts +50 -0
- package/dist/detectors/solidity.d.ts.map +1 -0
- package/dist/detectors/solidity.js +108 -0
- package/dist/detectors/solidity.js.map +1 -0
- package/dist/detectors/source.d.ts.map +1 -1
- package/dist/detectors/source.js +53 -6
- package/dist/detectors/source.js.map +1 -1
- package/dist/detectors/spire.d.ts +49 -0
- package/dist/detectors/spire.d.ts.map +1 -0
- package/dist/detectors/spire.js +77 -0
- package/dist/detectors/spire.js.map +1 -0
- package/dist/detectors/ssh-ca.d.ts +72 -0
- package/dist/detectors/ssh-ca.d.ts.map +1 -0
- package/dist/detectors/ssh-ca.js +119 -0
- package/dist/detectors/ssh-ca.js.map +1 -0
- package/dist/detectors/supply-chain.d.ts +17 -0
- package/dist/detectors/supply-chain.d.ts.map +1 -0
- package/dist/detectors/supply-chain.js +89 -0
- package/dist/detectors/supply-chain.js.map +1 -0
- package/dist/detectors/swift.d.ts +22 -0
- package/dist/detectors/swift.d.ts.map +1 -0
- package/dist/detectors/swift.js +120 -0
- package/dist/detectors/swift.js.map +1 -0
- package/dist/detectors/terraform.d.ts.map +1 -1
- package/dist/detectors/terraform.js +35 -8
- package/dist/detectors/terraform.js.map +1 -1
- package/dist/detectors/vault.d.ts +15 -0
- package/dist/detectors/vault.d.ts.map +1 -0
- package/dist/detectors/vault.js +97 -0
- package/dist/detectors/vault.js.map +1 -0
- package/dist/detectors/vpn.d.ts +8 -8
- package/dist/detectors/vpn.d.ts.map +1 -1
- package/dist/detectors/vpn.js +12 -38
- package/dist/detectors/vpn.js.map +1 -1
- package/dist/detectors/weak-hash.d.ts +71 -0
- package/dist/detectors/weak-hash.d.ts.map +1 -0
- package/dist/detectors/weak-hash.js +116 -0
- package/dist/detectors/weak-hash.js.map +1 -0
- package/dist/detectors/webauthn.d.ts +61 -0
- package/dist/detectors/webauthn.d.ts.map +1 -0
- package/dist/detectors/webauthn.js +109 -0
- package/dist/detectors/webauthn.js.map +1 -0
- package/dist/detectors/xmldsig.d.ts +23 -0
- package/dist/detectors/xmldsig.d.ts.map +1 -0
- package/dist/detectors/xmldsig.js +84 -0
- package/dist/detectors/xmldsig.js.map +1 -0
- package/dist/evidence.d.ts +74 -4
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +71 -0
- package/dist/evidence.js.map +1 -1
- package/dist/index.d.ts +14 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -11
- package/dist/index.js.map +1 -1
- package/dist/inventory.d.ts +0 -2
- package/dist/inventory.d.ts.map +1 -1
- package/dist/inventory.js +15 -6
- package/dist/inventory.js.map +1 -1
- package/dist/registry.d.ts +9 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +51 -4
- package/dist/registry.js.map +1 -1
- package/dist/remediate-pipeline.d.ts +0 -3
- package/dist/remediate-pipeline.d.ts.map +1 -1
- package/dist/remediate-pipeline.js +1 -1
- package/dist/remediate-pipeline.js.map +1 -1
- package/dist/remediation.d.ts +10 -0
- package/dist/remediation.d.ts.map +1 -1
- package/dist/remediation.js +32 -0
- package/dist/remediation.js.map +1 -1
- package/dist/report.d.ts +9 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +31 -1
- package/dist/report.js.map +1 -1
- package/dist/scan-worker.js +5 -3
- package/dist/scan-worker.js.map +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +35 -6
- package/dist/scan.js.map +1 -1
- package/dist/standards-profiles.d.ts +58 -0
- package/dist/standards-profiles.d.ts.map +1 -0
- package/dist/standards-profiles.js +100 -0
- package/dist/standards-profiles.js.map +1 -0
- package/dist/types.d.ts +6 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/vex.d.ts +39 -0
- package/dist/vex.d.ts.map +1 -0
- package/dist/vex.js +87 -0
- package/dist/vex.js.map +1 -0
- package/dist/walk.d.ts +8 -3
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +32 -1
- package/dist/walk.js.map +1 -1
- package/package.json +1 -1
- package/src/cbom-merge.ts +38 -8
- package/src/cbom.ts +153 -39
- package/src/comments.ts +25 -6
- package/src/detect-utils.ts +133 -11
- package/src/detectors/age.ts +60 -0
- package/src/detectors/ansible.ts +140 -0
- package/src/detectors/bicep.ts +107 -0
- package/src/detectors/c.ts +31 -3
- package/src/detectors/cicd.ts +18 -5
- package/src/detectors/cloud-kms.ts +79 -25
- package/src/detectors/cloudformation.ts +45 -10
- package/src/detectors/codesign.ts +214 -0
- package/src/detectors/csharp.ts +13 -3
- package/src/detectors/dart.ts +237 -0
- package/src/detectors/database.ts +64 -11
- package/src/detectors/dkim.ts +172 -0
- package/src/detectors/dnssec.ts +19 -10
- package/src/detectors/elixir.ts +33 -2
- package/src/detectors/java.ts +37 -3
- package/src/detectors/jose.ts +19 -3
- package/src/detectors/jwk.ts +59 -17
- package/src/detectors/k8s.ts +5 -2
- package/src/detectors/keystore.ts +109 -0
- package/src/detectors/mesh.ts +21 -45
- package/src/detectors/messaging.ts +22 -14
- package/src/detectors/objc.ts +209 -0
- package/src/detectors/openpgp.ts +194 -0
- package/src/detectors/pem.ts +30 -0
- package/src/detectors/php.ts +9 -4
- package/src/detectors/pkcs11.ts +113 -0
- package/src/detectors/proxy.ts +236 -0
- package/src/detectors/pulumi.ts +106 -0
- package/src/detectors/rust.ts +5 -0
- package/src/detectors/secrets.ts +15 -26
- package/src/detectors/solidity.ts +176 -0
- package/src/detectors/source.ts +48 -5
- package/src/detectors/spire.ts +143 -0
- package/src/detectors/ssh-ca.ts +215 -0
- package/src/detectors/supply-chain.ts +117 -0
- package/src/detectors/swift.ts +149 -0
- package/src/detectors/terraform.ts +42 -8
- package/src/detectors/vault.ts +120 -0
- package/src/detectors/vpn.ts +20 -48
- package/src/detectors/weak-hash.ts +217 -0
- package/src/detectors/webauthn.ts +205 -0
- package/src/detectors/xmldsig.ts +115 -0
- package/src/evidence.ts +127 -4
- package/src/index.ts +41 -13
- package/src/inventory.ts +15 -7
- package/src/registry.ts +51 -4
- package/src/remediate-pipeline.ts +1 -1
- package/src/remediation.ts +39 -0
- package/src/report.ts +36 -1
- package/src/scan-worker.ts +5 -3
- package/src/scan.ts +41 -6
- package/src/standards-profiles.ts +136 -0
- package/src/types.ts +9 -4
- package/src/version.ts +1 -1
- package/src/vex.ts +122 -0
- package/src/walk.ts +33 -2
package/dist/detect-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-utils.js","sourceRoot":"","sources":["../src/detect-utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared helpers for the regex-based source detectors: turning a string offset\n * into a 1-based line/column, extracting a trimmed single-line snippet, and a\n * small factory for building Finding objects with consistent remediation text.\n */\nimport type {\n AlgorithmFamily,\n Confidence,\n Finding,\n FindingCategory,\n RuleMeta,\n Severity,\n} from \"./types.js\";\nimport { remediationText } from \"./remediation.js\";\n\n/** A 1-based line/column position derived from a character offset. */\nexport interface LineCol {\n line: number;\n column: number;\n}\n\n/**\n * Memoized line-start offsets for the CURRENT content string. A scan processes\n * all of one file's findings consecutively against the same `content` reference,\n * so a single-entry cache turns the previously O(offset)-per-finding line/column\n * math into O(log n) — the whole file is O(n log n) instead of O(n²). Without\n * this, a large file with many findings scaled quadratically (a real perf cliff,\n * caught by the ReDoS/time-budget test).\n */\nlet cachedContent: string | null = null;\nlet cachedLineStarts: number[] = [];\nfunction lineStartsFor(content: string): number[] {\n if (content === cachedContent) return cachedLineStarts;\n const starts = [0];\n for (let i = 0; i < content.length; i++) {\n if (content.charCodeAt(i) === 10 /* \\n */) starts.push(i + 1);\n }\n cachedContent = content;\n cachedLineStarts = starts;\n return starts;\n}\n\n/** Index of the line containing `offset` (0-based into the line-starts array). */\nfunction lineIndexFor(starts: readonly number[], offset: number): number {\n let lo = 0;\n let hi = starts.length - 1;\n let best = 0;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n if (starts[mid] <= offset) {\n best = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n return best;\n}\n\n/**\n * Convert a 0-based character offset within `content` into a 1-based\n * line/column. Newlines are LF; CR is treated as an ordinary character, so on\n * CRLF files the column includes the trailing CR offset harmlessly.\n */\nexport function offsetToLineCol(content: string, offset: number): LineCol {\n const starts = lineStartsFor(content);\n const idx = lineIndexFor(starts, offset);\n return { line: idx + 1, column: offset - starts[idx] + 1 };\n}\n\n/** Extract the (trimmed) single source line containing `offset`. */\nexport function lineAt(content: string, offset: number): string {\n const starts = lineStartsFor(content);\n const idx = lineIndexFor(starts, offset);\n const start = starts[idx];\n const nextStart = idx + 1 < starts.length ? starts[idx + 1] : content.length + 1;\n // nextStart points just past the '\\n'; the line content ends before it.\n const end = Math.min(nextStart - 1, content.length);\n return content.slice(start, end).replace(/\\r$/, \"\").trim();\n}\n\n/** Inputs for {@link makeFinding}. */\nexport interface FindingSpec {\n ruleId: string;\n title: string;\n category: FindingCategory;\n severity: Severity;\n confidence: Confidence;\n algorithm?: AlgorithmFamily;\n hndl: boolean;\n message: string;\n /** Override the auto-derived remediation text. */\n remediation?: string;\n /** Associated CWE id (e.g. \"CWE-327\"). */\n cwe?: string;\n /** Marks the matched snippet as the sensitive value itself (key material). */\n sensitive?: boolean;\n /** The matched source text and its start offset within `content`. */\n file: string;\n content: string;\n index: number;\n /** Length of the match, used to compute endLine for multi-line matches. */\n matchLength?: number;\n}\n\n/**\n * Build a {@link Finding} with location info derived from a match offset. When\n * no explicit remediation is given but an algorithm is, the canonical\n * remediation text for that family is used.\n */\nexport function makeFinding(spec: FindingSpec): Finding {\n const { line, column } = offsetToLineCol(spec.content, spec.index);\n const snippet = lineAt(spec.content, spec.index);\n\n const remediation =\n spec.remediation ?? (spec.algorithm ? remediationText(spec.algorithm) : undefined);\n\n const location: Finding[\"location\"] = {\n file: spec.file,\n line,\n column,\n snippet: snippet.length > 200 ? `${snippet.slice(0, 197)}...` : snippet,\n };\n\n if (spec.matchLength && spec.matchLength > 0) {\n const matched = spec.content.slice(spec.index, spec.index + spec.matchLength);\n const extraLines = (matched.match(/\\n/g) ?? []).length;\n if (extraLines > 0) location.endLine = line + extraLines;\n }\n\n const finding: Finding = {\n ruleId: spec.ruleId,\n title: spec.title,\n category: spec.category,\n severity: spec.severity,\n confidence: spec.confidence,\n hndl: spec.hndl,\n message: spec.message,\n location,\n };\n if (spec.algorithm) finding.algorithm = spec.algorithm;\n if (remediation) finding.remediation = remediation;\n if (spec.cwe) finding.cwe = spec.cwe;\n if (spec.sensitive) finding.sensitive = true;\n return finding;\n}\n\n/** Where a match occurred, plus optional per-finding field overrides. */\nexport interface RuleMatch {\n file: string;\n content: string;\n /** Match start offset within `content`. */\n index: number;\n /** Length of the match (used to compute endLine for multi-line matches). */\n matchLength?: number;\n}\n\n/**\n * Fields of a {@link RuleMeta} a detector may refine at match time. Multi-variant\n * rules (e.g. key generation across algorithm families) override these; fixed\n * rules pass none and inherit the catalog metadata verbatim.\n */\nexport type RuleOverrides = Partial<\n Pick<\n RuleMeta,\n \"title\" | \"category\" | \"severity\" | \"confidence\" | \"algorithm\" | \"hndl\" | \"message\" | \"cwe\"\n >\n> & { remediation?: string };\n\n/**\n * Build a {@link Finding} from a rule's catalog metadata plus a match location,\n * applying any per-finding overrides. This is the single construction path for\n * detector findings: the invariant fields (title/severity/category/…) live once\n * in the {@link RuleMeta} declaration, so they can't drift from what the SARIF\n * catalog and the MCP resolver report.\n */\nexport function findingFromRule(rule: RuleMeta, at: RuleMatch, overrides?: RuleOverrides): Finding {\n return makeFinding({\n ruleId: rule.id,\n title: overrides?.title ?? rule.title,\n category: overrides?.category ?? rule.category,\n severity: overrides?.severity ?? rule.severity,\n confidence: overrides?.confidence ?? rule.confidence,\n algorithm: overrides?.algorithm ?? rule.algorithm,\n hndl: overrides?.hndl ?? rule.hndl,\n cwe: overrides?.cwe ?? rule.cwe,\n remediation: overrides?.remediation ?? rule.remediation,\n sensitive: rule.sensitive,\n message: overrides?.message ?? rule.message,\n file: at.file,\n content: at.content,\n index: at.index,\n matchLength: at.matchLength,\n });\n}\n\n/** True if `filePath` has one of the given (lower-case, dotted) extensions. */\nexport function hasExtension(filePath: string, exts: readonly string[]): boolean {\n const lower = filePath.toLowerCase();\n return exts.some((e) => lower.endsWith(e));\n}\n\n/**\n * JavaScript / TypeScript source extensions handled by the source detectors.\n * `.vue` / `.svelte` single-file components embed a `<script>` block, so the\n * lexical detectors catch crypto usage inside them.\n */\nexport const JS_TS_EXTENSIONS: readonly string[] = [\n \".js\",\n \".jsx\",\n \".ts\",\n \".tsx\",\n \".mjs\",\n \".cjs\",\n \".vue\",\n \".svelte\",\n];\n\n/** Python source extensions handled by the Python detector. */\nexport const PYTHON_EXTENSIONS: readonly string[] = [\".py\", \".pyi\", \".pyw\"];\n\n/** Go source extensions handled by the Go detector. */\nexport const GO_EXTENSIONS: readonly string[] = [\".go\"];\n\n/** Java / Kotlin source extensions handled by the JCA detector. */\n// The JVM detector also covers Kotlin (`.kt`/`.kts`) and Scala (`.scala`/`.sc`):\n// all three compile against the same JCA (`KeyPairGenerator`, `Signature`,\n// `KeyAgreement`) and BouncyCastle APIs the Java rules match.\nexport const JAVA_EXTENSIONS: readonly string[] = [\".java\", \".kt\", \".kts\", \".scala\", \".sc\"];\n\n/** C# source extensions handled by the .NET detector. */\nexport const CSHARP_EXTENSIONS: readonly string[] = [\".cs\"];\n\n/** Rust source extensions handled by the Rust detector. */\nexport const RUST_EXTENSIONS: readonly string[] = [\".rs\"];\n\n/** Ruby source extensions handled by the Ruby detector. */\nexport const RUBY_EXTENSIONS: readonly string[] = [\".rb\"];\n\n/** Elixir source extensions handled by the Elixir detector. */\nexport const ELIXIR_EXTENSIONS: readonly string[] = [\".ex\", \".exs\"];\n\n/** PHP source extensions handled by the PHP detector. */\nexport const PHP_EXTENSIONS: readonly string[] = [\".php\", \".phtml\", \".php3\", \".php4\", \".php5\"];\n\n/** C / C++ source extensions handled by the OpenSSL detector. */\nexport const C_EXTENSIONS: readonly string[] = [\".c\", \".h\", \".cc\", \".cpp\", \".cxx\", \".hpp\", \".hh\"];\n\n/**\n * Prose/documentation extensions. The language-agnostic *token* detectors (SSH\n * public keys, TLS cipher suites, certificate signature algorithms) must not run\n * on these: a changelog or README that mentions `ssh-rsa` or `ECDHE-RSA` in a\n * sentence is prose, not crypto config. (PEM/`-----BEGIN` material stays in scope\n * everywhere, so a key pasted into docs is still caught.)\n */\nexport const DOC_EXTENSIONS: readonly string[] = [\n \".md\",\n \".markdown\",\n \".mdown\",\n \".mkd\",\n \".rst\",\n \".adoc\",\n \".asciidoc\",\n \".textile\",\n \".org\",\n \".rdoc\",\n \".pod\",\n];\n\n/**\n * File-literal surfaces where a JWT/JOSE algorithm string (`\"RS256\"`, `\"ES256\"`)\n * is the same evidence regardless of language. Used to un-gate the JWT detector\n * from JS-only. Covers the languages whose JWT libraries pass the alg as a quoted\n * token — JS/TS, Python, Go (`jwt.GetSigningMethod(\"RS256\")`) and Ruby\n * (`JWT.encode(payload, key, 'RS256')`) — so the regex stays precise (`HS*` HMAC\n * tokens are excluded by `RE_JWT_ALG`). Java/C# pass the alg as an *identifier*\n * (`SignatureAlgorithm.RS256`), which needs its own pattern — a later pass.\n * YAML/JSON config carry unquoted tokens and higher FP risk, so they wait too.\n */\nexport const JWT_HOST_EXTENSIONS: readonly string[] = [\n ...JS_TS_EXTENSIONS,\n ...PYTHON_EXTENSIONS,\n ...GO_EXTENSIONS,\n ...RUBY_EXTENSIONS,\n];\n\n/**\n * Extensions the scanner can actually analyze for inline crypto usage today\n * (the language-specific source detectors). A scan that walked files but found\n * none of these has NOT meaningfully assessed the codebase — reporters surface\n * that ({@link CryptoInventory}/coverage), so a bare 100/100 can't masquerade as\n * \"safe\" on, say, a Go or Rust repo. PEM / SSH / dependency detectors run on any\n * file and are intentionally excluded here.\n */\nexport const ANALYZABLE_SOURCE_EXTENSIONS: readonly string[] = [\n ...JS_TS_EXTENSIONS,\n ...PYTHON_EXTENSIONS,\n ...GO_EXTENSIONS,\n ...JAVA_EXTENSIONS,\n ...CSHARP_EXTENSIONS,\n ...RUST_EXTENSIONS,\n ...RUBY_EXTENSIONS,\n ...PHP_EXTENSIONS,\n ...ELIXIR_EXTENSIONS,\n ...C_EXTENSIONS,\n];\n\n/**\n * Human label for the source languages the scanner can analyze for inline\n * crypto, shown in coverage output. Kept next to {@link\n * ANALYZABLE_SOURCE_EXTENSIONS} so a new language pack updates one place.\n */\nexport const ANALYZABLE_LANGUAGES_LABEL =\n \"JS/TS, Python, Go, Java/Kotlin/Scala, C#, Rust, Ruby, PHP, Elixir, C/C++\";\n\n/** True when a path is in a source language the scanner can analyze for crypto. */\nexport function isAnalyzableSource(filePath: string): boolean {\n return hasExtension(filePath, ANALYZABLE_SOURCE_EXTENSIONS);\n}\n\n/**\n * Given a SORTED ascending array of call offsets, return true when `idx` is at\n * or after some call offset `c` with `idx - c < window`. Runs in O(log n) by\n * binary-searching the largest call offset ≤ `idx` and checking the gap. This\n * replaces the previous O(matches × calls) linear scan (`nearCall`).\n */\nexport function nearSortedCall(\n sortedCalls: readonly number[],\n idx: number,\n window: number,\n): boolean {\n // Find the rightmost element <= idx.\n let lo = 0;\n let hi = sortedCalls.length - 1;\n let best = -1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n if (sortedCalls[mid] <= idx) {\n best = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n if (best < 0) return false;\n return idx - sortedCalls[best] < window;\n}\n\n/**\n * Run a global regex over `content`, invoking `onMatch` for each hit. Resets\n * lastIndex and guards against zero-width matches (which would loop forever).\n */\nexport function eachMatch(\n re: RegExp,\n content: string,\n onMatch: (match: RegExpExecArray) => void,\n): void {\n const g = re.global ? re : new RegExp(re.source, `${re.flags}g`);\n g.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = g.exec(content)) !== null) {\n onMatch(m);\n if (m.index === g.lastIndex) g.lastIndex++; // avoid infinite loop on empty match\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"detect-utils.js","sourceRoot":"","sources":["../src/detect-utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared helpers for the regex-based source detectors: turning a string offset\n * into a 1-based line/column, extracting a trimmed single-line snippet, and a\n * small factory for building Finding objects with consistent remediation text.\n */\nimport type {\n AlgorithmFamily,\n Confidence,\n Finding,\n FindingCategory,\n RuleMeta,\n Severity,\n} from \"./types.js\";\nimport { remediationText } from \"./remediation.js\";\n\n/** A 1-based line/column position derived from a character offset. */\ninterface LineCol {\n line: number;\n column: number;\n}\n\n/**\n * Memoized line-start offsets for the CURRENT content string. A scan processes\n * all of one file's findings consecutively against the same `content` reference,\n * so a single-entry cache turns the previously O(offset)-per-finding line/column\n * math into O(log n) — the whole file is O(n log n) instead of O(n²). Without\n * this, a large file with many findings scaled quadratically (a real perf cliff,\n * caught by the ReDoS/time-budget test).\n */\nlet cachedContent: string | null = null;\nlet cachedLineStarts: number[] = [];\nfunction lineStartsFor(content: string): number[] {\n if (content === cachedContent) return cachedLineStarts;\n const starts = [0];\n for (let i = 0; i < content.length; i++) {\n if (content.charCodeAt(i) === 10 /* \\n */) starts.push(i + 1);\n }\n cachedContent = content;\n cachedLineStarts = starts;\n return starts;\n}\n\n/** Index of the line containing `offset` (0-based into the line-starts array). */\nfunction lineIndexFor(starts: readonly number[], offset: number): number {\n let lo = 0;\n let hi = starts.length - 1;\n let best = 0;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n if (starts[mid] <= offset) {\n best = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n return best;\n}\n\n/**\n * Convert a 0-based character offset within `content` into a 1-based\n * line/column. Newlines are LF; CR is treated as an ordinary character, so on\n * CRLF files the column includes the trailing CR offset harmlessly.\n */\nfunction offsetToLineCol(content: string, offset: number): LineCol {\n const starts = lineStartsFor(content);\n const idx = lineIndexFor(starts, offset);\n return { line: idx + 1, column: offset - starts[idx] + 1 };\n}\n\n/** Extract the (trimmed) single source line containing `offset`. */\nfunction lineAt(content: string, offset: number): string {\n const starts = lineStartsFor(content);\n const idx = lineIndexFor(starts, offset);\n const start = starts[idx];\n const nextStart = idx + 1 < starts.length ? starts[idx + 1] : content.length + 1;\n // nextStart points just past the '\\n'; the line content ends before it.\n const end = Math.min(nextStart - 1, content.length);\n return content.slice(start, end).replace(/\\r$/, \"\").trim();\n}\n\n/** Inputs for {@link makeFinding}. */\ninterface FindingSpec {\n ruleId: string;\n title: string;\n category: FindingCategory;\n severity: Severity;\n confidence: Confidence;\n algorithm?: AlgorithmFamily;\n hndl: boolean;\n message: string;\n /** Override the auto-derived remediation text. */\n remediation?: string;\n /** Associated CWE id (e.g. \"CWE-327\"). */\n cwe?: string;\n /** Marks the matched snippet as the sensitive value itself (key material). */\n sensitive?: boolean;\n /** The matched source text and its start offset within `content`. */\n file: string;\n content: string;\n index: number;\n /** Length of the match, used to compute endLine for multi-line matches. */\n matchLength?: number;\n}\n\n/**\n * Build a {@link Finding} with location info derived from a match offset. When\n * no explicit remediation is given but an algorithm is, the canonical\n * remediation text for that family is used.\n */\nexport function makeFinding(spec: FindingSpec): Finding {\n const { line, column } = offsetToLineCol(spec.content, spec.index);\n const snippet = lineAt(spec.content, spec.index);\n\n const remediation =\n spec.remediation ?? (spec.algorithm ? remediationText(spec.algorithm) : undefined);\n\n const location: Finding[\"location\"] = {\n file: spec.file,\n line,\n column,\n snippet: snippet.length > 200 ? `${snippet.slice(0, 197)}...` : snippet,\n };\n\n if (spec.matchLength && spec.matchLength > 0) {\n const matched = spec.content.slice(spec.index, spec.index + spec.matchLength);\n const extraLines = (matched.match(/\\n/g) ?? []).length;\n if (extraLines > 0) location.endLine = line + extraLines;\n }\n\n const finding: Finding = {\n ruleId: spec.ruleId,\n title: spec.title,\n category: spec.category,\n severity: spec.severity,\n confidence: spec.confidence,\n hndl: spec.hndl,\n message: spec.message,\n location,\n };\n if (spec.algorithm) finding.algorithm = spec.algorithm;\n if (remediation) finding.remediation = remediation;\n if (spec.cwe) finding.cwe = spec.cwe;\n if (spec.sensitive) finding.sensitive = true;\n return finding;\n}\n\n/** Where a match occurred, plus optional per-finding field overrides. */\ninterface RuleMatch {\n file: string;\n content: string;\n /** Match start offset within `content`. */\n index: number;\n /** Length of the match (used to compute endLine for multi-line matches). */\n matchLength?: number;\n}\n\n/**\n * Fields of a {@link RuleMeta} a detector may refine at match time. Multi-variant\n * rules (e.g. key generation across algorithm families) override these; fixed\n * rules pass none and inherit the catalog metadata verbatim.\n */\ntype RuleOverrides = Partial<\n Pick<\n RuleMeta,\n \"title\" | \"category\" | \"severity\" | \"confidence\" | \"algorithm\" | \"hndl\" | \"message\" | \"cwe\"\n >\n> & { remediation?: string };\n\n/**\n * Build a {@link Finding} from a rule's catalog metadata plus a match location,\n * applying any per-finding overrides. This is the single construction path for\n * detector findings: the invariant fields (title/severity/category/…) live once\n * in the {@link RuleMeta} declaration, so they can't drift from what the SARIF\n * catalog and the MCP resolver report.\n */\nexport function findingFromRule(rule: RuleMeta, at: RuleMatch, overrides?: RuleOverrides): Finding {\n return makeFinding({\n ruleId: rule.id,\n title: overrides?.title ?? rule.title,\n category: overrides?.category ?? rule.category,\n severity: overrides?.severity ?? rule.severity,\n confidence: overrides?.confidence ?? rule.confidence,\n algorithm: overrides?.algorithm ?? rule.algorithm,\n hndl: overrides?.hndl ?? rule.hndl,\n cwe: overrides?.cwe ?? rule.cwe,\n remediation: overrides?.remediation ?? rule.remediation,\n sensitive: rule.sensitive,\n message: overrides?.message ?? rule.message,\n file: at.file,\n content: at.content,\n index: at.index,\n matchLength: at.matchLength,\n });\n}\n\n/** True if `filePath` has one of the given (lower-case, dotted) extensions. */\nexport function hasExtension(filePath: string, exts: readonly string[]): boolean {\n const lower = filePath.toLowerCase();\n return exts.some((e) => lower.endsWith(e));\n}\n\n/**\n * JavaScript / TypeScript source extensions handled by the source detectors.\n * `.vue` / `.svelte` single-file components embed a `<script>` block, so the\n * lexical detectors catch crypto usage inside them.\n */\nexport const JS_TS_EXTENSIONS: readonly string[] = [\n \".js\",\n \".jsx\",\n \".ts\",\n \".tsx\",\n \".mjs\",\n \".cjs\",\n \".vue\",\n \".svelte\",\n];\n\n/** Python source extensions handled by the Python detector. */\nexport const PYTHON_EXTENSIONS: readonly string[] = [\".py\", \".pyi\", \".pyw\"];\n\n/** Go source extensions handled by the Go detector. */\nexport const GO_EXTENSIONS: readonly string[] = [\".go\"];\n\n/** Java / Kotlin source extensions handled by the JCA detector. */\n// The JVM detector also covers Kotlin (`.kt`/`.kts`) and Scala (`.scala`/`.sc`):\n// all three compile against the same JCA (`KeyPairGenerator`, `Signature`,\n// `KeyAgreement`) and BouncyCastle APIs the Java rules match.\nexport const JAVA_EXTENSIONS: readonly string[] = [\".java\", \".kt\", \".kts\", \".scala\", \".sc\"];\n\n/** C# source extensions handled by the .NET detector. */\nexport const CSHARP_EXTENSIONS: readonly string[] = [\".cs\"];\n\n/** Rust source extensions handled by the Rust detector. */\nexport const RUST_EXTENSIONS: readonly string[] = [\".rs\"];\n\n/** Ruby source extensions handled by the Ruby detector. */\nexport const RUBY_EXTENSIONS: readonly string[] = [\".rb\"];\n\n/** Elixir source extensions handled by the Elixir detector. */\nexport const ELIXIR_EXTENSIONS: readonly string[] = [\".ex\", \".exs\"];\n\n/** PHP source extensions handled by the PHP detector. */\nexport const PHP_EXTENSIONS: readonly string[] = [\".php\", \".phtml\", \".php3\", \".php4\", \".php5\"];\n\n/** C / C++ source extensions handled by the OpenSSL detector. */\nexport const C_EXTENSIONS: readonly string[] = [\".c\", \".h\", \".cc\", \".cpp\", \".cxx\", \".hpp\", \".hh\"];\n\n/** Swift source extensions handled by the CryptoKit / Security-framework detector. */\nexport const SWIFT_EXTENSIONS: readonly string[] = [\".swift\"];\n\n/** Objective-C implementation extensions (`.h` is owned by the C pack). */\nexport const OBJC_EXTENSIONS: readonly string[] = [\".m\", \".mm\"];\n\n/** Dart / Flutter source extensions. */\nexport const DART_EXTENSIONS: readonly string[] = [\".dart\"];\n\n/** Smart-contract source extensions (Solidity/EVM, Move, Cairo/Starknet). */\nexport const SMART_CONTRACT_EXTENSIONS: readonly string[] = [\".sol\", \".move\", \".cairo\"];\n\n/**\n * Prose/documentation extensions. The language-agnostic *token* detectors (SSH\n * public keys, TLS cipher suites, certificate signature algorithms) must not run\n * on these: a changelog or README that mentions `ssh-rsa` or `ECDHE-RSA` in a\n * sentence is prose, not crypto config. (PEM/`-----BEGIN` material stays in scope\n * everywhere, so a key pasted into docs is still caught.)\n */\nexport const DOC_EXTENSIONS: readonly string[] = [\n \".md\",\n \".markdown\",\n \".mdown\",\n \".mkd\",\n \".rst\",\n \".adoc\",\n \".asciidoc\",\n \".textile\",\n \".org\",\n \".rdoc\",\n \".pod\",\n];\n\n/**\n * File-literal surfaces where a JWT/JOSE algorithm string (`\"RS256\"`, `\"ES256\"`)\n * is the same evidence regardless of language. Used to un-gate the JWT detector\n * from JS-only. Covers the languages whose JWT libraries pass the alg as a quoted\n * token — JS/TS, Python, Go (`jwt.GetSigningMethod(\"RS256\")`) and Ruby\n * (`JWT.encode(payload, key, 'RS256')`) — so the regex stays precise (`HS*` HMAC\n * tokens are excluded by `RE_JWT_ALG`). Java/C# pass the alg as an *identifier*\n * (`SignatureAlgorithm.RS256`), which needs its own pattern — a later pass.\n * YAML/JSON config carry unquoted tokens and higher FP risk, so they wait too.\n */\nexport const JWT_HOST_EXTENSIONS: readonly string[] = [\n ...JS_TS_EXTENSIONS,\n ...PYTHON_EXTENSIONS,\n ...GO_EXTENSIONS,\n ...RUBY_EXTENSIONS,\n // PHP's firebase/php-jwt passes the alg as a quoted token (`'RS256'`, `'ES256'`),\n // the same shape RE_JWT_ALG matches — so PHP files are in scope for it too.\n ...PHP_EXTENSIONS,\n];\n\n/**\n * Extensions the scanner can actually analyze for inline crypto usage today\n * (the language-specific source detectors). A scan that walked files but found\n * none of these has NOT meaningfully assessed the codebase — reporters surface\n * that ({@link CryptoInventory}/coverage), so a bare 100/100 can't masquerade as\n * \"safe\" on, say, a Go or Rust repo. PEM / SSH / dependency detectors run on any\n * file and are intentionally excluded here.\n */\nexport const ANALYZABLE_SOURCE_EXTENSIONS: readonly string[] = [\n ...JS_TS_EXTENSIONS,\n ...PYTHON_EXTENSIONS,\n ...GO_EXTENSIONS,\n ...JAVA_EXTENSIONS,\n ...CSHARP_EXTENSIONS,\n ...RUST_EXTENSIONS,\n ...RUBY_EXTENSIONS,\n ...PHP_EXTENSIONS,\n ...ELIXIR_EXTENSIONS,\n ...C_EXTENSIONS,\n ...SWIFT_EXTENSIONS,\n ...OBJC_EXTENSIONS,\n ...DART_EXTENSIONS,\n ...SMART_CONTRACT_EXTENSIONS,\n];\n\n/**\n * Human label for the source languages the scanner can analyze for inline\n * crypto, shown in coverage output. Kept next to {@link\n * ANALYZABLE_SOURCE_EXTENSIONS} so a new language pack updates one place.\n */\nexport const ANALYZABLE_LANGUAGES_LABEL =\n \"JS/TS, Python, Go, Java/Kotlin/Scala, C#, Rust, Ruby, PHP, Elixir, C/C++, Swift, Objective-C, Dart, Solidity/Move/Cairo\";\n\n/** True when a path is in a source language the scanner can analyze for crypto. */\nexport function isAnalyzableSource(filePath: string): boolean {\n return hasExtension(filePath, ANALYZABLE_SOURCE_EXTENSIONS);\n}\n\n/**\n * Given a SORTED ascending array of call offsets, return true when `idx` is at or\n * after some call offset `c` with `idx - c < window`. Deliberately ONE-SIDED\n * (forward only): the WebCrypto algorithm name follows the `subtle.*(` call it\n * belongs to, and reaching backward would wrongly attribute an UNRELATED earlier\n * call's argument (e.g. a Node `generateKeyPairSync('rsa-pss', …)` sitting a few\n * lines above a subtle call) to the wrong API. Runs in O(log n) by binary-searching\n * the largest call offset ≤ `idx` and checking the gap.\n */\nexport function nearSortedCall(\n sortedCalls: readonly number[],\n idx: number,\n window: number,\n): boolean {\n // Find the rightmost element <= idx.\n let lo = 0;\n let hi = sortedCalls.length - 1;\n let best = -1;\n while (lo <= hi) {\n const mid = (lo + hi) >>> 1;\n if (sortedCalls[mid] <= idx) {\n best = mid;\n lo = mid + 1;\n } else {\n hi = mid - 1;\n }\n }\n if (best < 0) return false;\n return idx - sortedCalls[best] < window;\n}\n\n/**\n * Run a global regex over `content`, invoking `onMatch` for each hit. Resets\n * lastIndex and guards against zero-width matches (which would loop forever).\n */\nexport function eachMatch(\n re: RegExp,\n content: string,\n onMatch: (match: RegExpExecArray) => void,\n): void {\n const g = re.global ? re : new RegExp(re.source, `${re.flags}g`);\n g.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = g.exec(content)) !== null) {\n onMatch(m);\n if (m.index === g.lastIndex) g.lastIndex++; // avoid infinite loop on empty match\n }\n}\n\n/**\n * Return the innermost `{ … }` object that contains `index`, as a substring — so a\n * per-match check (e.g. a JWK's own `use`/`alg`, or whether an `alg` sits inside a\n * JWK) analyses ONLY that object and can't be contaminated by a neighbouring object\n * in a packed array. The scan is STRING-AWARE (braces and quotes inside JSON string\n * values, honouring `\\\"`, are ignored), so a value like `\"a}b{c\"` can't mis-scope the\n * object. Bounded by `maxSpan` each way so a pathological input can't blow up; falls\n * back to a bounded ±window when no enclosing object is found in range.\n */\nexport function enclosingObject(content: string, index: number, maxSpan = 4000): string {\n const lo = Math.max(0, index - maxSpan);\n const hi = Math.min(content.length, index + maxSpan);\n const stack: number[] = []; // positions of currently-open `{`\n let inStr = false;\n let esc = false;\n let enclosingOpen = -1; // innermost `{` open at `index`\n for (let i = lo; i < hi; i++) {\n const c = content[i];\n if (inStr) {\n if (esc) esc = false;\n else if (c === \"\\\\\") esc = true;\n else if (c === '\"') inStr = false;\n } else if (c === '\"') {\n inStr = true;\n } else if (c === \"{\") {\n stack.push(i);\n } else if (c === \"}\") {\n const open = stack.pop();\n if (open !== undefined && open === enclosingOpen) {\n return content.slice(enclosingOpen, i + 1); // closed the enclosing object\n }\n }\n if (i === index && enclosingOpen < 0 && stack.length > 0) {\n enclosingOpen = stack[stack.length - 1];\n }\n }\n // No complete enclosing object in range: if we found an (unclosed) enclosing open\n // brace, return from it to hi. Otherwise the token is not inside any object (top-level\n // token / brace-less config like YAML) — return a bounded ±250 window, NOT the full\n // forward span, so a `\"kty\"` far AFTER the token can't pull an unrelated JWK into\n // scope and over-suppress a legitimate standalone finding.\n if (enclosingOpen >= 0) return content.slice(enclosingOpen, hi);\n return content.slice(Math.max(0, index - 250), Math.min(content.length, index + 250));\n}\n\n/**\n * Blank out FULL-LINE comments so a commented-out directive isn't reported as an\n * active setting. A line counts as a comment when its first non-whitespace characters\n * match one of `markers` (e.g. `#`, `//`, `;`, `!`). Each such line's characters are\n * replaced with spaces of the SAME length (newlines preserved), so every byte offset\n * is unchanged — the finding line/column/snippet for the non-comment lines that remain\n * stay exactly correct. Inline trailing comments are intentionally left alone (the\n * directive before them is still active, so it should still be flagged).\n *\n * Config detectors run their rules over the masked content; matches can then only land\n * on live config, not on commented examples (mosquitto.conf, CI YAML, ipsec.conf, and\n * HCL all conventionally ship large blocks of commented-out directives).\n */\nexport function maskCommentLines(content: string, markers: readonly string[]): string {\n if (markers.length === 0) return content;\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const lead = line.trimStart();\n if (lead !== \"\" && markers.some((mk) => lead.startsWith(mk))) {\n lines[i] = \" \".repeat(line.length);\n }\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Blank out C-style block comments (the `slash-star … star-slash` form, multi-line\n * included), replacing every\n * character with a space and preserving newlines so byte offsets — and therefore\n * finding line/column — stay exact. Complements {@link maskCommentLines}, which only\n * masks whole single-line comments: an HCL/Bicep resource wrapped in a block comment\n * has its inner lines mid-block (they don't START with `/*`), so a line-only masker\n * leaves them live. Not string-aware — a literal `/*` inside a config string value is\n * rare and, for these token detectors, a tolerable over-mask.\n */\nexport function maskBlockComments(content: string): string {\n if (!content.includes(\"/*\")) return content;\n const out: string[] = [];\n let i = 0;\n const n = content.length;\n while (i < n) {\n if (content[i] === \"/\" && content[i + 1] === \"*\") {\n const end = content.indexOf(\"*/\", i + 2);\n const stop = end === -1 ? n : end + 2;\n for (let j = i; j < stop; j++) out.push(content[j] === \"\\n\" ? \"\\n\" : \" \");\n i = stop;\n } else {\n out.push(content[i]);\n i++;\n }\n }\n return out.join(\"\");\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: age IDENTITY (private) keys committed to disk. The secrets
|
|
3
|
+
* detector flags age *recipients* (`age1…` public keys); this flags the far worse
|
|
4
|
+
* case — an `AGE-SECRET-KEY-1…` private identity checked into a repo or config.
|
|
5
|
+
* age identities are X25519 secret keys: a classical key-agreement secret that,
|
|
6
|
+
* once leaked or committed, lets an attacker decrypt every age-wrapped payload
|
|
7
|
+
* addressed to it (harvest-now-decrypt-later, and retroactively un-fixable if the
|
|
8
|
+
* ciphertext is already in git history).
|
|
9
|
+
*
|
|
10
|
+
* The matched value IS the private key, so it is marked sensitive and reporters
|
|
11
|
+
* drop the snippet.
|
|
12
|
+
*/
|
|
13
|
+
import type { Detector } from "../types.js";
|
|
14
|
+
/** Detects committed age identity (private) keys. */
|
|
15
|
+
export declare const ageDetector: Detector;
|
|
16
|
+
//# sourceMappingURL=age.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"age.d.ts","sourceRoot":"","sources":["../../src/detectors/age.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAwB/D,qDAAqD;AACrD,eAAO,MAAM,WAAW,EAAE,QAsBzB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { eachMatch, findingFromRule } from "../detect-utils.js";
|
|
2
|
+
import { CWE_HARDCODED_KEY } from "../cwe.js";
|
|
3
|
+
// age secret keys are Bech32 in uppercase: `AGE-SECRET-KEY-1` + data part.
|
|
4
|
+
const RE_AGE_SECRET = /\bAGE-SECRET-KEY-1[0-9A-Z]{50,}\b/g;
|
|
5
|
+
const RULE_AGE_SECRET = {
|
|
6
|
+
id: "age-secret-key",
|
|
7
|
+
title: "age identity (X25519 private key)",
|
|
8
|
+
description: "An age AGE-SECRET-KEY-1 private identity key committed to disk",
|
|
9
|
+
category: "key-exchange",
|
|
10
|
+
severity: "high",
|
|
11
|
+
confidence: "high",
|
|
12
|
+
algorithm: "X25519",
|
|
13
|
+
hndl: true,
|
|
14
|
+
cwe: CWE_HARDCODED_KEY,
|
|
15
|
+
sensitive: true,
|
|
16
|
+
message: "An age identity (X25519 private key) is committed to disk; every age-wrapped payload addressed to it is decryptable by anyone with this key, and the exposure is retroactive if the ciphertext is already in git history.",
|
|
17
|
+
remediation: "Rotate the age identity and re-encrypt affected data; move the private key to a secret manager. Plan for a post-quantum KEM (ML-KEM) recipient when available.",
|
|
18
|
+
};
|
|
19
|
+
/** Detects committed age identity (private) keys. */
|
|
20
|
+
export const ageDetector = {
|
|
21
|
+
id: "age-identity",
|
|
22
|
+
description: "Committed age identity (X25519 private) keys",
|
|
23
|
+
scope: "config",
|
|
24
|
+
language: "any",
|
|
25
|
+
rules: [RULE_AGE_SECRET],
|
|
26
|
+
appliesTo: () => true,
|
|
27
|
+
detect({ file, content }) {
|
|
28
|
+
if (!content.includes("AGE-SECRET-KEY-1"))
|
|
29
|
+
return [];
|
|
30
|
+
const findings = [];
|
|
31
|
+
eachMatch(RE_AGE_SECRET, content, (m) => findings.push(findingFromRule(RULE_AGE_SECRET, {
|
|
32
|
+
file,
|
|
33
|
+
content,
|
|
34
|
+
index: m.index,
|
|
35
|
+
matchLength: m[0].length,
|
|
36
|
+
})));
|
|
37
|
+
return findings;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=age.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"age.js","sourceRoot":"","sources":["../../src/detectors/age.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,2EAA2E;AAC3E,MAAM,aAAa,GAAG,oCAAoC,CAAC;AAE3D,MAAM,eAAe,GAAa;IAChC,EAAE,EAAE,gBAAgB;IACpB,KAAK,EAAE,mCAAmC;IAC1C,WAAW,EAAE,gEAAgE;IAC7E,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,IAAI;IACV,GAAG,EAAE,iBAAiB;IACtB,SAAS,EAAE,IAAI;IACf,OAAO,EACL,2NAA2N;IAC7N,WAAW,EACT,gKAAgK;CACnK,CAAC;AAEF,qDAAqD;AACrD,MAAM,CAAC,MAAM,WAAW,GAAa;IACnC,EAAE,EAAE,cAAc;IAClB,WAAW,EAAE,8CAA8C;IAC3D,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE,CAAC,eAAe,CAAC;IACxB,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI;IACrB,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;QACtB,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;YAAE,OAAO,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,SAAS,CAAC,aAAa,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CACtC,QAAQ,CAAC,IAAI,CACX,eAAe,CAAC,eAAe,EAAE;YAC/B,IAAI;YACJ,OAAO;YACP,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM;SACzB,CAAC,CACH,CACF,CAAC;QACF,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC","sourcesContent":["/**\n * Config detector: age IDENTITY (private) keys committed to disk. The secrets\n * detector flags age *recipients* (`age1…` public keys); this flags the far worse\n * case — an `AGE-SECRET-KEY-1…` private identity checked into a repo or config.\n * age identities are X25519 secret keys: a classical key-agreement secret that,\n * once leaked or committed, lets an attacker decrypt every age-wrapped payload\n * addressed to it (harvest-now-decrypt-later, and retroactively un-fixable if the\n * ciphertext is already in git history).\n *\n * The matched value IS the private key, so it is marked sensitive and reporters\n * drop the snippet.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { eachMatch, findingFromRule } from \"../detect-utils.js\";\nimport { CWE_HARDCODED_KEY } from \"../cwe.js\";\n\n// age secret keys are Bech32 in uppercase: `AGE-SECRET-KEY-1` + data part.\nconst RE_AGE_SECRET = /\\bAGE-SECRET-KEY-1[0-9A-Z]{50,}\\b/g;\n\nconst RULE_AGE_SECRET: RuleMeta = {\n id: \"age-secret-key\",\n title: \"age identity (X25519 private key)\",\n description: \"An age AGE-SECRET-KEY-1 private identity key committed to disk\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"X25519\",\n hndl: true,\n cwe: CWE_HARDCODED_KEY,\n sensitive: true,\n message:\n \"An age identity (X25519 private key) is committed to disk; every age-wrapped payload addressed to it is decryptable by anyone with this key, and the exposure is retroactive if the ciphertext is already in git history.\",\n remediation:\n \"Rotate the age identity and re-encrypt affected data; move the private key to a secret manager. Plan for a post-quantum KEM (ML-KEM) recipient when available.\",\n};\n\n/** Detects committed age identity (private) keys. */\nexport const ageDetector: Detector = {\n id: \"age-identity\",\n description: \"Committed age identity (X25519 private) keys\",\n scope: \"config\",\n language: \"any\",\n rules: [RULE_AGE_SECRET],\n appliesTo: () => true,\n detect({ file, content }): Finding[] {\n if (!content.includes(\"AGE-SECRET-KEY-1\")) return [];\n const findings: Finding[] = [];\n eachMatch(RE_AGE_SECRET, content, (m) =>\n findings.push(\n findingFromRule(RULE_AGE_SECRET, {\n file,\n content,\n index: m.index,\n matchLength: m[0].length,\n }),\n ),\n );\n return findings;\n },\n};\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config detector: classical asymmetric keys provisioned by Ansible's
|
|
3
|
+
* `community.crypto` collection. Playbooks that mint TLS/SSH key material with
|
|
4
|
+
* `openssl_privatekey` / `openssl_csr` / `x509_certificate` provision real
|
|
5
|
+
* classical keys the language packs never see — the IaC surface, in YAML.
|
|
6
|
+
*
|
|
7
|
+
* Gated to a `community.crypto` / `openssl_privatekey` marker so a bare
|
|
8
|
+
* `type: RSA` in unrelated YAML does not fire.
|
|
9
|
+
*/
|
|
10
|
+
import type { Detector } from "../types.js";
|
|
11
|
+
/** Detects classical asymmetric keys provisioned by Ansible community.crypto. */
|
|
12
|
+
export declare const ansibleDetector: Detector;
|
|
13
|
+
//# sourceMappingURL=ansible.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansible.d.ts","sourceRoot":"","sources":["../../src/detectors/ansible.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AAsF/D,iFAAiF;AACjF,eAAO,MAAM,eAAe,EAAE,QA2C7B,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { eachMatch, findingFromRule, hasExtension, maskCommentLines } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO } from "../cwe.js";
|
|
3
|
+
const YAML_EXTENSIONS = [".yml", ".yaml"];
|
|
4
|
+
const RE_ANSIBLE_RSA = /\btype:\s*["']?RSA\b/g;
|
|
5
|
+
const RE_ANSIBLE_ECC = /\btype:\s*["']?ECC\b/g;
|
|
6
|
+
// community.crypto also supports these `type:` values, all classical.
|
|
7
|
+
const RE_ANSIBLE_EDDSA = /\btype:\s*["']?Ed(?:25519|448)\b/g;
|
|
8
|
+
const RE_ANSIBLE_XDH = /\btype:\s*["']?X(?:25519|448)\b/g;
|
|
9
|
+
const RE_ANSIBLE_DSA = /\btype:\s*["']?DSA\b/g;
|
|
10
|
+
const RULE_ANSIBLE_RSA = {
|
|
11
|
+
id: "ansible-openssl-rsa",
|
|
12
|
+
title: "Ansible community.crypto RSA key",
|
|
13
|
+
description: "Ansible community.crypto openssl_privatekey/csr with type: RSA",
|
|
14
|
+
category: "kem",
|
|
15
|
+
severity: "medium",
|
|
16
|
+
confidence: "high",
|
|
17
|
+
algorithm: "RSA",
|
|
18
|
+
hndl: true,
|
|
19
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
20
|
+
message: "Ansible provisions a classical RSA key (community.crypto), which is not quantum-safe.",
|
|
21
|
+
remediation: "Plan migration to PQC keys (ML-KEM-768 / ML-DSA-65) as the collection adds support.",
|
|
22
|
+
};
|
|
23
|
+
const RULE_ANSIBLE_ECC = {
|
|
24
|
+
id: "ansible-openssl-ecc",
|
|
25
|
+
title: "Ansible community.crypto EC key",
|
|
26
|
+
description: "Ansible community.crypto openssl_privatekey/csr with type: ECC",
|
|
27
|
+
category: "key-exchange",
|
|
28
|
+
severity: "medium",
|
|
29
|
+
confidence: "high",
|
|
30
|
+
algorithm: "ECDH",
|
|
31
|
+
hndl: true,
|
|
32
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
33
|
+
message: "Ansible provisions a classical EC key (community.crypto); EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).",
|
|
34
|
+
remediation: "For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).",
|
|
35
|
+
};
|
|
36
|
+
const RULE_ANSIBLE_EDDSA = {
|
|
37
|
+
id: "ansible-openssl-eddsa",
|
|
38
|
+
title: "Ansible community.crypto Ed25519/Ed448 key",
|
|
39
|
+
description: "Ansible community.crypto openssl_privatekey with type: Ed25519/Ed448",
|
|
40
|
+
category: "signature",
|
|
41
|
+
severity: "medium",
|
|
42
|
+
confidence: "high",
|
|
43
|
+
algorithm: "EdDSA",
|
|
44
|
+
hndl: false,
|
|
45
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
46
|
+
message: "Ansible provisions a classical Ed25519/Ed448 key (community.crypto), forgeable by a quantum attacker.",
|
|
47
|
+
remediation: "ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).",
|
|
48
|
+
};
|
|
49
|
+
const RULE_ANSIBLE_XDH = {
|
|
50
|
+
id: "ansible-openssl-xdh",
|
|
51
|
+
title: "Ansible community.crypto X25519/X448 key",
|
|
52
|
+
description: "Ansible community.crypto openssl_privatekey with type: X25519/X448",
|
|
53
|
+
category: "key-exchange",
|
|
54
|
+
severity: "medium",
|
|
55
|
+
confidence: "high",
|
|
56
|
+
algorithm: "X25519",
|
|
57
|
+
hndl: true,
|
|
58
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
59
|
+
message: "Ansible provisions a classical X25519/X448 key-agreement key (community.crypto), which is harvest-now-decrypt-later exposed.",
|
|
60
|
+
remediation: "Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768).",
|
|
61
|
+
};
|
|
62
|
+
const RULE_ANSIBLE_DSA = {
|
|
63
|
+
id: "ansible-openssl-dsa",
|
|
64
|
+
title: "Ansible community.crypto DSA key (deprecated)",
|
|
65
|
+
description: "Ansible community.crypto openssl_privatekey with type: DSA",
|
|
66
|
+
category: "signature",
|
|
67
|
+
severity: "medium",
|
|
68
|
+
confidence: "high",
|
|
69
|
+
algorithm: "DSA",
|
|
70
|
+
hndl: false,
|
|
71
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
72
|
+
message: "Ansible provisions a classical DSA key (community.crypto); deprecated and forgeable by a quantum attacker.",
|
|
73
|
+
remediation: "Rotate off DSA today; migrate to ML-DSA-65 (FIPS 204).",
|
|
74
|
+
};
|
|
75
|
+
/** Detects classical asymmetric keys provisioned by Ansible community.crypto. */
|
|
76
|
+
export const ansibleDetector = {
|
|
77
|
+
id: "ansible-crypto",
|
|
78
|
+
description: "Classical asymmetric keys provisioned by Ansible community.crypto",
|
|
79
|
+
scope: "config",
|
|
80
|
+
language: "any",
|
|
81
|
+
rules: [
|
|
82
|
+
RULE_ANSIBLE_RSA,
|
|
83
|
+
RULE_ANSIBLE_ECC,
|
|
84
|
+
RULE_ANSIBLE_EDDSA,
|
|
85
|
+
RULE_ANSIBLE_XDH,
|
|
86
|
+
RULE_ANSIBLE_DSA,
|
|
87
|
+
],
|
|
88
|
+
appliesTo: (f) => hasExtension(f, YAML_EXTENSIONS),
|
|
89
|
+
detect({ file, content }) {
|
|
90
|
+
// Gate: only inside a community.crypto / openssl_privatekey context.
|
|
91
|
+
if (!content.includes("community.crypto") && !content.includes("openssl_privatekey"))
|
|
92
|
+
return [];
|
|
93
|
+
// YAML `#` comments are not active settings; match over comment-masked content
|
|
94
|
+
// (offsets preserved) so a `# type: RSA` note does not fire.
|
|
95
|
+
const scan = maskCommentLines(content, ["#"]);
|
|
96
|
+
const findings = [];
|
|
97
|
+
const add = (re, rule) => eachMatch(re, scan, (m) => findings.push(findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length })));
|
|
98
|
+
add(RE_ANSIBLE_RSA, RULE_ANSIBLE_RSA);
|
|
99
|
+
add(RE_ANSIBLE_ECC, RULE_ANSIBLE_ECC);
|
|
100
|
+
add(RE_ANSIBLE_EDDSA, RULE_ANSIBLE_EDDSA);
|
|
101
|
+
// Report the matched curve's own algorithm (X448 vs X25519) instead of the rule's
|
|
102
|
+
// default label.
|
|
103
|
+
eachMatch(RE_ANSIBLE_XDH, scan, (m) => findings.push(findingFromRule(RULE_ANSIBLE_XDH, { file, content, index: m.index, matchLength: m[0].length }, m[0].includes("448") ? { algorithm: "X448" } : undefined)));
|
|
104
|
+
add(RE_ANSIBLE_DSA, RULE_ANSIBLE_DSA);
|
|
105
|
+
return findings;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=ansible.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansible.js","sourceRoot":"","sources":["../../src/detectors/ansible.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAChG,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,eAAe,GAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7D,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAC/C,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAC/C,sEAAsE;AACtE,MAAM,gBAAgB,GAAG,mCAAmC,CAAC;AAC7D,MAAM,cAAc,GAAG,kCAAkC,CAAC;AAC1D,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAE/C,MAAM,gBAAgB,GAAa;IACjC,EAAE,EAAE,qBAAqB;IACzB,KAAK,EAAE,kCAAkC;IACzC,WAAW,EAAE,gEAAgE;IAC7E,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,KAAK;IAChB,IAAI,EAAE,IAAI;IACV,GAAG,EAAE,iBAAiB;IACtB,OAAO,EAAE,uFAAuF;IAChG,WAAW,EACT,qFAAqF;CACxF,CAAC;AACF,MAAM,gBAAgB,GAAa;IACjC,EAAE,EAAE,qBAAqB;IACzB,KAAK,EAAE,iCAAiC;IACxC,WAAW,EAAE,gEAAgE;IAC7E,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,MAAM;IACjB,IAAI,EAAE,IAAI;IACV,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,sKAAsK;IACxK,WAAW,EACT,8FAA8F;CACjG,CAAC;AAEF,MAAM,kBAAkB,GAAa;IACnC,EAAE,EAAE,uBAAuB;IAC3B,KAAK,EAAE,4CAA4C;IACnD,WAAW,EAAE,sEAAsE;IACnF,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,OAAO;IAClB,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,uGAAuG;IACzG,WAAW,EAAE,6CAA6C;CAC3D,CAAC;AACF,MAAM,gBAAgB,GAAa;IACjC,EAAE,EAAE,qBAAqB;IACzB,KAAK,EAAE,0CAA0C;IACjD,WAAW,EAAE,oEAAoE;IACjF,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,QAAQ;IACnB,IAAI,EAAE,IAAI;IACV,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,8HAA8H;IAChI,WAAW,EAAE,8DAA8D;CAC5E,CAAC;AACF,MAAM,gBAAgB,GAAa;IACjC,EAAE,EAAE,qBAAqB;IACzB,KAAK,EAAE,+CAA+C;IACtD,WAAW,EAAE,4DAA4D;IACzE,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,KAAK;IAChB,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,4GAA4G;IAC9G,WAAW,EAAE,wDAAwD;CACtE,CAAC;AAEF,iFAAiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAa;IACvC,EAAE,EAAE,gBAAgB;IACpB,WAAW,EAAE,mEAAmE;IAChF,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE;QACL,gBAAgB;QAChB,gBAAgB;QAChB,kBAAkB;QAClB,gBAAgB;QAChB,gBAAgB;KACjB;IACD,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,eAAe,CAAC;IAClD,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;QACtB,qEAAqE;QACrE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAAE,OAAO,EAAE,CAAC;QAChG,+EAA+E;QAC/E,6DAA6D;QAC7D,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,CAAC,EAAU,EAAE,IAAc,EAAE,EAAE,CACzC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CACxB,QAAQ,CAAC,IAAI,CACX,eAAe,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CACnF,CACF,CAAC;QACJ,GAAG,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;QACtC,GAAG,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;QACtC,GAAG,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;QAC1C,kFAAkF;QAClF,iBAAiB;QACjB,SAAS,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CACpC,QAAQ,CAAC,IAAI,CACX,eAAe,CACb,gBAAgB,EAChB,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAC3D,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CACzD,CACF,CACF,CAAC;QACF,GAAG,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;QACtC,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC","sourcesContent":["/**\n * Config detector: classical asymmetric keys provisioned by Ansible's\n * `community.crypto` collection. Playbooks that mint TLS/SSH key material with\n * `openssl_privatekey` / `openssl_csr` / `x509_certificate` provision real\n * classical keys the language packs never see — the IaC surface, in YAML.\n *\n * Gated to a `community.crypto` / `openssl_privatekey` marker so a bare\n * `type: RSA` in unrelated YAML does not fire.\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport { eachMatch, findingFromRule, hasExtension, maskCommentLines } from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO } from \"../cwe.js\";\n\nconst YAML_EXTENSIONS: readonly string[] = [\".yml\", \".yaml\"];\n\nconst RE_ANSIBLE_RSA = /\\btype:\\s*[\"']?RSA\\b/g;\nconst RE_ANSIBLE_ECC = /\\btype:\\s*[\"']?ECC\\b/g;\n// community.crypto also supports these `type:` values, all classical.\nconst RE_ANSIBLE_EDDSA = /\\btype:\\s*[\"']?Ed(?:25519|448)\\b/g;\nconst RE_ANSIBLE_XDH = /\\btype:\\s*[\"']?X(?:25519|448)\\b/g;\nconst RE_ANSIBLE_DSA = /\\btype:\\s*[\"']?DSA\\b/g;\n\nconst RULE_ANSIBLE_RSA: RuleMeta = {\n id: \"ansible-openssl-rsa\",\n title: \"Ansible community.crypto RSA key\",\n description: \"Ansible community.crypto openssl_privatekey/csr with type: RSA\",\n category: \"kem\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Ansible provisions a classical RSA key (community.crypto), which is not quantum-safe.\",\n remediation:\n \"Plan migration to PQC keys (ML-KEM-768 / ML-DSA-65) as the collection adds support.\",\n};\nconst RULE_ANSIBLE_ECC: RuleMeta = {\n id: \"ansible-openssl-ecc\",\n title: \"Ansible community.crypto EC key\",\n description: \"Ansible community.crypto openssl_privatekey/csr with type: ECC\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Ansible provisions a classical EC key (community.crypto); EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).\",\n remediation:\n \"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).\",\n};\n\nconst RULE_ANSIBLE_EDDSA: RuleMeta = {\n id: \"ansible-openssl-eddsa\",\n title: \"Ansible community.crypto Ed25519/Ed448 key\",\n description: \"Ansible community.crypto openssl_privatekey with type: Ed25519/Ed448\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"EdDSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Ansible provisions a classical Ed25519/Ed448 key (community.crypto), forgeable by a quantum attacker.\",\n remediation: \"ML-DSA-65 (FIPS 204) or SLH-DSA (FIPS 205).\",\n};\nconst RULE_ANSIBLE_XDH: RuleMeta = {\n id: \"ansible-openssl-xdh\",\n title: \"Ansible community.crypto X25519/X448 key\",\n description: \"Ansible community.crypto openssl_privatekey with type: X25519/X448\",\n category: \"key-exchange\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"X25519\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Ansible provisions a classical X25519/X448 key-agreement key (community.crypto), which is harvest-now-decrypt-later exposed.\",\n remediation: \"Migrate key agreement to hybrid X25519MLKEM768 (ML-KEM-768).\",\n};\nconst RULE_ANSIBLE_DSA: RuleMeta = {\n id: \"ansible-openssl-dsa\",\n title: \"Ansible community.crypto DSA key (deprecated)\",\n description: \"Ansible community.crypto openssl_privatekey with type: DSA\",\n category: \"signature\",\n severity: \"medium\",\n confidence: \"high\",\n algorithm: \"DSA\",\n hndl: false,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Ansible provisions a classical DSA key (community.crypto); deprecated and forgeable by a quantum attacker.\",\n remediation: \"Rotate off DSA today; migrate to ML-DSA-65 (FIPS 204).\",\n};\n\n/** Detects classical asymmetric keys provisioned by Ansible community.crypto. */\nexport const ansibleDetector: Detector = {\n id: \"ansible-crypto\",\n description: \"Classical asymmetric keys provisioned by Ansible community.crypto\",\n scope: \"config\",\n language: \"any\",\n rules: [\n RULE_ANSIBLE_RSA,\n RULE_ANSIBLE_ECC,\n RULE_ANSIBLE_EDDSA,\n RULE_ANSIBLE_XDH,\n RULE_ANSIBLE_DSA,\n ],\n appliesTo: (f) => hasExtension(f, YAML_EXTENSIONS),\n detect({ file, content }): Finding[] {\n // Gate: only inside a community.crypto / openssl_privatekey context.\n if (!content.includes(\"community.crypto\") && !content.includes(\"openssl_privatekey\")) return [];\n // YAML `#` comments are not active settings; match over comment-masked content\n // (offsets preserved) so a `# type: RSA` note does not fire.\n const scan = maskCommentLines(content, [\"#\"]);\n const findings: Finding[] = [];\n const add = (re: RegExp, rule: RuleMeta) =>\n eachMatch(re, scan, (m) =>\n findings.push(\n findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n add(RE_ANSIBLE_RSA, RULE_ANSIBLE_RSA);\n add(RE_ANSIBLE_ECC, RULE_ANSIBLE_ECC);\n add(RE_ANSIBLE_EDDSA, RULE_ANSIBLE_EDDSA);\n // Report the matched curve's own algorithm (X448 vs X25519) instead of the rule's\n // default label.\n eachMatch(RE_ANSIBLE_XDH, scan, (m) =>\n findings.push(\n findingFromRule(\n RULE_ANSIBLE_XDH,\n { file, content, index: m.index, matchLength: m[0].length },\n m[0].includes(\"448\") ? { algorithm: \"X448\" } : undefined,\n ),\n ),\n );\n add(RE_ANSIBLE_DSA, RULE_ANSIBLE_DSA);\n return findings;\n },\n};\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IaC detector: classical asymmetric cryptography and legacy TLS declared in Azure
|
|
3
|
+
* Bicep (`.bicep`) — Azure's native IaC DSL, distinct from the ARM/CloudFormation
|
|
4
|
+
* JSON the cloudformation detector already covers (Bicep is the *source*; ARM JSON is
|
|
5
|
+
* its compiled output). Bicep provisions the same Key Vault keys and TLS floors, but
|
|
6
|
+
* in its own single-quoted-string syntax that the JSON detector never sees.
|
|
7
|
+
*
|
|
8
|
+
* Covered (gated to `.bicep` files):
|
|
9
|
+
* - `Microsoft.KeyVault/vaults/keys` `kty: 'RSA' | 'RSA-HSM' | 'EC' | 'EC-HSM'` — a
|
|
10
|
+
* classical Key Vault key. RSA is KEM (HNDL); EC feeds ECDSA + ECDH (the ECDH path
|
|
11
|
+
* is HNDL). Gated to a `Microsoft.KeyVault` marker so a stray `kty` elsewhere in a
|
|
12
|
+
* template does not fire.
|
|
13
|
+
* - `minimumTlsVersion: 'TLS1_0' | 'TLS1_1'` — a deprecated TLS floor on storage /
|
|
14
|
+
* app-service / other resources (RFC 8996). Not HNDL (a config weakness), category
|
|
15
|
+
* "tls".
|
|
16
|
+
*/
|
|
17
|
+
import type { Detector } from "../types.js";
|
|
18
|
+
/** Detects classical asymmetric crypto and legacy TLS declared in Azure Bicep. */
|
|
19
|
+
export declare const bicepDetector: Detector;
|
|
20
|
+
//# sourceMappingURL=bicep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bicep.d.ts","sourceRoot":"","sources":["../../src/detectors/bicep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AA8D/D,kFAAkF;AAClF,eAAO,MAAM,aAAa,EAAE,QA2B3B,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { eachMatch, findingFromRule, hasExtension, maskBlockComments, maskCommentLines, } from "../detect-utils.js";
|
|
2
|
+
import { CWE_BROKEN_CRYPTO, CWE_WEAK_STRENGTH } from "../cwe.js";
|
|
3
|
+
const BICEP_EXTENSIONS = [".bicep"];
|
|
4
|
+
// Bicep string values are single-quoted. `kty: 'RSA'` / `'RSA-HSM'` (KEM) and
|
|
5
|
+
// `kty: 'EC'` / `'EC-HSM'` (EC). The `(?<![\w-])` lookbehind stops a longer key name
|
|
6
|
+
// from matching on a `kty` suffix; the value bound keeps `'EC'` from matching `'ECX'`.
|
|
7
|
+
const RE_BICEP_KTY_RSA = /(?<![\w-])kty\s*:\s*'RSA(?:-HSM)?'/g;
|
|
8
|
+
const RE_BICEP_KTY_EC = /(?<![\w-])kty\s*:\s*'EC(?:-HSM)?'/g;
|
|
9
|
+
// Legacy TLS floor. `TLS1_0` / `TLS1_1` are deprecated; `TLS1_2` / `TLS1_3` are fine,
|
|
10
|
+
// and the closing quote keeps this from matching them.
|
|
11
|
+
const RE_BICEP_MIN_TLS = /(?<![\w])minimumTlsVersion\s*:\s*'TLS1_[01]'/g;
|
|
12
|
+
const RULE_BICEP_KTY_RSA = {
|
|
13
|
+
id: "bicep-keyvault-rsa",
|
|
14
|
+
title: "Bicep Azure Key Vault RSA key",
|
|
15
|
+
description: "Azure Bicep Microsoft.KeyVault key with kty: 'RSA'",
|
|
16
|
+
category: "kem",
|
|
17
|
+
severity: "high",
|
|
18
|
+
confidence: "high",
|
|
19
|
+
algorithm: "RSA",
|
|
20
|
+
hndl: true,
|
|
21
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
22
|
+
message: "Bicep provisions a classical RSA Azure Key Vault key, which is not quantum-safe.",
|
|
23
|
+
remediation: "Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).",
|
|
24
|
+
};
|
|
25
|
+
const RULE_BICEP_KTY_EC = {
|
|
26
|
+
id: "bicep-keyvault-ec",
|
|
27
|
+
title: "Bicep Azure Key Vault EC key",
|
|
28
|
+
description: "Azure Bicep Microsoft.KeyVault key with kty: 'EC'",
|
|
29
|
+
category: "key-exchange",
|
|
30
|
+
severity: "high",
|
|
31
|
+
confidence: "high",
|
|
32
|
+
algorithm: "ECDH",
|
|
33
|
+
hndl: true,
|
|
34
|
+
cwe: CWE_BROKEN_CRYPTO,
|
|
35
|
+
message: "Bicep provisions a classical EC Azure Key Vault key; EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).",
|
|
36
|
+
remediation: "For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).",
|
|
37
|
+
};
|
|
38
|
+
const RULE_BICEP_MIN_TLS = {
|
|
39
|
+
id: "bicep-min-tls-legacy",
|
|
40
|
+
title: "Bicep legacy minimum TLS version",
|
|
41
|
+
description: "Azure Bicep resource pins minimumTlsVersion to TLS 1.0/1.1",
|
|
42
|
+
category: "tls",
|
|
43
|
+
severity: "medium",
|
|
44
|
+
confidence: "high",
|
|
45
|
+
hndl: false,
|
|
46
|
+
cwe: CWE_WEAK_STRENGTH,
|
|
47
|
+
message: "Bicep resource pins a deprecated TLS floor (TLS 1.0/1.1); require TLS 1.2+ (1.3).",
|
|
48
|
+
remediation: "Set minimumTlsVersion: 'TLS1_2' and prefer PQC-hybrid key exchange as it lands.",
|
|
49
|
+
};
|
|
50
|
+
/** Detects classical asymmetric crypto and legacy TLS declared in Azure Bicep. */
|
|
51
|
+
export const bicepDetector = {
|
|
52
|
+
id: "bicep-crypto",
|
|
53
|
+
description: "Classical asymmetric crypto and legacy TLS declared in Azure Bicep (IaC)",
|
|
54
|
+
scope: "config",
|
|
55
|
+
language: "any",
|
|
56
|
+
rules: [RULE_BICEP_KTY_RSA, RULE_BICEP_KTY_EC, RULE_BICEP_MIN_TLS],
|
|
57
|
+
appliesTo: (f) => hasExtension(f, BICEP_EXTENSIONS),
|
|
58
|
+
detect({ file, content }) {
|
|
59
|
+
// Bicep comments are `//` and `/* … */`; mask block comments (multi-line) then
|
|
60
|
+
// whole `//` comment lines so a commented-out resource can't fire. Offsets preserved.
|
|
61
|
+
const scan = maskCommentLines(maskBlockComments(content), ["//"]);
|
|
62
|
+
const findings = [];
|
|
63
|
+
const add = (re, rule) => eachMatch(re, scan, (m) => findings.push(findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length })));
|
|
64
|
+
// The bare `kty` field is generic; only treat it as a Key Vault key when the
|
|
65
|
+
// resource type is actually present, mirroring the cloudformation ARM gate.
|
|
66
|
+
if (content.includes("Microsoft.KeyVault")) {
|
|
67
|
+
add(RE_BICEP_KTY_RSA, RULE_BICEP_KTY_RSA);
|
|
68
|
+
add(RE_BICEP_KTY_EC, RULE_BICEP_KTY_EC);
|
|
69
|
+
}
|
|
70
|
+
add(RE_BICEP_MIN_TLS, RULE_BICEP_MIN_TLS);
|
|
71
|
+
return findings;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=bicep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bicep.js","sourceRoot":"","sources":["../../src/detectors/bicep.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,SAAS,EACT,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEjE,MAAM,gBAAgB,GAAsB,CAAC,QAAQ,CAAC,CAAC;AAEvD,8EAA8E;AAC9E,qFAAqF;AACrF,uFAAuF;AACvF,MAAM,gBAAgB,GAAG,qCAAqC,CAAC;AAC/D,MAAM,eAAe,GAAG,oCAAoC,CAAC;AAC7D,sFAAsF;AACtF,uDAAuD;AACvD,MAAM,gBAAgB,GAAG,+CAA+C,CAAC;AAEzE,MAAM,kBAAkB,GAAa;IACnC,EAAE,EAAE,oBAAoB;IACxB,KAAK,EAAE,+BAA+B;IACtC,WAAW,EAAE,oDAAoD;IACjE,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,KAAK;IAChB,IAAI,EAAE,IAAI;IACV,GAAG,EAAE,iBAAiB;IACtB,OAAO,EAAE,kFAAkF;IAC3F,WAAW,EAAE,8EAA8E;CAC5F,CAAC;AACF,MAAM,iBAAiB,GAAa;IAClC,EAAE,EAAE,mBAAmB;IACvB,KAAK,EAAE,8BAA8B;IACrC,WAAW,EAAE,mDAAmD;IAChE,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,MAAM;IACjB,IAAI,EAAE,IAAI;IACV,GAAG,EAAE,iBAAiB;IACtB,OAAO,EACL,iKAAiK;IACnK,WAAW,EACT,8FAA8F;CACjG,CAAC;AACF,MAAM,kBAAkB,GAAa;IACnC,EAAE,EAAE,sBAAsB;IAC1B,KAAK,EAAE,kCAAkC;IACzC,WAAW,EAAE,4DAA4D;IACzE,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,iBAAiB;IACtB,OAAO,EAAE,mFAAmF;IAC5F,WAAW,EAAE,iFAAiF;CAC/F,CAAC;AAEF,kFAAkF;AAClF,MAAM,CAAC,MAAM,aAAa,GAAa;IACrC,EAAE,EAAE,cAAc;IAClB,WAAW,EAAE,0EAA0E;IACvF,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE,CAAC,kBAAkB,EAAE,iBAAiB,EAAE,kBAAkB,CAAC;IAClE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,gBAAgB,CAAC;IACnD,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;QACtB,+EAA+E;QAC/E,sFAAsF;QACtF,MAAM,IAAI,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAClE,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,CAAC,EAAU,EAAE,IAAc,EAAE,EAAE,CACzC,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CACxB,QAAQ,CAAC,IAAI,CACX,eAAe,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CACnF,CACF,CAAC;QACJ,6EAA6E;QAC7E,4EAA4E;QAC5E,IAAI,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;YAC3C,GAAG,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;YAC1C,GAAG,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAC;QAC1C,CAAC;QACD,GAAG,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;QAC1C,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC","sourcesContent":["/**\n * IaC detector: classical asymmetric cryptography and legacy TLS declared in Azure\n * Bicep (`.bicep`) — Azure's native IaC DSL, distinct from the ARM/CloudFormation\n * JSON the cloudformation detector already covers (Bicep is the *source*; ARM JSON is\n * its compiled output). Bicep provisions the same Key Vault keys and TLS floors, but\n * in its own single-quoted-string syntax that the JSON detector never sees.\n *\n * Covered (gated to `.bicep` files):\n * - `Microsoft.KeyVault/vaults/keys` `kty: 'RSA' | 'RSA-HSM' | 'EC' | 'EC-HSM'` — a\n * classical Key Vault key. RSA is KEM (HNDL); EC feeds ECDSA + ECDH (the ECDH path\n * is HNDL). Gated to a `Microsoft.KeyVault` marker so a stray `kty` elsewhere in a\n * template does not fire.\n * - `minimumTlsVersion: 'TLS1_0' | 'TLS1_1'` — a deprecated TLS floor on storage /\n * app-service / other resources (RFC 8996). Not HNDL (a config weakness), category\n * \"tls\".\n */\nimport type { Detector, Finding, RuleMeta } from \"../types.js\";\nimport {\n eachMatch,\n findingFromRule,\n hasExtension,\n maskBlockComments,\n maskCommentLines,\n} from \"../detect-utils.js\";\nimport { CWE_BROKEN_CRYPTO, CWE_WEAK_STRENGTH } from \"../cwe.js\";\n\nconst BICEP_EXTENSIONS: readonly string[] = [\".bicep\"];\n\n// Bicep string values are single-quoted. `kty: 'RSA'` / `'RSA-HSM'` (KEM) and\n// `kty: 'EC'` / `'EC-HSM'` (EC). The `(?<![\\w-])` lookbehind stops a longer key name\n// from matching on a `kty` suffix; the value bound keeps `'EC'` from matching `'ECX'`.\nconst RE_BICEP_KTY_RSA = /(?<![\\w-])kty\\s*:\\s*'RSA(?:-HSM)?'/g;\nconst RE_BICEP_KTY_EC = /(?<![\\w-])kty\\s*:\\s*'EC(?:-HSM)?'/g;\n// Legacy TLS floor. `TLS1_0` / `TLS1_1` are deprecated; `TLS1_2` / `TLS1_3` are fine,\n// and the closing quote keeps this from matching them.\nconst RE_BICEP_MIN_TLS = /(?<![\\w])minimumTlsVersion\\s*:\\s*'TLS1_[01]'/g;\n\nconst RULE_BICEP_KTY_RSA: RuleMeta = {\n id: \"bicep-keyvault-rsa\",\n title: \"Bicep Azure Key Vault RSA key\",\n description: \"Azure Bicep Microsoft.KeyVault key with kty: 'RSA'\",\n category: \"kem\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"RSA\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message: \"Bicep provisions a classical RSA Azure Key Vault key, which is not quantum-safe.\",\n remediation: \"Plan migration to PQC (ML-KEM-768 for encryption, ML-DSA-65 for signatures).\",\n};\nconst RULE_BICEP_KTY_EC: RuleMeta = {\n id: \"bicep-keyvault-ec\",\n title: \"Bicep Azure Key Vault EC key\",\n description: \"Azure Bicep Microsoft.KeyVault key with kty: 'EC'\",\n category: \"key-exchange\",\n severity: \"high\",\n confidence: \"high\",\n algorithm: \"ECDH\",\n hndl: true,\n cwe: CWE_BROKEN_CRYPTO,\n message:\n \"Bicep provisions a classical EC Azure Key Vault key; EC keys feed ECDSA signatures and ECDH key agreement (the ECDH path is harvest-now-decrypt-later exposed).\",\n remediation:\n \"For key agreement: hybrid X25519MLKEM768 (ML-KEM-768). For signatures: ML-DSA-65 (FIPS 204).\",\n};\nconst RULE_BICEP_MIN_TLS: RuleMeta = {\n id: \"bicep-min-tls-legacy\",\n title: \"Bicep legacy minimum TLS version\",\n description: \"Azure Bicep resource pins minimumTlsVersion to TLS 1.0/1.1\",\n category: \"tls\",\n severity: \"medium\",\n confidence: \"high\",\n hndl: false,\n cwe: CWE_WEAK_STRENGTH,\n message: \"Bicep resource pins a deprecated TLS floor (TLS 1.0/1.1); require TLS 1.2+ (1.3).\",\n remediation: \"Set minimumTlsVersion: 'TLS1_2' and prefer PQC-hybrid key exchange as it lands.\",\n};\n\n/** Detects classical asymmetric crypto and legacy TLS declared in Azure Bicep. */\nexport const bicepDetector: Detector = {\n id: \"bicep-crypto\",\n description: \"Classical asymmetric crypto and legacy TLS declared in Azure Bicep (IaC)\",\n scope: \"config\",\n language: \"any\",\n rules: [RULE_BICEP_KTY_RSA, RULE_BICEP_KTY_EC, RULE_BICEP_MIN_TLS],\n appliesTo: (f) => hasExtension(f, BICEP_EXTENSIONS),\n detect({ file, content }): Finding[] {\n // Bicep comments are `//` and `/* … */`; mask block comments (multi-line) then\n // whole `//` comment lines so a commented-out resource can't fire. Offsets preserved.\n const scan = maskCommentLines(maskBlockComments(content), [\"//\"]);\n const findings: Finding[] = [];\n const add = (re: RegExp, rule: RuleMeta) =>\n eachMatch(re, scan, (m) =>\n findings.push(\n findingFromRule(rule, { file, content, index: m.index, matchLength: m[0].length }),\n ),\n );\n // The bare `kty` field is generic; only treat it as a Key Vault key when the\n // resource type is actually present, mirroring the cloudformation ARM gate.\n if (content.includes(\"Microsoft.KeyVault\")) {\n add(RE_BICEP_KTY_RSA, RULE_BICEP_KTY_RSA);\n add(RE_BICEP_KTY_EC, RULE_BICEP_KTY_EC);\n }\n add(RE_BICEP_MIN_TLS, RULE_BICEP_MIN_TLS);\n return findings;\n },\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"c.d.ts","sourceRoot":"","sources":["../../src/detectors/c.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"c.d.ts","sourceRoot":"","sources":["../../src/detectors/c.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,aAAa,CAAC;AA4b/D,8DAA8D;AAC9D,eAAO,MAAM,SAAS,EAAE,QAsGvB,CAAC"}
|
package/dist/detectors/c.js
CHANGED
|
@@ -27,7 +27,9 @@ const RE_C_RSA_VERIFY = /\bRSA_verify\s*\(/g;
|
|
|
27
27
|
const RE_C_RSA_CRYPT = /\bRSA_public_encrypt\s*\(|\bRSA_private_decrypt\s*\(/g;
|
|
28
28
|
// C/OpenSSL legacy TLS configuration (mirrors source.ts tlsDetector): forcing a
|
|
29
29
|
// deprecated protocol version or disabling certificate verification.
|
|
30
|
-
|
|
30
|
+
// TLSv1_method / TLSv1_1_method (+ the client/server-specific variants) and the
|
|
31
|
+
// SSLv2/SSLv3 method constructors — all pin a deprecated protocol (RFC 8996).
|
|
32
|
+
const RE_C_TLS_VERSION = /\b(?:TLSv1(?:_1)?|SSLv2|SSLv3)(?:_client|_server)?_method\b/g;
|
|
31
33
|
const RE_C_TLS_VERIFY_NONE = /\bSSL_VERIFY_NONE\b/g;
|
|
32
34
|
// Embedded C crypto libraries (the "embedded" niche): Mbed TLS and wolfSSL /
|
|
33
35
|
// wolfCrypt. Both use highly-distinctive `mbedtls_*` / `wc_*` prefixes, so the
|
|
@@ -455,9 +457,34 @@ export const cDetector = {
|
|
|
455
457
|
add(RE_C_DSA, RULE_C_DSA);
|
|
456
458
|
add(RE_C_DH, RULE_C_DH);
|
|
457
459
|
add(RE_C_EVP_KEYGEN, RULE_C_EVP_KEYGEN);
|
|
458
|
-
|
|
460
|
+
// `EVP_PKEY_derive` is OpenSSL's entry point for BOTH (EC)DH key agreement and the
|
|
461
|
+
// symmetric KDFs (HKDF/scrypt/TLS1-PRF). Real (EC)DH always sets a peer key via
|
|
462
|
+
// `EVP_PKEY_derive_set_peer`; a KDF never does. So only treat it as classical key
|
|
463
|
+
// agreement when a peer is set, or when no KDF context marker is present.
|
|
464
|
+
const hasKdfCtx = /\bEVP_PKEY_(?:HKDF|SCRYPT|TLS1_PRF)\b/.test(content);
|
|
465
|
+
if (content.includes("EVP_PKEY_derive_set_peer") || !hasKdfCtx) {
|
|
466
|
+
add(RE_C_EVP_DERIVE, RULE_C_EVP_DERIVE);
|
|
467
|
+
}
|
|
459
468
|
add(RE_C_EVP_CRYPT, RULE_C_EVP_CRYPT);
|
|
460
|
-
|
|
469
|
+
// `EVP_DigestSign*` also drives HMAC/CMAC (a symmetric MAC keyed by
|
|
470
|
+
// `EVP_PKEY_new_mac_key(EVP_PKEY_HMAC/CMAC, …)`), which is NOT a classical
|
|
471
|
+
// asymmetric signature. Suppress a `DigestSign*` match ONLY when a MAC key setup
|
|
472
|
+
// sits just before it — per-match, so a real RSA/ECDSA sign elsewhere in the same
|
|
473
|
+
// file is still reported. `DigestVerify*` (no MAC form here) is never suppressed.
|
|
474
|
+
eachMatch(RE_C_EVP_SIGN, content, (m) => {
|
|
475
|
+
const isSign = m[0].includes("DigestSign");
|
|
476
|
+
if (isSign) {
|
|
477
|
+
const back = content.slice(Math.max(0, m.index - 300), m.index);
|
|
478
|
+
if (/EVP_PKEY_new_mac_key|EVP_PKEY_(?:HMAC|CMAC)\b/.test(back))
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
findings.push(findingFromRule(RULE_C_EVP_SIGN, {
|
|
482
|
+
file,
|
|
483
|
+
content,
|
|
484
|
+
index: m.index,
|
|
485
|
+
matchLength: m[0].length,
|
|
486
|
+
}));
|
|
487
|
+
});
|
|
461
488
|
add(RE_C_SODIUM_BOX, RULE_C_SODIUM_BOX);
|
|
462
489
|
add(RE_C_SODIUM_SIGN, RULE_C_SODIUM_SIGN);
|
|
463
490
|
add(RE_C_ECDSA_VERIFY, RULE_C_ECDSA_VERIFY);
|