@clear-capabilities/agentic-security-scanner 0.74.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/CHANGELOG.md +1580 -0
- package/bin/.agentic-security/findings.json +1577 -0
- package/bin/.agentic-security/last-scan.json +1577 -0
- package/bin/.agentic-security/last-scan.json.sig +1 -0
- package/bin/.agentic-security/scan-history.json +465 -0
- package/bin/.agentic-security/streak.json +25 -0
- package/bin/agentic-security-audit.js +198 -0
- package/bin/agentic-security-consistency.js +80 -0
- package/bin/agentic-security-diff.js +136 -0
- package/bin/agentic-security-lsp.js +12 -0
- package/bin/agentic-security-mcp.js +40 -0
- package/bin/agentic-security-rule.js +153 -0
- package/bin/agentic-security.js +1683 -0
- package/dist/117.index.js +207 -0
- package/dist/178.index.js +250 -0
- package/dist/218.index.js +793 -0
- package/dist/227.index.js +192 -0
- package/dist/301.index.js +167 -0
- package/dist/384.index.js +18 -0
- package/dist/476.index.js +126 -0
- package/dist/513.index.js +373 -0
- package/dist/520.index.js +13 -0
- package/dist/601.index.js +1038 -0
- package/dist/634.index.js +1892 -0
- package/dist/637.index.js +216 -0
- package/dist/660.index.js +131 -0
- package/dist/675.index.js +451 -0
- package/dist/826.index.js +188 -0
- package/dist/830.index.js +133 -0
- package/dist/agentic-security.mjs +272 -0
- package/dist/agentic-security.mjs.sha256 +1 -0
- package/dist/calibration-seed.json +27 -0
- package/package.json +77 -0
- package/src/.agentic-security/findings.json +80844 -0
- package/src/.agentic-security/last-scan.json +80844 -0
- package/src/.agentic-security/last-scan.json.sig +1 -0
- package/src/.agentic-security/scan-history.json +8408 -0
- package/src/.agentic-security/streak.json +26 -0
- package/src/badge.js +188 -0
- package/src/compare.js +203 -0
- package/src/dataflow/.agentic-security/findings.json +3487 -0
- package/src/dataflow/.agentic-security/last-scan.json +3487 -0
- package/src/dataflow/.agentic-security/last-scan.json.sig +1 -0
- package/src/dataflow/.agentic-security/scan-history.json +735 -0
- package/src/dataflow/.agentic-security/streak.json +24 -0
- package/src/dataflow/CLAUDE.md +38 -0
- package/src/dataflow/access-paths.js +172 -0
- package/src/dataflow/async-sequencing.js +177 -0
- package/src/dataflow/backward.js +201 -0
- package/src/dataflow/catalog-expanded.js +485 -0
- package/src/dataflow/catalog.js +659 -0
- package/src/dataflow/cross-repo.js +219 -0
- package/src/dataflow/engine.js +588 -0
- package/src/dataflow/exception-flow.js +116 -0
- package/src/dataflow/exploit-prover.js +187 -0
- package/src/dataflow/higher-order.js +221 -0
- package/src/dataflow/ifds.js +347 -0
- package/src/dataflow/implicit-flow.js +129 -0
- package/src/dataflow/incremental.js +229 -0
- package/src/dataflow/index.js +181 -0
- package/src/dataflow/numeric-domain.js +192 -0
- package/src/dataflow/path-feasibility.js +114 -0
- package/src/dataflow/points-to.js +337 -0
- package/src/dataflow/polyglot.js +190 -0
- package/src/dataflow/proven-clean.js +159 -0
- package/src/dataflow/receiver-context.js +76 -0
- package/src/dataflow/sanitizer-proof.js +154 -0
- package/src/dataflow/soft-taint.js +140 -0
- package/src/dataflow/string-domain.js +234 -0
- package/src/dataflow/stub-aware-filter.js +100 -0
- package/src/dataflow/summaries.js +132 -0
- package/src/dataflow/symbolic-exec.js +238 -0
- package/src/dataflow/tabulation.js +135 -0
- package/src/engine.js +7763 -0
- package/src/history-scan.js +229 -0
- package/src/index.js +3 -0
- package/src/integrations/.agentic-security/findings.json +1504 -0
- package/src/integrations/.agentic-security/last-scan.json +1504 -0
- package/src/integrations/.agentic-security/scan-history.json +40 -0
- package/src/integrations/.agentic-security/streak.json +21 -0
- package/src/integrations/index.js +321 -0
- package/src/integrations/tickets.js +200 -0
- package/src/ir/.agentic-security/findings.json +3036 -0
- package/src/ir/.agentic-security/last-scan.json +3036 -0
- package/src/ir/.agentic-security/last-scan.json.sig +1 -0
- package/src/ir/.agentic-security/scan-history.json +364 -0
- package/src/ir/.agentic-security/streak.json +23 -0
- package/src/ir/CLAUDE.md +172 -0
- package/src/ir/callgraph.js +73 -0
- package/src/ir/class-hierarchy.js +195 -0
- package/src/ir/index.js +152 -0
- package/src/ir/parser-cs.js +260 -0
- package/src/ir/parser-java.js +286 -0
- package/src/ir/parser-js.js +413 -0
- package/src/ir/parser-kt.js +258 -0
- package/src/ir/parser-py-cst.js +136 -0
- package/src/ir/parser-py.helper.py +501 -0
- package/src/ir/parser-py.js +312 -0
- package/src/ir/ssa.js +315 -0
- package/src/ir/type-stubs.js +288 -0
- package/src/leaderboard.js +152 -0
- package/src/llm-validator/.agentic-security/findings.json +1891 -0
- package/src/llm-validator/.agentic-security/last-scan.json +1891 -0
- package/src/llm-validator/.agentic-security/last-scan.json.sig +1 -0
- package/src/llm-validator/.agentic-security/scan-history.json +168 -0
- package/src/llm-validator/.agentic-security/streak.json +20 -0
- package/src/llm-validator/consistency.js +141 -0
- package/src/llm-validator/index.js +437 -0
- package/src/lsp/.agentic-security/findings.json +28 -0
- package/src/lsp/.agentic-security/last-scan.json +28 -0
- package/src/lsp/.agentic-security/scan-history.json +79 -0
- package/src/lsp/.agentic-security/streak.json +22 -0
- package/src/lsp/server.js +275 -0
- package/src/mcp/.agentic-security/findings.json +8358 -0
- package/src/mcp/.agentic-security/last-scan.json +8358 -0
- package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
- package/src/mcp/.agentic-security/scan-history.json +1125 -0
- package/src/mcp/.agentic-security/streak.json +22 -0
- package/src/mcp/CLAUDE.md +54 -0
- package/src/mcp/audit.js +136 -0
- package/src/mcp/redact.js +75 -0
- package/src/mcp/server.js +158 -0
- package/src/mcp/stdio.js +83 -0
- package/src/mcp/tools.js +940 -0
- package/src/mcp/validate.js +49 -0
- package/src/personality.js +164 -0
- package/src/poc-video.js +239 -0
- package/src/posture/.agentic-security/findings.json +51239 -0
- package/src/posture/.agentic-security/last-scan.json +51239 -0
- package/src/posture/.agentic-security/last-scan.json.sig +1 -0
- package/src/posture/.agentic-security/scan-history.json +5557 -0
- package/src/posture/.agentic-security/streak.json +24 -0
- package/src/posture/CLAUDE.md +42 -0
- package/src/posture/adversarial-self-test.js +114 -0
- package/src/posture/adversary-agent.js +204 -0
- package/src/posture/agents-memory.js +135 -0
- package/src/posture/ai-code-fingerprint.js +171 -0
- package/src/posture/aibom.js +284 -0
- package/src/posture/api-inventory.js +96 -0
- package/src/posture/attack-playbooks.js +305 -0
- package/src/posture/auditor-agent.js +115 -0
- package/src/posture/auth-posture-import.js +135 -0
- package/src/posture/baseline-compare.js +114 -0
- package/src/posture/blast-radius.js +836 -0
- package/src/posture/bounty-prediction.js +141 -0
- package/src/posture/business-logic.js +239 -0
- package/src/posture/calibration-drift.js +93 -0
- package/src/posture/calibration-seed.json +27 -0
- package/src/posture/calibration.js +204 -0
- package/src/posture/clustering.js +75 -0
- package/src/posture/concurrency-checker.js +265 -0
- package/src/posture/confidence.js +65 -0
- package/src/posture/container-runtime.js +149 -0
- package/src/posture/counterfactual.js +109 -0
- package/src/posture/cross-lang-graphql.js +165 -0
- package/src/posture/cross-lang-grpc.js +166 -0
- package/src/posture/cross-lang-meta.js +101 -0
- package/src/posture/cross-lang-openapi.js +187 -0
- package/src/posture/cross-lang-orm.js +153 -0
- package/src/posture/cross-lang-queues.js +210 -0
- package/src/posture/crown-jewels.js +110 -0
- package/src/posture/custom-rules.js +361 -0
- package/src/posture/cve-alert-daemon.js +433 -0
- package/src/posture/cve-lookup.js +129 -0
- package/src/posture/dead-code.js +430 -0
- package/src/posture/defender-agent.js +158 -0
- package/src/posture/deploy-platform.js +204 -0
- package/src/posture/detector-fuzz.js +61 -0
- package/src/posture/deterministic.js +99 -0
- package/src/posture/drift.js +165 -0
- package/src/posture/epss.js +156 -0
- package/src/posture/exploitability-probability.js +212 -0
- package/src/posture/exploitability.js +121 -0
- package/src/posture/feature-flags.js +110 -0
- package/src/posture/finding-defaults.js +132 -0
- package/src/posture/fix-history.js +411 -0
- package/src/posture/fix-plan.js +121 -0
- package/src/posture/fix-verify-loop.js +157 -0
- package/src/posture/fix-verify.js +130 -0
- package/src/posture/flow-narration.js +105 -0
- package/src/posture/grader-calibration.js +156 -0
- package/src/posture/harness-discovery.js +113 -0
- package/src/posture/holdout-eval.js +144 -0
- package/src/posture/iac-reachability.js +163 -0
- package/src/posture/iam-policy.js +128 -0
- package/src/posture/integrity.js +97 -0
- package/src/posture/learning.js +166 -0
- package/src/posture/license-policy.js +109 -0
- package/src/posture/llm-redteam-prompts.js +418 -0
- package/src/posture/llm-redteam.js +303 -0
- package/src/posture/material-change.js +163 -0
- package/src/posture/mitigation-composite.js +55 -0
- package/src/posture/mttr.js +91 -0
- package/src/posture/network-policy-import.js +126 -0
- package/src/posture/path-predicates.js +99 -0
- package/src/posture/persona-prioritization.js +153 -0
- package/src/posture/poc-cwe-map.js +51 -0
- package/src/posture/poc-generator.js +500 -0
- package/src/posture/policy-gate.js +174 -0
- package/src/posture/pre-incident-archaeology.js +110 -0
- package/src/posture/profile.js +93 -0
- package/src/posture/reachability-filter.js +42 -0
- package/src/posture/regression-test-gen.js +200 -0
- package/src/posture/reverse-blast-radius.js +110 -0
- package/src/posture/router.js +109 -0
- package/src/posture/rule-overrides.js +198 -0
- package/src/posture/rule-pack-signing.js +209 -0
- package/src/posture/rule-packs.js +143 -0
- package/src/posture/rule-synthesis.js +108 -0
- package/src/posture/ruleset-version.js +71 -0
- package/src/posture/sbom.js +129 -0
- package/src/posture/schema-aware-bridge.js +207 -0
- package/src/posture/security-trend.js +87 -0
- package/src/posture/semantic-clone.js +114 -0
- package/src/posture/specification-mining.js +170 -0
- package/src/posture/stable-id.js +75 -0
- package/src/posture/stack-playbook.js +229 -0
- package/src/posture/streak.js +249 -0
- package/src/posture/suppressions.js +135 -0
- package/src/posture/telemetry-ingest.js +112 -0
- package/src/posture/threat-model.js +145 -0
- package/src/posture/three-agent-pipeline.js +74 -0
- package/src/posture/triage.js +146 -0
- package/src/posture/trust-boundary-diagram.js +115 -0
- package/src/posture/type-narrowing.js +129 -0
- package/src/posture/validator-metrics.js +179 -0
- package/src/posture/verifier-ephemeral.js +118 -0
- package/src/posture/verifier-target.js +147 -0
- package/src/posture/verifier.js +257 -0
- package/src/posture/version.js +75 -0
- package/src/posture/waf-ingest.js +200 -0
- package/src/posture/why-fired.js +141 -0
- package/src/pr-comment.js +172 -0
- package/src/pr-delta.js +198 -0
- package/src/report/.agentic-security/findings.json +79 -0
- package/src/report/.agentic-security/last-scan.json +79 -0
- package/src/report/.agentic-security/last-scan.json.sig +1 -0
- package/src/report/.agentic-security/scan-history.json +332 -0
- package/src/report/.agentic-security/streak.json +23 -0
- package/src/report/index.js +1136 -0
- package/src/report/mascot.js +42 -0
- package/src/runScan.js +141 -0
- package/src/sast/.agentic-security/findings.json +5051 -0
- package/src/sast/.agentic-security/last-scan.json +5051 -0
- package/src/sast/.agentic-security/last-scan.json.sig +1 -0
- package/src/sast/.agentic-security/scan-history.json +788 -0
- package/src/sast/.agentic-security/streak.json +23 -0
- package/src/sast/CLAUDE.md +39 -0
- package/src/sast/_comment-strip.js +46 -0
- package/src/sast/agent-tool-escalation.js +131 -0
- package/src/sast/auth-provider.js +171 -0
- package/src/sast/authz.js +236 -0
- package/src/sast/bench-shape/.agentic-security/findings.json +28 -0
- package/src/sast/bench-shape/.agentic-security/last-scan.json +28 -0
- package/src/sast/bench-shape/.agentic-security/scan-history.json +24 -0
- package/src/sast/bench-shape/.agentic-security/streak.json +22 -0
- package/src/sast/bench-shape/index.js +62 -0
- package/src/sast/claude-hook-injection.js +199 -0
- package/src/sast/claude-md-prompt-injection.js +170 -0
- package/src/sast/claude-settings.js +165 -0
- package/src/sast/client-side.js +149 -0
- package/src/sast/cpp-bench-extras.js +122 -0
- package/src/sast/cpp-dataflow.js +430 -0
- package/src/sast/cpp.js +248 -0
- package/src/sast/csharp.js +152 -0
- package/src/sast/csrf.js +82 -0
- package/src/sast/dart-flutter.js +173 -0
- package/src/sast/db-rls.js +147 -0
- package/src/sast/db-taint.js +215 -0
- package/src/sast/defi-deep.js +242 -0
- package/src/sast/deserialization-gadgets.js +113 -0
- package/src/sast/django-hardening.js +230 -0
- package/src/sast/env-hygiene.js +125 -0
- package/src/sast/fastapi-hardening.js +145 -0
- package/src/sast/go-extended.js +84 -0
- package/src/sast/host-header.js +106 -0
- package/src/sast/index.js +17 -0
- package/src/sast/java-ast-folding.js +561 -0
- package/src/sast/java-bench-extras.js +708 -0
- package/src/sast/java-collection-passthrough.js +178 -0
- package/src/sast/java-constant-fold.js +244 -0
- package/src/sast/java-deserialization.js +125 -0
- package/src/sast/jndi.js +104 -0
- package/src/sast/juliet-shape.js +324 -0
- package/src/sast/jwt-exp.js +104 -0
- package/src/sast/kotlin.js +82 -0
- package/src/sast/laravel-hardening.js +198 -0
- package/src/sast/ldap-injection.js +100 -0
- package/src/sast/llm-owasp.js +465 -0
- package/src/sast/llm-stored-prompt.js +103 -0
- package/src/sast/llm-trading-agent.js +161 -0
- package/src/sast/llm.js +308 -0
- package/src/sast/logic.js +140 -0
- package/src/sast/mass-assignment.js +101 -0
- package/src/sast/mcp-audit.js +242 -0
- package/src/sast/mobile-manifest.js +195 -0
- package/src/sast/model-load.js +164 -0
- package/src/sast/mutation-xss.js +87 -0
- package/src/sast/nosql-injection.js +82 -0
- package/src/sast/open-redirect.js +119 -0
- package/src/sast/php.js +91 -0
- package/src/sast/pipeline.js +122 -0
- package/src/sast/primary-cwe-java.js +155 -0
- package/src/sast/prompt-firewall.js +151 -0
- package/src/sast/prompt-template.js +157 -0
- package/src/sast/prototype-pollution.js +112 -0
- package/src/sast/python-sinks.js +195 -0
- package/src/sast/quarkus-hardening.js +102 -0
- package/src/sast/rag-poisoning.js +118 -0
- package/src/sast/rate-limit.js +128 -0
- package/src/sast/response-splitting.js +138 -0
- package/src/sast/ruby.js +108 -0
- package/src/sast/rust.js +105 -0
- package/src/sast/solidity.js +167 -0
- package/src/sast/springboot-hardening.js +186 -0
- package/src/sast/ssrf-cloud-metadata.js +80 -0
- package/src/sast/ssti.js +116 -0
- package/src/sast/swift.js +162 -0
- package/src/sast/toctou.js +95 -0
- package/src/sast/webhook.js +101 -0
- package/src/sast/xpath-injection.js +51 -0
- package/src/sast/xxe.js +140 -0
- package/src/sast/zip-slip.js +200 -0
- package/src/sca/base-images.json +45 -0
- package/src/sca/container.js +107 -0
- package/src/sca/dep-confusion.js +134 -0
- package/src/sca/index.js +6 -0
- package/src/sca/popular-packages.json +41 -0
- package/src/sca/sarif-ingest.js +187 -0
- package/src/sca/vuln-function-hints.json +89 -0
- package/src/secrets/index.js +4 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Minimal LSP server for agentic-security.
|
|
2
|
+
//
|
|
3
|
+
// Speaks the Language Server Protocol over stdio. On every textDocument/
|
|
4
|
+
// didSave (and didOpen), the server runs runScan on the file and emits
|
|
5
|
+
// textDocument/publishDiagnostics with the resulting findings mapped to
|
|
6
|
+
// LSP Diagnostic objects.
|
|
7
|
+
//
|
|
8
|
+
// This is a STARTER implementation — feature-complete enough that JetBrains
|
|
9
|
+
// (via LSP4IJ) and Neovim (via built-in LSP) can both attach and see
|
|
10
|
+
// findings inline. The full feature set (code actions for /fix, inline
|
|
11
|
+
// remediation hover, exploitability tooltip) is future work.
|
|
12
|
+
//
|
|
13
|
+
// Wire-format: vscode-jsonrpc framing (Content-Length headers). Stateless
|
|
14
|
+
// per file — no incremental analysis yet.
|
|
15
|
+
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import * as readline from 'node:readline';
|
|
19
|
+
import { runScan } from '../runScan.js';
|
|
20
|
+
import { resetCustomRulesBudget } from '../posture/custom-rules.js';
|
|
21
|
+
|
|
22
|
+
const PROTOCOL_VERSION = '3.17';
|
|
23
|
+
const SERVER_NAME = 'agentic-security-lsp';
|
|
24
|
+
const SERVER_VERSION = '0.1.0';
|
|
25
|
+
|
|
26
|
+
let _rootUri = null;
|
|
27
|
+
let _rootDir = process.cwd();
|
|
28
|
+
let _stdoutMutex = Promise.resolve();
|
|
29
|
+
const _diagnosticsByUri = new Map();
|
|
30
|
+
|
|
31
|
+
function uriToPath(uri) {
|
|
32
|
+
if (!uri) return null;
|
|
33
|
+
if (uri.startsWith('file://')) return decodeURIComponent(uri.slice(7));
|
|
34
|
+
return uri;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function pathToUri(p) {
|
|
38
|
+
if (!p) return null;
|
|
39
|
+
if (p.startsWith('file://')) return p;
|
|
40
|
+
return 'file://' + encodeURI(path.resolve(p));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function sevToLsp(sev) {
|
|
44
|
+
switch ((sev || '').toLowerCase()) {
|
|
45
|
+
case 'critical': return 1; // Error
|
|
46
|
+
case 'high': return 1; // Error
|
|
47
|
+
case 'medium': return 2; // Warning
|
|
48
|
+
case 'low': return 3; // Information
|
|
49
|
+
default: return 4; // Hint
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findingToDiagnostic(f) {
|
|
54
|
+
const line = Math.max(0, (f.line || 1) - 1);
|
|
55
|
+
return {
|
|
56
|
+
range: {
|
|
57
|
+
start: { line, character: 0 },
|
|
58
|
+
end: { line, character: 200 },
|
|
59
|
+
},
|
|
60
|
+
severity: sevToLsp(f.severity),
|
|
61
|
+
source: 'agentic-security',
|
|
62
|
+
code: f.cwe || f.family || 'finding',
|
|
63
|
+
message: `${f.vuln || 'Security finding'}${f.remediation ? '\n\n' + (typeof f.remediation === 'string' ? f.remediation : '') : ''}`.slice(0, 2000),
|
|
64
|
+
tags: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function send(message) {
|
|
69
|
+
const json = JSON.stringify({ jsonrpc: '2.0', ...message });
|
|
70
|
+
_stdoutMutex = _stdoutMutex.then(() => new Promise(resolve => {
|
|
71
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`, resolve);
|
|
72
|
+
}));
|
|
73
|
+
return _stdoutMutex;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function publishDiagnostics(uri, findings) {
|
|
77
|
+
await send({
|
|
78
|
+
method: 'textDocument/publishDiagnostics',
|
|
79
|
+
params: { uri, diagnostics: findings.map(findingToDiagnostic) },
|
|
80
|
+
});
|
|
81
|
+
_diagnosticsByUri.set(uri, findings);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Manifest / schema files that downstream passes (SCA, cross-language) read.
|
|
85
|
+
// We walk the project tree once per LSP session and cache these so the
|
|
86
|
+
// per-save scan has them.
|
|
87
|
+
const DEP_BASE_NAMES = new Set([
|
|
88
|
+
'package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
|
|
89
|
+
'requirements.txt', 'pyproject.toml', 'poetry.lock', 'Pipfile.lock',
|
|
90
|
+
'composer.json', 'composer.lock', 'Gemfile', 'Gemfile.lock',
|
|
91
|
+
'go.mod', 'Cargo.toml', 'Cargo.lock',
|
|
92
|
+
'pom.xml', 'build.gradle', 'build.gradle.kts',
|
|
93
|
+
]);
|
|
94
|
+
const DEP_EXT_RE = /\.(?:proto|graphql|gql|tf)$/i;
|
|
95
|
+
const DEP_NAME_RE = /(?:openapi|swagger)\.(?:ya?ml|json)$/i;
|
|
96
|
+
|
|
97
|
+
let _depCache = { rootDir: null, depFileContents: {} };
|
|
98
|
+
|
|
99
|
+
function _loadDepFileContents(rootDir) {
|
|
100
|
+
if (_depCache.rootDir === rootDir) return _depCache.depFileContents;
|
|
101
|
+
const out = {};
|
|
102
|
+
const skipDirs = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'target', 'vendor', '.bench-cache']);
|
|
103
|
+
function walk(dir) {
|
|
104
|
+
let entries;
|
|
105
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
106
|
+
for (const e of entries) {
|
|
107
|
+
if (skipDirs.has(e.name)) continue;
|
|
108
|
+
const full = path.join(dir, e.name);
|
|
109
|
+
if (e.isDirectory()) { walk(full); continue; }
|
|
110
|
+
if (!e.isFile()) continue;
|
|
111
|
+
const base = e.name;
|
|
112
|
+
if (DEP_BASE_NAMES.has(base) || DEP_EXT_RE.test(base) || DEP_NAME_RE.test(base)) {
|
|
113
|
+
let stat;
|
|
114
|
+
try { stat = fs.statSync(full); } catch { continue; }
|
|
115
|
+
if (stat.size > 500_000) continue;
|
|
116
|
+
try { out[path.relative(rootDir, full)] = fs.readFileSync(full, 'utf8'); }
|
|
117
|
+
catch { /* skip unreadable */ }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
walk(rootDir);
|
|
122
|
+
_depCache = { rootDir, depFileContents: out };
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function scanFile(uri) {
|
|
127
|
+
const filePath = uriToPath(uri);
|
|
128
|
+
if (!filePath || !fs.existsSync(filePath)) return;
|
|
129
|
+
// Incremental scan (premortem 2R4.5 / 2R-10): hand runScan a single-file
|
|
130
|
+
// fileContents map for the saved code, AND a cached set of dep-manifest /
|
|
131
|
+
// schema files so SCA + cross-language passes have their inputs. Without
|
|
132
|
+
// depFileContents, the LSP path would silently drop CVE / OpenAPI / proto
|
|
133
|
+
// findings on the saved file.
|
|
134
|
+
try {
|
|
135
|
+
const rel = path.relative(_rootDir, filePath);
|
|
136
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
137
|
+
const fileContents = { [rel]: content };
|
|
138
|
+
const depFileContents = _loadDepFileContents(_rootDir);
|
|
139
|
+
// Premortem 4R-12 + 4R-15: reset the per-process custom-rules budget at
|
|
140
|
+
// the start of each LSP scan. Each save is a logical scan session; without
|
|
141
|
+
// the reset, a long-lived LSP server would accumulate budget across saves
|
|
142
|
+
// and eventually start skipping custom rules.
|
|
143
|
+
resetCustomRulesBudget(_rootDir);
|
|
144
|
+
const { scan } = await runScan(_rootDir, { fileContents, depFileContents });
|
|
145
|
+
const findings = (scan.findings || []).filter(f => f.file === rel);
|
|
146
|
+
await publishDiagnostics(uri, findings);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
process.stderr.write(`agentic-security-lsp: scan failed: ${e.message}\n`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function handleInitialize(params) {
|
|
153
|
+
if (params.rootUri) {
|
|
154
|
+
_rootUri = params.rootUri;
|
|
155
|
+
_rootDir = uriToPath(params.rootUri) || process.cwd();
|
|
156
|
+
} else if (params.rootPath) {
|
|
157
|
+
_rootDir = params.rootPath;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
capabilities: {
|
|
161
|
+
textDocumentSync: {
|
|
162
|
+
openClose: true,
|
|
163
|
+
change: 1,
|
|
164
|
+
save: { includeText: false },
|
|
165
|
+
},
|
|
166
|
+
diagnosticProvider: { interFileDependencies: true, workspaceDiagnostics: false },
|
|
167
|
+
},
|
|
168
|
+
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function handleMessage(msg) {
|
|
173
|
+
if (msg.method === 'initialize') {
|
|
174
|
+
return { id: msg.id, result: handleInitialize(msg.params || {}) };
|
|
175
|
+
}
|
|
176
|
+
if (msg.method === 'initialized' || msg.method === 'workspace/didChangeConfiguration') {
|
|
177
|
+
return null; // notification, no response
|
|
178
|
+
}
|
|
179
|
+
if (msg.method === 'shutdown') {
|
|
180
|
+
return { id: msg.id, result: null };
|
|
181
|
+
}
|
|
182
|
+
if (msg.method === 'exit') {
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
if (msg.method === 'textDocument/didOpen') {
|
|
186
|
+
const uri = msg.params?.textDocument?.uri;
|
|
187
|
+
if (uri) scanFile(uri);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (msg.method === 'textDocument/didSave') {
|
|
191
|
+
const uri = msg.params?.textDocument?.uri;
|
|
192
|
+
if (uri) {
|
|
193
|
+
// Premortem 3R-9 / 4R-5: when the user saves a manifest file, the
|
|
194
|
+
// dep-cache entry for THAT file is stale. Granular invalidation (only
|
|
195
|
+
// re-read the saved file from disk) avoids the O(project) re-walk that
|
|
196
|
+
// 3R-9 introduced — important in monorepos where mass manifest edits
|
|
197
|
+
// would otherwise re-scan thousands of files per save.
|
|
198
|
+
const savedPath = uriToPath(uri);
|
|
199
|
+
if (savedPath && _depCache.rootDir === _rootDir) {
|
|
200
|
+
const base = path.basename(savedPath);
|
|
201
|
+
if (DEP_BASE_NAMES.has(base) || DEP_EXT_RE.test(base) || DEP_NAME_RE.test(base)) {
|
|
202
|
+
try {
|
|
203
|
+
const rel = path.relative(_rootDir, savedPath);
|
|
204
|
+
const st = fs.statSync(savedPath);
|
|
205
|
+
if (st.size <= 500_000) {
|
|
206
|
+
_depCache.depFileContents[rel] = fs.readFileSync(savedPath, 'utf8');
|
|
207
|
+
} else {
|
|
208
|
+
delete _depCache.depFileContents[rel];
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
// File vanished between save event and stat — drop from cache.
|
|
212
|
+
try {
|
|
213
|
+
const rel = path.relative(_rootDir, savedPath);
|
|
214
|
+
delete _depCache.depFileContents[rel];
|
|
215
|
+
} catch {}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
scanFile(uri);
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (msg.method === 'textDocument/didClose') {
|
|
224
|
+
const uri = msg.params?.textDocument?.uri;
|
|
225
|
+
if (uri) await publishDiagnostics(uri, []);
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
// Unknown method.
|
|
229
|
+
if (msg.id != null) {
|
|
230
|
+
return { id: msg.id, error: { code: -32601, message: `Method not found: ${msg.method}` } };
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function startLspServer() {
|
|
236
|
+
let buffer = Buffer.alloc(0);
|
|
237
|
+
let expected = -1;
|
|
238
|
+
process.stdin.on('data', async (chunk) => {
|
|
239
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
240
|
+
while (true) {
|
|
241
|
+
if (expected < 0) {
|
|
242
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
243
|
+
if (headerEnd < 0) break;
|
|
244
|
+
const headers = buffer.slice(0, headerEnd).toString('utf8');
|
|
245
|
+
const m = headers.match(/Content-Length:\s*(\d+)/i);
|
|
246
|
+
if (!m) {
|
|
247
|
+
process.stderr.write('agentic-security-lsp: missing Content-Length header\n');
|
|
248
|
+
buffer = buffer.slice(headerEnd + 4);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
expected = parseInt(m[1], 10);
|
|
252
|
+
buffer = buffer.slice(headerEnd + 4);
|
|
253
|
+
}
|
|
254
|
+
if (buffer.length < expected) break;
|
|
255
|
+
const body = buffer.slice(0, expected).toString('utf8');
|
|
256
|
+
buffer = buffer.slice(expected);
|
|
257
|
+
expected = -1;
|
|
258
|
+
let msg;
|
|
259
|
+
try { msg = JSON.parse(body); }
|
|
260
|
+
catch { process.stderr.write('agentic-security-lsp: malformed JSON\n'); continue; }
|
|
261
|
+
try {
|
|
262
|
+
const response = await handleMessage(msg);
|
|
263
|
+
if (response) await send(response);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
if (msg.id != null) await send({ id: msg.id, error: { code: -32603, message: e.message } });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
process.stdin.on('end', () => process.exit(0));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Allow direct invocation as a bin entry: `node lsp/server.js`.
|
|
273
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
274
|
+
startLspServer();
|
|
275
|
+
}
|