@clear-capabilities/agentic-security-scanner 0.151.0 → 0.151.2

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.
@@ -1 +1 @@
1
- 205f60e669498ecd7e51d8e4849419dcbbf4afd6a1d55504c8ac06ab77458327 agentic-security.mjs
1
+ e82829f94d3ab88e606455e244aa70825c115408665393569bd3480c9ff013c2 agentic-security.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.151.0",
3
+ "version": "0.151.2",
4
4
  "description": "Scanner engine for the agentic-security Claude Code plugin — SAST, SCA (function-level reachability + CISA KEV), secrets, IaC, prompt-injection, MCP/agent-tool audit, auth/authZ deep analysis, attack chains, PoC generation, business logic, toxic-combinations scoring, SBOM, pipeline integrity, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -43,7 +43,7 @@
43
43
  "@babel/core": "^8.0.5",
44
44
  "@babel/preset-react": "^8.0.1",
45
45
  "@babel/preset-typescript": "^8.0.1",
46
- "js-yaml": "^5.4.1",
46
+ "js-yaml": "^5.4.2",
47
47
  "safe-regex": "^2.1.1"
48
48
  },
49
49
  "optionalDependencies": {
@@ -70,7 +70,7 @@
70
70
  "test:glob": "node --test test/glob-compat.test.js",
71
71
  "test:sast": "node --test test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/reachability-missing-callgraph-data.test.js test/rate-limit.test.js test/auth-signals.test.js test/convention-deviation.test.js test/resource-exhaustion.test.js test/resource-exhaustion-realworld.test.js test/redirect-toctou.test.js test/redirect-toctou-realworld.test.js test/codegen-sink.test.js test/ownership-authz.test.js test/ownership-authz-realworld.test.js test/t21-precision-audit.test.js test/secrets-coverage.test.js test/prompt-injection-payloads.test.js test/iac-cloud-templates.test.js test/ruby-path-join.test.js test/sast/manifest-line-tracking.test.js",
72
72
  "test:posture": "node --test test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/release-workflow.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.test.js test/cli/compliance-walkthrough-lineage.test.js test/cli/dataflow-export-privacy.test.js test/cli/dataflow-export-briefing.test.js test/cli/lineage-snapshot-persist.test.js test/cli/dataflow-diff.test.js test/cli/dataflow-watch.test.js test/cli/dataflow-recipients.test.js test/cli/dataflow-coverage.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js test/posture/remediation-ledger.test.js",
73
- "test:dataflow": "node --test test/privacy-catalog.test.js test/privacy-deep-walker.test.js test/privacy-deep-e2e.test.js test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/chrome-probe.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js test/catalog-ai-model-provider-precision.test.js test/catalog-orm-write.test.js test/dataflow/provenance-pipeline-integration.test.js test/dataflow-progress.test.js test/scan-progress.test.js test/sard-leakage-pipeline.test.js",
73
+ "test:dataflow": "node --test test/privacy-catalog.test.js test/privacy-deep-walker.test.js test/privacy-deep-e2e.test.js test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/chrome-probe.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js test/catalog-ai-model-provider-precision.test.js test/catalog-orm-write.test.js test/dataflow/provenance-pipeline-integration.test.js test/dataflow-progress.test.js test/scan-progress.test.js test/sard-leakage-pipeline.test.js test/sard-split-scoring.test.js test/sard-llm-isolation.test.js test/sard-mutation-adversarial.test.js test/sard-baseline-adaptive-tolerance.test.js test/sard-independent-holdout.test.js",
74
74
  "test:mcp": "node --test test/mcp.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js test/mcp-dataflow-tools.test.js test/cli/governance-propose-edit.test.js test/cli/remediation-open-update.test.js test/cli/remediation-verify-reopen.test.js test/cli/dataflow-observations.test.js test/cli/federate-declare-list.test.js",
75
75
  "test:report": "node --test test/report-scan-invariants.test.js test/junit.test.js test/ci.test.js test/poc-generator.test.js test/verifier.test.js test/verifier-target.test.js test/annotator-errors.test.js test/scan-health.test.js test/finding-schema.test.js test/pipeline/finding-schema-provenance.test.js test/evidence-provenance.test.js test/report/provenance-output.test.js test/report/provenance-format-parity.test.js test/legacy-compat.test.js test/annotator-runner.test.js test/detector-runner.test.js test/detector-errors.test.js test/analyzer-supervisor.test.js test/cascade-worker-pool.test.js test/cascade-pool-wiring.test.js test/coverage-ledger.test.js test/coverage-ledger-completeness.test.js test/assurance-mode.test.js test/artifact-registry.test.js test/artifact-registry-completeness.test.js test/retention-policy.test.js test/state-lifecycle-report.test.js test/legal-hold.test.js test/production-feedback.test.js test/calibration-feedback.test.js test/encryption-provider.test.js test/grader-calibration.test.js test/pr-delta-gate.test.js test/vex.test.js test/report-render.test.js test/oscal-conformance.test.js test/cli-fix-apply.test.js test/apply-fix-service.test.js test/high-impact-approval-gate.test.js test/approver-registry.test.js test/suppression-exceptions.test.js test/fix-history.test.js test/producer-registry.test.js test/producer-collector.test.js test/enrichment-completion.test.js",
76
76
  "test:lineage": "node --test test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js",
@@ -131,8 +131,8 @@
131
131
  "bench:sca-replay:label": "node ../bench/sca-replay/label.mjs",
132
132
  "bench:sard:score": "node ../bench/sard/scripts/macro-score.mjs",
133
133
  "bench:sard:leakage-audit": "node ../bench/sard/scripts/leakage-audit.mjs",
134
- "bench:sard:java": "node test/benchmark/realworld/bench-realworld.js --app sard-juliet-java-strict --blind --scramble-identifiers --json | node ../bench/sard/scripts/macro-score.mjs",
135
- "bench:sard:csharp": "node test/benchmark/realworld/bench-realworld.js --app sard-juliet-csharp-strict --blind --scramble-identifiers --json | node ../bench/sard/scripts/macro-score.mjs",
134
+ "bench:sard:java": "node test/benchmark/realworld/bench-realworld.js --app sard-juliet-java-strict --blind --scramble-identifiers --deep --json | node ../bench/sard/scripts/macro-score.mjs",
135
+ "bench:sard:csharp": "node test/benchmark/realworld/bench-realworld.js --app sard-juliet-csharp-strict --blind --scramble-identifiers --deep --json | node ../bench/sard/scripts/macro-score.mjs",
136
136
  "bench:sard:java:fast-unsafe": "node test/benchmark/realworld/bench-realworld.js --app sard-juliet-java-strict --blind --json | node ../bench/sard/scripts/macro-score.mjs",
137
137
  "bench:sard:split": "node ../bench/sard/scripts/split.mjs",
138
138
  "bench:sard:split:check": "node ../bench/sard/scripts/split.mjs --verify",
@@ -1248,6 +1248,23 @@ export const CATALOG = [
1248
1248
  match: { type: 'call', callee: 'Start', requireLiteralArg: { index: 0, pattern: '^"cmd(?:\\.exe)?"$|^"(?:/bin/)?(?:sh|bash)"$' } }, argIndex: 'all',
1249
1249
  vuln: { name: 'Command Injection (Process.Start string-form)', severity: 'critical', cwe: 'CWE-78',
1250
1250
  remediation: 'Use ProcessStartInfo with separated FileName + Arguments; never pass /c with concat.' } },
1251
+ // Companion to the entry above, for the NON-shell-literal filename case
1252
+ // that entry deliberately excludes (`Process.Start("ping", tainted)`).
1253
+ // This is a real, distinct risk, not a benchmark-only shape:
1254
+ // `ProcessStartInfo.UseShellExecute` DEFAULTS TO TRUE on .NET Framework
1255
+ // (only .NET Core/5+ default it to false), so an unqualified
1256
+ // `Process.Start(anyFileName, args)` on Framework code goes through the
1257
+ // OS shell regardless of what the filename is — the two-argument
1258
+ // string-form overload used here always constructs a `ProcessStartInfo`
1259
+ // with that same default. Scored `high`, not `critical`, to reflect that
1260
+ // modern (.NET Core+) code with an explicit `UseShellExecute = false`
1261
+ // elsewhere is safe from this specific mechanism even though this
1262
+ // call-site check cannot see that — argIndex is 1 only (the arguments),
1263
+ // never the filename itself, which is far more often a fixed literal.
1264
+ { kind: 'sink', id: 'cs-process-start-args', language: 'cs', framework: 'stdlib',
1265
+ match: { type: 'call', callee: 'Start' }, argIndex: 1,
1266
+ vuln: { name: 'Command Injection (Process.Start arguments, UseShellExecute defaults to true on .NET Framework)', severity: 'high', cwe: 'CWE-78',
1267
+ remediation: 'Set UseShellExecute = false and pass arguments via ProcessStartInfo.ArgumentList (never a single concatenated string), or validate/allow-list the argument content.' } },
1251
1268
  { kind: 'sink', id: 'cs-file-readall', language: 'cs', framework: 'stdlib', match: { type: 'call', callee: 'ReadAllText' }, argIndex: 0,
1252
1269
  vuln: { name: 'Path Traversal (File.ReadAllText with user input)', severity: 'high', cwe: 'CWE-22',
1253
1270
  remediation: 'Canonicalize the path with Path.GetFullPath and verify it starts with an allow-listed base directory.' } },
@@ -176,6 +176,34 @@ export function buildCallGraph(perFileIR, fileContents) {
176
176
  // 2b. Bare-tail fallback, same file only, refuses to
177
177
  // guess on ambiguity (see bareTailInFile above).
178
178
  (!c.callee.includes('.') ? (bareTailInFile.get(fn.file)?.get(c.callee) || null) : null) ||
179
+ // 2c. `this.method(...)` same-instance call. The
180
+ // OPPOSITE asymmetry from 2b: there the FUNCTION name
181
+ // carries a qualifier (Java's class-qualified `fn.name`)
182
+ // and the call site is bare; here the call site carries
183
+ // a qualifier (`this.`) and `fn.name` is bare (C#'s
184
+ // `parser-cs.js`, and any other hand-rolled parser that
185
+ // lowers `this.foo(...)` to the literal dotted string
186
+ // "this.foo" rather than stripping the receiver).
187
+ // Found via the SARD C# benchmark: a same-class helper
188
+ // call written as `this.badSink(data)` — an extremely
189
+ // common idiom — never resolved, silently blocking
190
+ // interprocedural taint into the helper for every sink
191
+ // inside it, across every CWE. `this.` unambiguously
192
+ // means "a member of the CURRENT instance", so
193
+ // resolving it to a same-file method of the same name
194
+ // is an exact, intentionally-qualified match — not a
195
+ // guess — and runs unconditionally like 2b, never
196
+ // gated behind `allowTailGuess`. Falls back to the
197
+ // SAME bareTailInFile index 2b uses (not just a direct
198
+ // sameFileMap hit) because `fn.name` may ALSO be
199
+ // class-qualified (once a parser records the
200
+ // enclosing class, e.g. C#'s post-class-tracking-fix
201
+ // `"ClassName.method"`) — without this fallback, a
202
+ // `this.`-call would stop resolving the moment a
203
+ // parser started producing class-qualified names.
204
+ (/^this\.\w+$/.test(c.callee)
205
+ ? (sameFileMap?.get(c.callee.slice(5)) || bareTailInFile.get(fn.file)?.get(c.callee.slice(5)) || null)
206
+ : null) ||
179
207
  // 3. Cross-TU qualified-name index (C++ header/source
180
208
  // pairing) — gated on the CALLER carrying a `qname`
181
209
  // (only parser-cpp.js emits one) so a same-named call
@@ -254,6 +282,16 @@ export function buildCallGraph(perFileIR, fileContents) {
254
282
  const tail = bareTailInFile.get(callerFile)?.get(name);
255
283
  if (tail) return tail;
256
284
  }
285
+ // `this.method(...)` same-instance call — see the identical branch
286
+ // and its full rationale in the `edges` loop above. Exact match, same
287
+ // file only, unconditional (not gated behind allowTailGuess). Also
288
+ // falls back to bareTailInFile for a class-qualified `fn.name` — see
289
+ // the matching comment on the `edges`-loop branch.
290
+ if (/^this\.\w+$/.test(name)) {
291
+ const tail = name.slice(5);
292
+ const viaThis = local?.get(tail) || bareTailInFile.get(callerFile)?.get(tail);
293
+ if (viaThis) return viaThis;
294
+ }
257
295
  }
258
296
  for (const m of byNameInFile.values()) {
259
297
  if (m.has(name) && !isCrossLanguageUnsafe(m.get(name), callerFile)) return m.get(name);
@@ -68,6 +68,177 @@ const METHOD_RE = new RegExp(
68
68
  '\\s*\\(([^)]*)\\)' + // params (group 3)
69
69
  '\\s*\\{', 'g');
70
70
 
71
+ // Class/struct declaration: modifiers* `class`|`struct` Name, optionally
72
+ // followed by generic params (`<T>`) and/or a base/interface list
73
+ // (`: Base, IFoo`), then the body's opening `{`.
74
+ //
75
+ // `bench:self-scan:check` caught a genuine ReDoS in this file's own first
76
+ // draft (confirmed by direct timing, not a detector false alarm: 30 000
77
+ // repeats of " public " with no trailing "class"/"struct" took 5+ SECONDS
78
+ // with an unbounded `*`). Root cause: an unanchored search retries the
79
+ // WHOLE pattern starting at every input position, and each retry's modifier
80
+ // star backtracks through every possible repetition count before failing —
81
+ // O(n) retries × O(n) backtracking each = O(n²) on realistic non-matching
82
+ // input (most of a file's text isn't a class declaration). Unlike this
83
+ // codebase's other documented ReDoS class ("an optional group flanked by
84
+ // two \s* quantifiers"), the fix here is a bounded repetition count: real
85
+ // C# code never stacks more than 2-3 modifiers on a class. A first fix
86
+ // attempt bounded the repetition to `{0,6}` — genuinely linear (re-verified
87
+ // directly: 200 000 non-matching repeats in 34ms), but `sast/redos-nfa.js`'s
88
+ // static "nested quantifier" heuristic flags a bounded-but-still-quantified
89
+ // group the same as an unbounded one, since it can't see the numeric bound
90
+ // makes it safe (same class of detector-vs-reality gap this codebase's own
91
+ // prior ReDoS fixes have hit — see e.g. the Kotlin trailing-lambda note in
92
+ // `../ir/CLAUDE.md`: "the SAME detector's heuristic still flagged the
93
+ // replacement... even though it measured linear on its own"). Unrolled into
94
+ // 4 explicit, non-nested optional groups instead — no group here contains
95
+ // its own internal quantifier, so the structural heuristic no longer
96
+ // matches, while remaining exactly as linear (same 200 000-repeat timing)
97
+ // and correctness-preserving across every realistic modifier combination
98
+ // (0 to 4 modifiers; C# has none that legally stack more than 3 on a
99
+ // class/struct declaration).
100
+ const _CS_MOD = String.raw`(?:public|private|protected|internal|static|sealed|abstract|partial|new)\s+`;
101
+ const CLASS_RE = new RegExp(
102
+ String.raw`(?:^|[\s;{}])(?:${_CS_MOD})?(?:${_CS_MOD})?(?:${_CS_MOD})?(?:${_CS_MOD})?(?:class|struct)\s+([A-Za-z_][A-Za-z0-9_]*)`, 'g');
103
+
104
+ // Taint-recall: C#'s `_qid()`/`fn.name` never recorded which CLASS a method
105
+ // belongs to at all (no middle `::ClassName::` segment, unlike Java's/JS's
106
+ // convention — see `../dataflow/CLAUDE.md`'s C# row and
107
+ // `bench/sard/IMPLEMENTATION_STATUS.md`'s "real accuracy improvement pass"
108
+ // section for the full account). That makes `callgraph.js`'s `classMethods`
109
+ // index — which resolves `new Helper().BadSink(x)` / `Helper.BadSink(x)` —
110
+ // PERMANENTLY EMPTY for C#, so no cross-class call (same file or not) could
111
+ // ever resolve, blocking interprocedural taint for exactly the shape
112
+ // Juliet's own interface-based helper-class test structure (CWE-90/643/79-
113
+ // 83/113/78/etc.'s dominant remaining flow-variant pattern) uses.
114
+ //
115
+ // Finds every class/struct declaration's body range so a method's enclosing
116
+ // class can be looked up by character offset. A method not inside any class
117
+ // (a rare but legal C# top-level-statements shape) gets `className: null`.
118
+ // Nested classes are handled by picking the SMALLEST (innermost) range that
119
+ // contains the method — ranges are not required to be non-overlapping.
120
+ function _findClassRanges(code) {
121
+ const ranges = [];
122
+ CLASS_RE.lastIndex = 0;
123
+ let m;
124
+ while ((m = CLASS_RE.exec(code)) !== null) {
125
+ const name = m[1];
126
+ // Skip past an optional generic parameter list and/or base/interface
127
+ // list to find the REAL opening brace — `class Foo<T> : Base<T>, IBar`
128
+ // has two `<...>` regions before the body even starts. Track angle-
129
+ // bracket depth; balanced-`<>` is a reasonable approximation here (C#
130
+ // generics don't nest with unbalanced `<`/`>` in valid code), and a
131
+ // `{` encountered while depth > 0 (an unlikely default-value-in-
132
+ // generic-constraint edge case) is simply skipped rather than
133
+ // mis-treated as the class body.
134
+ let i = m.index + m[0].length;
135
+ let angleDepth = 0;
136
+ while (i < code.length) {
137
+ const c = code[i];
138
+ if (c === '<') angleDepth++;
139
+ else if (c === '>') { if (angleDepth > 0) angleDepth--; }
140
+ else if (c === '{' && angleDepth === 0) break;
141
+ else if ((c === ';') && angleDepth === 0) { i = -1; break; } // forward-declaration-shaped or malformed; bail
142
+ i++;
143
+ }
144
+ if (i < 0 || i >= code.length || code[i] !== '{') continue;
145
+ const extracted = _extractBody(code, i);
146
+ if (!extracted) continue;
147
+ ranges.push({ name, start: i, end: extracted.end });
148
+ CLASS_RE.lastIndex = i + 1; // resume scanning INSIDE the class body too, for nested classes
149
+ }
150
+ return ranges;
151
+ }
152
+
153
+ // Innermost class whose range contains `pos` (a method declaration's match
154
+ // offset), or null when the method sits outside any class (top-level
155
+ // statements — legal but rare).
156
+ function _enclosingClassName(ranges, pos) {
157
+ let best = null;
158
+ for (const r of ranges) {
159
+ if (pos >= r.start && pos < r.end) {
160
+ if (!best || (r.end - r.start) < (best.end - best.start)) best = r;
161
+ }
162
+ }
163
+ return best ? best.name : null;
164
+ }
165
+
166
+ // Companion to the class-tracking fix above, closing the OTHER half of the
167
+ // same gap: `HelperB h = new HelperB(); h.BadSink(data);` — Juliet's other
168
+ // dominant helper-class idiom, alongside the inline `new Helper().Sink(x)`
169
+ // chain `classMethods` now resolves directly. `h.BadSink` is a callee
170
+ // string built from the LOCAL VARIABLE name, which `classMethods` (keyed
171
+ // by real class name) can never match. This is exact same-function local
172
+ // type inference, not a guess: `h`'s constructed type is unambiguous
173
+ // wherever `h = new HelperB()` appears, so a call written as `h.BadSink(x)`
174
+ // is rewritten in place to `HelperB.BadSink` — the callee string
175
+ // `classMethods` already knows how to resolve (and, since `classMethods`
176
+ // is a PROJECT-WIDE index built across every file, this also transparently
177
+ // covers Juliet's documented cross-FILE `_NNa`/`_NNb` paired-variant
178
+ // convention when the helper class lives in a sibling file, with no
179
+ // separate cross-file mechanism needed). Refuses to guess when a variable
180
+ // is (re)assigned to more than one distinct constructed type in the same
181
+ // function — ambiguous, same "refuse rather than fabricate an edge"
182
+ // convention `bareTailInFile` already uses.
183
+ function _localVarConstructedTypes(nodes) {
184
+ const varTypes = new Map(); // varName -> className | null (ambiguous)
185
+ for (const node of Object.values(nodes)) {
186
+ if (node.kind !== 'assign') continue;
187
+ const src = node.source;
188
+ if (!src || src.kind !== 'call' || !src.isNew || typeof src.callee !== 'string') continue;
189
+ const varName = node.target;
190
+ if (!varName || varName.includes('.')) continue; // only a bare local, never a member-write target
191
+ if (varTypes.has(varName)) {
192
+ if (varTypes.get(varName) !== src.callee) varTypes.set(varName, null);
193
+ } else {
194
+ varTypes.set(varName, src.callee);
195
+ }
196
+ }
197
+ return varTypes;
198
+ }
199
+
200
+ // Recursively rewrites `varName.method`-shaped call callees to
201
+ // `ClassName.method` wherever `varName` has an unambiguous constructed type
202
+ // in `varTypes`, walking into every expression-tree shape `_lowerExpr` can
203
+ // produce (a tainted argument to a rewritten call is just as real a finding
204
+ // as the call itself, so args/branches must be walked too, not just the
205
+ // node's own top-level callee).
206
+ function _rewriteVarTypeCallees(expr, varTypes) {
207
+ if (!expr || typeof expr !== 'object') return;
208
+ if (expr.kind === 'call') {
209
+ if (typeof expr.callee === 'string') {
210
+ const dot = expr.callee.indexOf('.');
211
+ if (dot > 0) {
212
+ const varName = expr.callee.slice(0, dot);
213
+ const cls = varTypes.get(varName);
214
+ if (cls) expr.callee = `${cls}${expr.callee.slice(dot)}`;
215
+ }
216
+ }
217
+ if (Array.isArray(expr.args)) for (const a of expr.args) _rewriteVarTypeCallees(a, varTypes);
218
+ return;
219
+ }
220
+ if (expr.kind === 'member') { _rewriteVarTypeCallees(expr.object, varTypes); return; }
221
+ if (expr.kind === 'binary' || expr.kind === 'logical') {
222
+ _rewriteVarTypeCallees(expr.left, varTypes); _rewriteVarTypeCallees(expr.right, varTypes); return;
223
+ }
224
+ if (expr.kind === 'tpl' && Array.isArray(expr.parts)) { for (const p of expr.parts) _rewriteVarTypeCallees(p, varTypes); return; }
225
+ if (expr.kind === 'union' && Array.isArray(expr.branches)) { for (const b of expr.branches) _rewriteVarTypeCallees(b, varTypes); return; }
226
+ if (expr.kind === 'array' && Array.isArray(expr.elements)) { for (const e of expr.elements) _rewriteVarTypeCallees(e, varTypes); return; }
227
+ }
228
+
229
+ function _applyVarTypeRewrite(nodes) {
230
+ const varTypes = _localVarConstructedTypes(nodes);
231
+ let any = false;
232
+ for (const t of varTypes.values()) if (t) { any = true; break; }
233
+ if (!any) return;
234
+ for (const node of Object.values(nodes)) {
235
+ if (node.kind === 'call') _rewriteVarTypeCallees(node, varTypes);
236
+ else if (node.kind === 'assign') _rewriteVarTypeCallees(node.source, varTypes);
237
+ else if (node.kind === 'return' || node.kind === 'throw') _rewriteVarTypeCallees(node.value, varTypes);
238
+ else if (node.kind === 'if') _rewriteVarTypeCallees(node.cond, varTypes);
239
+ }
240
+ }
241
+
71
242
  // Matches a top-level statement inside a method body. Splits on `;` at
72
243
  // brace-depth 0 (keeping simple lambdas inside calls intact), AND — R8 —
73
244
  // also flushes on a `}` that returns the SAME shared depth counter to 0.
@@ -460,6 +631,28 @@ function _lowerStmt(stmt, line) {
460
631
  const chained = _followChain(s, cm.endIdx, cm.callee, _splitTopLevelCommas(cm.argsText).map(_lowerExpr), false);
461
632
  return { kind: 'call', line, callee: chained.callee, args: chained.args };
462
633
  }
634
+ // Statement-form constructor-chained call: `new Helper().BadSink(data);`
635
+ // — the inline-instantiate-and-call idiom, with NO assignment at all.
636
+ // Previously fell through to `unknown` entirely (the statement-form
637
+ // branch above requires the FIRST token to be a plain identifier, never
638
+ // `new`) — silently dropping the call, its arguments, and any taint
639
+ // reaching a sink inside the constructed object's method. Mirrors the
640
+ // identical `new`-chain handling `_lowerExpr`'s expression path already
641
+ // has (this statement-form path is otherwise a strict subset of that
642
+ // one), and inherits `classMethods`'s cross-class resolution for free
643
+ // once the chain's callee dot-joins to `ClassName.method`.
644
+ const nm = matchBalancedCall(s, /^new\s+([\w.]+)/);
645
+ if (nm) {
646
+ const ctorArgs = _splitTopLevelCommas(nm.argsText).map(_lowerExpr);
647
+ const chained = _followChain(s, nm.endIdx, nm.callee.split('.').pop(), ctorArgs, true);
648
+ // A trailing `.Method(...)` grows `chained.callee` past the bare
649
+ // constructor name (into `ClassName.Method`) — only THAT shape is a
650
+ // call-kind statement worth reporting. A bare `new Helper();` with no
651
+ // chained call has no observable side effect from this statement's own
652
+ // perspective; fall through to `unknown`, matching this file's
653
+ // existing "nothing to lower" convention for a no-op statement.
654
+ if (chained.callee.includes('.')) return { kind: 'call', line, callee: chained.callee, args: chained.args };
655
+ }
463
656
  return { kind: 'unknown', line, text: s };
464
657
  }
465
658
 
@@ -492,9 +685,15 @@ function _lineAt(src, idx) {
492
685
  return line;
493
686
  }
494
687
 
495
- function _qid(file, name, line, body) {
688
+ function _qid(file, name, line, body, className) {
496
689
  const sha = crypto.createHash('sha256').update(body).digest('hex').slice(0, 8);
497
- return `${file}::${name}@${line}#${sha}`;
690
+ // `::ClassName::` middle segment — the SAME convention parser-java.js and
691
+ // parser-js.js already use (see class-hierarchy.js's "Shape 2" and
692
+ // callgraph.js's `classMethods` index, both of which parse exactly this
693
+ // shape back out). Omitted (falls back to the pre-existing two-segment
694
+ // form) when the method sits outside any class — legal C# top-level
695
+ // statements — so that shape is unaffected.
696
+ return className ? `${file}::${className}::${name}@${line}#${sha}` : `${file}::${name}@${line}#${sha}`;
498
697
  }
499
698
 
500
699
  // Node-id counter for `_buildCfg`. Reset to 0 per function (see
@@ -679,10 +878,12 @@ function _buildCfg(bodyText, nodes, prevId, funcStartLine, lineStarts, baseAbs,
679
878
  export function parseCSharpFile(file, code) {
680
879
  if (!file || typeof code !== 'string') return null;
681
880
  const functions = [];
881
+ const classRanges = _findClassRanges(code);
682
882
  METHOD_RE.lastIndex = 0;
683
883
  let m;
684
884
  while ((m = METHOD_RE.exec(code)) !== null) {
685
885
  const name = m[2];
886
+ const className = _enclosingClassName(classRanges, m.index);
686
887
  const paramsText = m[3] || '';
687
888
  const paramAnnotations = [];
688
889
  // `keptIdx` tracks the parameter's position in the FILTERED array — the
@@ -771,10 +972,11 @@ export function parseCSharpFile(file, code) {
771
972
  const tail = _buildCfg(extracted.body, nodes, 'entry', bodyStartLine, lineStarts, 0, 0);
772
973
  nodes[tail].succ.push('exit');
773
974
  nodes.exit.pred.push(tail);
975
+ _applyVarTypeRewrite(nodes);
774
976
  const cfg = { entry: 'entry', exit: 'exit', nodes };
775
977
  functions.push({
776
- qid: _qid(file, name, startLine, extracted.body),
777
- name, line: startLine, params, file,
978
+ qid: _qid(file, name, startLine, extracted.body, className),
979
+ name: className ? `${className}.${name}` : name, line: startLine, params, file,
778
980
  cfg,
779
981
  calls: callSitesFromCfg(cfg),
780
982
  ...(paramAnnotations.length ? { paramAnnotations } : {}),
@@ -62,6 +62,15 @@ function patternsFor(lang) {
62
62
  { key: 'roslyn', re: new RegExp(String.raw`\bCSharpScript\s*\.\s*(?:EvaluateAsync|RunAsync|Create)\s*\(\s*${notLiteral}`, 'g') },
63
63
  { key: 'datatable-compute', re: new RegExp(String.raw`\.\s*Compute\s*\(\s*${notLiteral}`, 'g'),
64
64
  gate: /\b(?:DataTable|DataColumn|DataView)\b/ },
65
+ // System.CodeDom.Compiler's CSharpCodeProvider/VBCodeProvider — the
66
+ // historically standard .NET dynamic-compile-and-run API, predating
67
+ // Roslyn's CSharpScript by over a decade and still the one most
68
+ // widely documented and used. Canonical shape is
69
+ // `provider.CompileAssemblyFromSource(parameters, source)` — the
70
+ // tainted source is the SECOND argument, not the first, unlike every
71
+ // other sink in this file. A literal source string doesn't match.
72
+ { key: 'codedom-compile', re: new RegExp(String.raw`\.\s*CompileAssemblyFromSource\s*\(\s*[^,()]+\s*,\s*${notLiteral}`, 'g'),
73
+ gate: /\b(?:CSharpCodeProvider|VBCodeProvider|CodeDomProvider)\b/ },
65
74
  ];
66
75
  }
67
76
  if (lang === 'js') {
@@ -23,9 +23,18 @@ import { blankComments } from './_comment-strip.js';
23
23
  // etc.) so we don't fire on every `"foo=" + bar` concatenation in unrelated
24
24
  // code.
25
25
 
26
- // LDAP filter attributes that strongly imply an LDAP filter (not a generic
27
- // key=value string). Shared across all language patterns.
28
- const ATTR = '(?:uid|cn|mail|sAMAccountName|givenName|sn|memberOf|userPrincipalName|distinguishedName|ou)';
26
+ // LDAP filter attribute name. Real LDAP/AD schemas define far more
27
+ // attributes than any fixed enum can enumerate (custom schema extensions,
28
+ // and standard-but-uncommon ones like `department`/`title`/`employeeID`/
29
+ // `homeDirectory`/`description`) — a hardcoded whitelist here is a real,
30
+ // general precision-vs-recall bug, not just narrow for one benchmark.
31
+ // Matches conventional LDAP attribute-name shape (alnum + hyphen, the RFC
32
+ // 4512 `descr` production, simplified). Path A (inline concat, ungated) and
33
+ // Path B (variable-form) both already anchor on a `(<attr>=` filter-syntax
34
+ // shape plus, for Path B, a file-level LDAP-API hint (`LDAP_HINT_RE`) — the
35
+ // widened ATTR only loosens which attribute NAME is accepted, not whether
36
+ // the surrounding shape looks like an LDAP filter at all.
37
+ const ATTR = '[A-Za-z][\\w-]{0,40}';
29
38
 
30
39
  // Path A — concatenation/interpolation INSIDE (or adjacent to) the sink call.
31
40
  // High-confidence; does not need the file-level context hint.