calllint 1.3.0 → 1.4.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/dist/index.js +318 -236
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -106,6 +106,10 @@ SCAN OPTIONS
|
|
|
106
106
|
--badge Emit a shields.io endpoint badge JSON (SAFE/REVIEW/UNKNOWN/BLOCK)
|
|
107
107
|
--receipt Generate a cryptographically-verifiable audit receipt (for CI/compliance)
|
|
108
108
|
--receipt-out <f> Receipt output path (default: calllint-receipt.json)
|
|
109
|
+
--evidence <file> Attach an external scanner report (e.g. SkillSpector JSON/SARIF)
|
|
110
|
+
as supporting evidence; shown side-by-side in a joint Trust
|
|
111
|
+
Packet. Never re-scored, never changes the CallLint verdict.
|
|
112
|
+
--evidence-format <fmt> Force evidence format (json|sarif); default auto-detect
|
|
109
113
|
--policy <file> Use a policy file (default: built-in defaults)
|
|
110
114
|
--stdin Read config JSON from stdin
|
|
111
115
|
--ci Exit non-zero per policy (BLOCK=30, UNKNOWN=20, REVIEW=10 if enabled)
|
|
@@ -130,6 +134,7 @@ EXAMPLES
|
|
|
130
134
|
calllint scan .cursor/mcp.json --markdown
|
|
131
135
|
calllint scan .cursor/mcp.json --badge > calllint-badge.json
|
|
132
136
|
calllint scan .cursor/mcp.json --receipt # Generate audit receipt
|
|
137
|
+
calllint scan .cursor/mcp.json --evidence skillspector-report.json # Joint Trust Packet
|
|
133
138
|
calllint receipt verify calllint-receipt.json # Verify receipt integrity
|
|
134
139
|
calllint action inspect payment.json
|
|
135
140
|
calllint inbox inspect gmail-reply.normalized.json
|
|
@@ -682,7 +687,8 @@ function resolveScanOptions(opts) {
|
|
|
682
687
|
now: opts?.now ?? 0,
|
|
683
688
|
generatedAt: opts?.generatedAt ?? "1970-01-01T00:00:00.000Z",
|
|
684
689
|
extraFindings: opts?.extraFindings ?? {},
|
|
685
|
-
surfaces: opts?.surfaces ?? []
|
|
690
|
+
surfaces: opts?.surfaces ?? [],
|
|
691
|
+
evidence: opts?.evidence ?? []
|
|
686
692
|
};
|
|
687
693
|
}
|
|
688
694
|
|
|
@@ -2670,7 +2676,7 @@ function enrichEvidencePositions(reports, positions) {
|
|
|
2670
2676
|
}
|
|
2671
2677
|
|
|
2672
2678
|
// ../../packages/core/src/scanConfig.ts
|
|
2673
|
-
function aggregate(configPath, reports, generatedAt) {
|
|
2679
|
+
function aggregate(configPath, reports, generatedAt, evidence) {
|
|
2674
2680
|
const counts = { SAFE: 0, REVIEW: 0, BLOCK: 0, UNKNOWN: 0 };
|
|
2675
2681
|
for (const r of reports) counts[r.verdict]++;
|
|
2676
2682
|
const verdict = reports.length === 0 ? "UNKNOWN" : mostSevereVerdict(reports.map((r) => r.verdict));
|
|
@@ -2683,18 +2689,23 @@ function aggregate(configPath, reports, generatedAt) {
|
|
|
2683
2689
|
counts,
|
|
2684
2690
|
reports,
|
|
2685
2691
|
diagnostics: [],
|
|
2686
|
-
generatedAt
|
|
2692
|
+
generatedAt,
|
|
2693
|
+
// Additive projection (ADR 0034). Attached only when the CLI imported evidence
|
|
2694
|
+
// via `scan --evidence`; omitted otherwise so default scan output is
|
|
2695
|
+
// byte-identical (the offline corpus never attaches evidence). Never re-scored:
|
|
2696
|
+
// the `verdict` above is unaffected by these findings.
|
|
2697
|
+
...evidence.length > 0 ? { evidence } : {}
|
|
2687
2698
|
};
|
|
2688
2699
|
}
|
|
2689
2700
|
function scanParsed(parsed, opts) {
|
|
2690
|
-
const { generatedAt, surfaces } = resolveScanOptions(opts);
|
|
2701
|
+
const { generatedAt, surfaces, evidence } = resolveScanOptions(opts);
|
|
2691
2702
|
const reports = parsed.servers.map(
|
|
2692
2703
|
(server) => scanServer({ server, targetKind: parsed.kind }, opts)
|
|
2693
2704
|
);
|
|
2694
2705
|
enrichEvidencePositions(reports, parsed.positions);
|
|
2695
2706
|
const surfaceReport = scanDocumentSurfaces(surfaces, parsed.configPath, generatedAt);
|
|
2696
2707
|
if (surfaceReport) reports.push(surfaceReport);
|
|
2697
|
-
return aggregate(parsed.configPath, reports, generatedAt);
|
|
2708
|
+
return aggregate(parsed.configPath, reports, generatedAt, evidence);
|
|
2698
2709
|
}
|
|
2699
2710
|
function scanConfigText(text, configPath, opts) {
|
|
2700
2711
|
return scanParsed(parseConfigText(text, configPath), opts);
|
|
@@ -4442,6 +4453,275 @@ function renderHtml(summary) {
|
|
|
4442
4453
|
</html>`;
|
|
4443
4454
|
}
|
|
4444
4455
|
|
|
4456
|
+
// ../../packages/report-renderer/src/renderTrustPacket.ts
|
|
4457
|
+
var COMPLETENESS_HINT = {
|
|
4458
|
+
complete: "complete",
|
|
4459
|
+
partial: "partial (incomplete \u2014 treat as inconclusive)",
|
|
4460
|
+
degraded: "degraded (not a pass)",
|
|
4461
|
+
failed: "failed (not a pass)"
|
|
4462
|
+
};
|
|
4463
|
+
function topProviderSeverity(ev) {
|
|
4464
|
+
const sevs = ev.findings.map(
|
|
4465
|
+
(f) => f && typeof f === "object" && "providerSeverity" in f ? String(f.providerSeverity) : void 0
|
|
4466
|
+
).filter((s) => Boolean(s));
|
|
4467
|
+
return sevs[0];
|
|
4468
|
+
}
|
|
4469
|
+
function whyTheyDiffer(ev, authorityVerdict) {
|
|
4470
|
+
if (ev.completeness === "failed" || ev.completeness === "degraded") {
|
|
4471
|
+
return "Why they differ: the content scan is " + COMPLETENESS_HINT[ev.completeness] + ", so it carries no weight here; CallLint's authority verdict stands on its own evidence.";
|
|
4472
|
+
}
|
|
4473
|
+
if (ev.findings.length === 0 && authorityVerdict !== "SAFE") {
|
|
4474
|
+
return "Why they differ: the content scan found nothing malicious, but CallLint judges the granted authority itself too broad \u2014 clean code can still request unsafe capabilities.";
|
|
4475
|
+
}
|
|
4476
|
+
return "Why they differ: the two tools answer different questions \u2014 content risk (the scanner) vs. whether the requested authority is acceptable (CallLint). Neither overrides the other.";
|
|
4477
|
+
}
|
|
4478
|
+
function renderTrustPacket(summary, toolVersion, style = DEFAULT_STYLE) {
|
|
4479
|
+
const evidence = summary.evidence;
|
|
4480
|
+
if (!evidence || evidence.length === 0) return "";
|
|
4481
|
+
const lines = ["", "Joint Trust Packet", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"];
|
|
4482
|
+
lines.push("Content scan");
|
|
4483
|
+
for (const ev of evidence) {
|
|
4484
|
+
const sev = topProviderSeverity(ev);
|
|
4485
|
+
const findingsLabel = ev.findings.length === 0 ? "no findings" : `${ev.findings.length} finding${ev.findings.length === 1 ? "" : "s"}` + (sev ? ` (top severity: ${sev})` : "");
|
|
4486
|
+
lines.push(
|
|
4487
|
+
` ${ev.provider} ${ev.providerVersion} scanMode: ${ev.scanMode} completeness: ${COMPLETENESS_HINT[ev.completeness]}`
|
|
4488
|
+
);
|
|
4489
|
+
lines.push(` ${findingsLabel}`);
|
|
4490
|
+
lines.push(` raw report digest: ${ev.rawReportDigest}`);
|
|
4491
|
+
for (const reason of ev.degradedReasons) {
|
|
4492
|
+
lines.push(` degraded: ${reason}`);
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
lines.push("Authority scan");
|
|
4496
|
+
lines.push(
|
|
4497
|
+
` CallLint ${toolVersion} ${verdictTag(summary.verdict, style)} (${summary.publicVerdictLabel})`
|
|
4498
|
+
);
|
|
4499
|
+
lines.push(whyTheyDiffer(evidence[0], summary.verdict));
|
|
4500
|
+
return lines.join("\n");
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4503
|
+
// ../../packages/evidence/src/types.ts
|
|
4504
|
+
var EVIDENCE_SCHEMA_VERSION = "calllint.evidence-provider.v0";
|
|
4505
|
+
|
|
4506
|
+
// ../../packages/evidence/src/providers/skillspector.ts
|
|
4507
|
+
function pinnedVersion(raw) {
|
|
4508
|
+
const tool = raw.tool;
|
|
4509
|
+
const commit = typeof raw.commit === "string" && raw.commit || tool && typeof tool.commit === "string" && tool.commit || "";
|
|
4510
|
+
if (commit) return `git:${commit}`;
|
|
4511
|
+
const version = typeof raw.version === "string" && raw.version || tool && typeof tool.version === "string" && tool.version || "";
|
|
4512
|
+
return version || void 0;
|
|
4513
|
+
}
|
|
4514
|
+
function parseSkillSpectorJson(parsed) {
|
|
4515
|
+
const raw = asObject(parsed, "SkillSpector JSON root");
|
|
4516
|
+
const degradedReasons = [];
|
|
4517
|
+
const usedLlm = raw.llm_used === true || raw.llmUsed === true;
|
|
4518
|
+
const scanMode = usedLlm ? "llm" : "static";
|
|
4519
|
+
const rawFindings = arrayOf(raw.findings) ?? arrayOf(raw.results) ?? [];
|
|
4520
|
+
const findings = rawFindings.map((f) => {
|
|
4521
|
+
const o = asObject(f, "SkillSpector finding");
|
|
4522
|
+
return {
|
|
4523
|
+
providerRuleId: String(o.rule_id ?? o.ruleId ?? o.id ?? "unknown"),
|
|
4524
|
+
providerSeverity: String(o.severity ?? o.level ?? "unknown"),
|
|
4525
|
+
message: typeof o.message === "string" ? o.message : void 0,
|
|
4526
|
+
locations: extractLocations(o)
|
|
4527
|
+
};
|
|
4528
|
+
});
|
|
4529
|
+
let completenessHint;
|
|
4530
|
+
const status = String(raw.status ?? raw.scan_status ?? "").toLowerCase();
|
|
4531
|
+
if (status && status !== "complete" && status !== "completed" && status !== "success") {
|
|
4532
|
+
degradedReasons.push(`SkillSpector reported status "${status}"`);
|
|
4533
|
+
completenessHint = status === "partial" ? "partial" : "degraded";
|
|
4534
|
+
}
|
|
4535
|
+
if (raw.partial === true) {
|
|
4536
|
+
degradedReasons.push("SkillSpector reported a partial scan");
|
|
4537
|
+
completenessHint = completenessHint ?? "partial";
|
|
4538
|
+
}
|
|
4539
|
+
if (raw.degraded === true) {
|
|
4540
|
+
degradedReasons.push("SkillSpector reported a degraded scan");
|
|
4541
|
+
completenessHint = "degraded";
|
|
4542
|
+
}
|
|
4543
|
+
const coverage = (arrayOf(raw.categories) ?? arrayOf(raw.coverage) ?? []).map((c) => String(c));
|
|
4544
|
+
return {
|
|
4545
|
+
provider: "skillspector",
|
|
4546
|
+
providerVersion: pinnedVersion(raw),
|
|
4547
|
+
scanMode,
|
|
4548
|
+
coverage,
|
|
4549
|
+
findings,
|
|
4550
|
+
degradedReasons,
|
|
4551
|
+
completenessHint
|
|
4552
|
+
};
|
|
4553
|
+
}
|
|
4554
|
+
function parseSkillSpectorSarif(parsed) {
|
|
4555
|
+
const raw = asObject(parsed, "SARIF root");
|
|
4556
|
+
const runs = arrayOf(raw.runs) ?? [];
|
|
4557
|
+
if (runs.length === 0) {
|
|
4558
|
+
return {
|
|
4559
|
+
provider: "skillspector",
|
|
4560
|
+
findings: [],
|
|
4561
|
+
degradedReasons: ["SARIF had no runs"]
|
|
4562
|
+
};
|
|
4563
|
+
}
|
|
4564
|
+
const degradedReasons = [];
|
|
4565
|
+
const findings = [];
|
|
4566
|
+
let providerVersion;
|
|
4567
|
+
const coverage = [];
|
|
4568
|
+
for (const runUnknown of runs) {
|
|
4569
|
+
const run2 = asObject(runUnknown, "SARIF run");
|
|
4570
|
+
const tool = asObject(run2.tool ?? {}, "SARIF tool");
|
|
4571
|
+
const driver = asObject(tool.driver ?? {}, "SARIF tool.driver");
|
|
4572
|
+
if (!providerVersion) {
|
|
4573
|
+
const v = typeof driver.semanticVersion === "string" && driver.semanticVersion || typeof driver.version === "string" && driver.version || "";
|
|
4574
|
+
if (v) providerVersion = v;
|
|
4575
|
+
}
|
|
4576
|
+
const results = arrayOf(run2.results) ?? [];
|
|
4577
|
+
for (const resUnknown of results) {
|
|
4578
|
+
const res = asObject(resUnknown, "SARIF result");
|
|
4579
|
+
findings.push({
|
|
4580
|
+
providerRuleId: String(res.ruleId ?? "unknown"),
|
|
4581
|
+
providerSeverity: String(res.level ?? "warning"),
|
|
4582
|
+
message: extractSarifMessage(res),
|
|
4583
|
+
locations: extractSarifLocations(res)
|
|
4584
|
+
});
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
degradedReasons.push("SARIF import: provider-specific fields may be lost vs JSON form");
|
|
4588
|
+
return {
|
|
4589
|
+
provider: "skillspector",
|
|
4590
|
+
providerVersion,
|
|
4591
|
+
scanMode: "static",
|
|
4592
|
+
coverage,
|
|
4593
|
+
findings,
|
|
4594
|
+
degradedReasons
|
|
4595
|
+
};
|
|
4596
|
+
}
|
|
4597
|
+
function asObject(v, what) {
|
|
4598
|
+
if (v && typeof v === "object" && !Array.isArray(v)) return v;
|
|
4599
|
+
throw new Error(`expected object for ${what}`);
|
|
4600
|
+
}
|
|
4601
|
+
function arrayOf(v) {
|
|
4602
|
+
return Array.isArray(v) ? v : void 0;
|
|
4603
|
+
}
|
|
4604
|
+
function extractLocations(o) {
|
|
4605
|
+
const loc = o.location ?? o.locations ?? o.path;
|
|
4606
|
+
if (typeof loc === "string") return [loc];
|
|
4607
|
+
if (Array.isArray(loc)) return loc.map((x) => String(x));
|
|
4608
|
+
return void 0;
|
|
4609
|
+
}
|
|
4610
|
+
function extractSarifMessage(res) {
|
|
4611
|
+
const msg = res.message;
|
|
4612
|
+
if (msg && typeof msg.text === "string") return msg.text;
|
|
4613
|
+
return void 0;
|
|
4614
|
+
}
|
|
4615
|
+
function extractSarifLocations(res) {
|
|
4616
|
+
const locs = arrayOf(res.locations);
|
|
4617
|
+
if (!locs) return void 0;
|
|
4618
|
+
const out = [];
|
|
4619
|
+
for (const l of locs) {
|
|
4620
|
+
const lo = l;
|
|
4621
|
+
const phys = lo?.physicalLocation;
|
|
4622
|
+
const art = phys?.artifactLocation;
|
|
4623
|
+
const uri = art && typeof art.uri === "string" ? art.uri : void 0;
|
|
4624
|
+
const region = phys?.region;
|
|
4625
|
+
const line = region && typeof region.startLine === "number" ? region.startLine : void 0;
|
|
4626
|
+
if (uri) out.push(line ? `${uri}:${line}` : uri);
|
|
4627
|
+
}
|
|
4628
|
+
return out.length > 0 ? out : void 0;
|
|
4629
|
+
}
|
|
4630
|
+
|
|
4631
|
+
// ../../packages/evidence/src/importEvidence.ts
|
|
4632
|
+
var ZERO_DIGEST = `sha256:${"0".repeat(64)}`;
|
|
4633
|
+
function detectProvider(raw, explicit) {
|
|
4634
|
+
if (explicit) return explicit;
|
|
4635
|
+
const r = raw;
|
|
4636
|
+
if (!r) return "unknown";
|
|
4637
|
+
const topTool = r.tool;
|
|
4638
|
+
const jsonName = typeof r.scanner === "string" && r.scanner || topTool && typeof topTool.name === "string" && topTool.name || "";
|
|
4639
|
+
let sarifName = "";
|
|
4640
|
+
const runs = r.runs;
|
|
4641
|
+
if (Array.isArray(runs) && runs.length > 0) {
|
|
4642
|
+
const run2 = runs[0];
|
|
4643
|
+
const tool = run2?.tool;
|
|
4644
|
+
const driver = tool?.driver;
|
|
4645
|
+
if (driver && typeof driver.name === "string") sarifName = driver.name;
|
|
4646
|
+
}
|
|
4647
|
+
if (/skillspector/i.test(String(jsonName)) || /skillspector/i.test(sarifName)) {
|
|
4648
|
+
return "skillspector";
|
|
4649
|
+
}
|
|
4650
|
+
return "unknown";
|
|
4651
|
+
}
|
|
4652
|
+
function importEvidence(rawText, opts = {}) {
|
|
4653
|
+
const format = opts.format ?? (looksLikeSarif(rawText) ? "sarif" : "json");
|
|
4654
|
+
const rawReportDigest = sha256(rawText);
|
|
4655
|
+
let parsed;
|
|
4656
|
+
try {
|
|
4657
|
+
parsed = JSON.parse(rawText);
|
|
4658
|
+
} catch {
|
|
4659
|
+
return failClosed(opts.provider ?? "unknown", rawReportDigest, [
|
|
4660
|
+
`report is not valid JSON (format=${format})`
|
|
4661
|
+
]);
|
|
4662
|
+
}
|
|
4663
|
+
const provider = detectProvider(parsed, opts.provider);
|
|
4664
|
+
let result;
|
|
4665
|
+
try {
|
|
4666
|
+
if (provider === "skillspector") {
|
|
4667
|
+
result = format === "sarif" ? parseSkillSpectorSarif(parsed) : parseSkillSpectorJson(parsed);
|
|
4668
|
+
} else {
|
|
4669
|
+
return failClosed(provider, rawReportDigest, [
|
|
4670
|
+
`no adapter for provider "${provider}"; evidence not interpreted`
|
|
4671
|
+
]);
|
|
4672
|
+
}
|
|
4673
|
+
} catch (err2) {
|
|
4674
|
+
return failClosed(provider, rawReportDigest, [
|
|
4675
|
+
`adapter error: ${err2.message}`
|
|
4676
|
+
]);
|
|
4677
|
+
}
|
|
4678
|
+
return finalizeEnvelope(result, rawReportDigest);
|
|
4679
|
+
}
|
|
4680
|
+
function failClosed(provider, rawReportDigest, reasons) {
|
|
4681
|
+
return {
|
|
4682
|
+
schema_version: EVIDENCE_SCHEMA_VERSION,
|
|
4683
|
+
provider: provider || "unknown",
|
|
4684
|
+
providerVersion: "unknown",
|
|
4685
|
+
artifactDigest: ZERO_DIGEST,
|
|
4686
|
+
scanMode: "static",
|
|
4687
|
+
coverage: [],
|
|
4688
|
+
completeness: "failed",
|
|
4689
|
+
findings: [],
|
|
4690
|
+
rawReportDigest,
|
|
4691
|
+
degradedReasons: reasons
|
|
4692
|
+
};
|
|
4693
|
+
}
|
|
4694
|
+
function finalizeEnvelope(r, rawReportDigest) {
|
|
4695
|
+
const degradedReasons = [...r.degradedReasons ?? []];
|
|
4696
|
+
const rank = { complete: 0, partial: 1, degraded: 2, failed: 3 };
|
|
4697
|
+
const rankToCompleteness = ["complete", "partial", "degraded", "failed"];
|
|
4698
|
+
let level = rank[r.completenessHint ?? (degradedReasons.length > 0 ? "degraded" : "complete")];
|
|
4699
|
+
let providerVersion = r.providerVersion?.trim() || "";
|
|
4700
|
+
if (!providerVersion) {
|
|
4701
|
+
providerVersion = "unknown";
|
|
4702
|
+
degradedReasons.push("provider version not pinned (no release/commit reported)");
|
|
4703
|
+
level = Math.max(level, rank.degraded);
|
|
4704
|
+
}
|
|
4705
|
+
const completeness = rankToCompleteness[level];
|
|
4706
|
+
return {
|
|
4707
|
+
schema_version: EVIDENCE_SCHEMA_VERSION,
|
|
4708
|
+
provider: r.provider,
|
|
4709
|
+
providerVersion,
|
|
4710
|
+
artifactDigest: r.artifactDigest ?? ZERO_DIGEST,
|
|
4711
|
+
scanMode: r.scanMode ?? "static",
|
|
4712
|
+
coverage: r.coverage ?? [],
|
|
4713
|
+
completeness,
|
|
4714
|
+
findings: r.findings,
|
|
4715
|
+
rawReportDigest,
|
|
4716
|
+
startedAt: r.startedAt,
|
|
4717
|
+
finishedAt: r.finishedAt,
|
|
4718
|
+
degradedReasons
|
|
4719
|
+
};
|
|
4720
|
+
}
|
|
4721
|
+
function looksLikeSarif(text) {
|
|
4722
|
+
return /"runs"\s*:/.test(text) && /sarif/i.test(text);
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4445
4725
|
// src/exitCode.ts
|
|
4446
4726
|
function exitCodeFor(summary, policy) {
|
|
4447
4727
|
const verdict = summary.verdict;
|
|
@@ -5030,6 +5310,15 @@ function scanCommand(args, deps) {
|
|
|
5030
5310
|
function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true) {
|
|
5031
5311
|
const surfaceDir = flagStr(args.flags, "surface-dir");
|
|
5032
5312
|
const surfaces = surfaceDir ? readDocumentSurfaces(resolve3(deps.cwd, surfaceDir)) : void 0;
|
|
5313
|
+
const evidenceFile = flagStr(args.flags, "evidence");
|
|
5314
|
+
let evidence;
|
|
5315
|
+
if (evidenceFile) {
|
|
5316
|
+
const loaded = loadEvidence(evidenceFile, args, deps);
|
|
5317
|
+
if ("error" in loaded) {
|
|
5318
|
+
return { stdout: "", stderr: loaded.error, exitCode: loaded.exitCode };
|
|
5319
|
+
}
|
|
5320
|
+
evidence = [loaded];
|
|
5321
|
+
}
|
|
5033
5322
|
let summary;
|
|
5034
5323
|
try {
|
|
5035
5324
|
summary = scanConfigText(text, configPath, {
|
|
@@ -5037,7 +5326,8 @@ function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true
|
|
|
5037
5326
|
now: deps.now,
|
|
5038
5327
|
generatedAt: deps.generatedAt,
|
|
5039
5328
|
extraFindings: deps.online?.extraFindings,
|
|
5040
|
-
surfaces
|
|
5329
|
+
surfaces,
|
|
5330
|
+
evidence
|
|
5041
5331
|
});
|
|
5042
5332
|
} catch (err2) {
|
|
5043
5333
|
if (err2 instanceof ConfigParseError) {
|
|
@@ -5055,7 +5345,7 @@ function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true
|
|
|
5055
5345
|
} catch {
|
|
5056
5346
|
}
|
|
5057
5347
|
}
|
|
5058
|
-
const stdout = renderSummary(summary, args);
|
|
5348
|
+
const stdout = renderSummary(summary, args, deps.toolVersion);
|
|
5059
5349
|
if (allowReceipt && flagBool(args.flags, "receipt")) {
|
|
5060
5350
|
const err2 = writeReceiptFile(summary, text, configPath, policy, args, deps);
|
|
5061
5351
|
if (err2) return { stdout: "", stderr: err2, exitCode: EXIT.ERROR };
|
|
@@ -5088,15 +5378,31 @@ function writeReceiptFile(summary, text, configPath, policy, args, deps) {
|
|
|
5088
5378
|
}
|
|
5089
5379
|
return void 0;
|
|
5090
5380
|
}
|
|
5091
|
-
function
|
|
5381
|
+
function loadEvidence(file, args, deps) {
|
|
5382
|
+
let rawText;
|
|
5383
|
+
try {
|
|
5384
|
+
rawText = readFileSync12(resolve3(deps.cwd, file), "utf8");
|
|
5385
|
+
} catch (err2) {
|
|
5386
|
+
const e = err2;
|
|
5387
|
+
return {
|
|
5388
|
+
error: e.code === "ENOENT" ? `Evidence file not found: ${file}` : e.message,
|
|
5389
|
+
exitCode: EXIT.USAGE
|
|
5390
|
+
};
|
|
5391
|
+
}
|
|
5392
|
+
const fmt = flagStr(args.flags, "evidence-format");
|
|
5393
|
+
const format = fmt === "sarif" ? "sarif" : fmt === "json" ? "json" : void 0;
|
|
5394
|
+
return importEvidence(rawText, { format });
|
|
5395
|
+
}
|
|
5396
|
+
function renderSummary(summary, args, toolVersion) {
|
|
5092
5397
|
const style = flagBool(args.flags, "no-emoji") ? NO_EMOJI_STYLE : DEFAULT_STYLE;
|
|
5093
5398
|
if (flagBool(args.flags, "json")) return renderJson(summary);
|
|
5094
5399
|
if (flagBool(args.flags, "sarif")) return renderSarif(summary);
|
|
5095
5400
|
if (flagBool(args.flags, "markdown")) return renderMarkdown(summary);
|
|
5096
5401
|
if (flagBool(args.flags, "badge")) return renderBadge(summary);
|
|
5097
5402
|
if (flagBool(args.flags, "html")) return renderHtml(summary);
|
|
5098
|
-
|
|
5099
|
-
|
|
5403
|
+
const base = flagBool(args.flags, "compact") ? renderCompact(summary, style) : renderTerminal(summary, style);
|
|
5404
|
+
const packet = renderTrustPacket(summary, toolVersion ?? "0.0.0-dev", style);
|
|
5405
|
+
return packet ? base + "\n" + packet : base;
|
|
5100
5406
|
}
|
|
5101
5407
|
function scanChangedCommand(args, deps) {
|
|
5102
5408
|
const policyPath = flagStr(args.flags, "policy");
|
|
@@ -7502,230 +7808,6 @@ function formatAgentType(agentType) {
|
|
|
7502
7808
|
// src/commands/evidence.ts
|
|
7503
7809
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
7504
7810
|
import { resolve as resolve8 } from "node:path";
|
|
7505
|
-
|
|
7506
|
-
// ../../packages/evidence/src/types.ts
|
|
7507
|
-
var EVIDENCE_SCHEMA_VERSION = "calllint.evidence-provider.v0";
|
|
7508
|
-
|
|
7509
|
-
// ../../packages/evidence/src/providers/skillspector.ts
|
|
7510
|
-
function pinnedVersion(raw) {
|
|
7511
|
-
const tool = raw.tool;
|
|
7512
|
-
const commit = typeof raw.commit === "string" && raw.commit || tool && typeof tool.commit === "string" && tool.commit || "";
|
|
7513
|
-
if (commit) return `git:${commit}`;
|
|
7514
|
-
const version = typeof raw.version === "string" && raw.version || tool && typeof tool.version === "string" && tool.version || "";
|
|
7515
|
-
return version || void 0;
|
|
7516
|
-
}
|
|
7517
|
-
function parseSkillSpectorJson(parsed) {
|
|
7518
|
-
const raw = asObject(parsed, "SkillSpector JSON root");
|
|
7519
|
-
const degradedReasons = [];
|
|
7520
|
-
const usedLlm = raw.llm_used === true || raw.llmUsed === true;
|
|
7521
|
-
const scanMode = usedLlm ? "llm" : "static";
|
|
7522
|
-
const rawFindings = arrayOf(raw.findings) ?? arrayOf(raw.results) ?? [];
|
|
7523
|
-
const findings = rawFindings.map((f) => {
|
|
7524
|
-
const o = asObject(f, "SkillSpector finding");
|
|
7525
|
-
return {
|
|
7526
|
-
providerRuleId: String(o.rule_id ?? o.ruleId ?? o.id ?? "unknown"),
|
|
7527
|
-
providerSeverity: String(o.severity ?? o.level ?? "unknown"),
|
|
7528
|
-
message: typeof o.message === "string" ? o.message : void 0,
|
|
7529
|
-
locations: extractLocations(o)
|
|
7530
|
-
};
|
|
7531
|
-
});
|
|
7532
|
-
let completenessHint;
|
|
7533
|
-
const status = String(raw.status ?? raw.scan_status ?? "").toLowerCase();
|
|
7534
|
-
if (status && status !== "complete" && status !== "completed" && status !== "success") {
|
|
7535
|
-
degradedReasons.push(`SkillSpector reported status "${status}"`);
|
|
7536
|
-
completenessHint = status === "partial" ? "partial" : "degraded";
|
|
7537
|
-
}
|
|
7538
|
-
if (raw.partial === true) {
|
|
7539
|
-
degradedReasons.push("SkillSpector reported a partial scan");
|
|
7540
|
-
completenessHint = completenessHint ?? "partial";
|
|
7541
|
-
}
|
|
7542
|
-
if (raw.degraded === true) {
|
|
7543
|
-
degradedReasons.push("SkillSpector reported a degraded scan");
|
|
7544
|
-
completenessHint = "degraded";
|
|
7545
|
-
}
|
|
7546
|
-
const coverage = (arrayOf(raw.categories) ?? arrayOf(raw.coverage) ?? []).map((c) => String(c));
|
|
7547
|
-
return {
|
|
7548
|
-
provider: "skillspector",
|
|
7549
|
-
providerVersion: pinnedVersion(raw),
|
|
7550
|
-
scanMode,
|
|
7551
|
-
coverage,
|
|
7552
|
-
findings,
|
|
7553
|
-
degradedReasons,
|
|
7554
|
-
completenessHint
|
|
7555
|
-
};
|
|
7556
|
-
}
|
|
7557
|
-
function parseSkillSpectorSarif(parsed) {
|
|
7558
|
-
const raw = asObject(parsed, "SARIF root");
|
|
7559
|
-
const runs = arrayOf(raw.runs) ?? [];
|
|
7560
|
-
if (runs.length === 0) {
|
|
7561
|
-
return {
|
|
7562
|
-
provider: "skillspector",
|
|
7563
|
-
findings: [],
|
|
7564
|
-
degradedReasons: ["SARIF had no runs"]
|
|
7565
|
-
};
|
|
7566
|
-
}
|
|
7567
|
-
const degradedReasons = [];
|
|
7568
|
-
const findings = [];
|
|
7569
|
-
let providerVersion;
|
|
7570
|
-
const coverage = [];
|
|
7571
|
-
for (const runUnknown of runs) {
|
|
7572
|
-
const run2 = asObject(runUnknown, "SARIF run");
|
|
7573
|
-
const tool = asObject(run2.tool ?? {}, "SARIF tool");
|
|
7574
|
-
const driver = asObject(tool.driver ?? {}, "SARIF tool.driver");
|
|
7575
|
-
if (!providerVersion) {
|
|
7576
|
-
const v = typeof driver.semanticVersion === "string" && driver.semanticVersion || typeof driver.version === "string" && driver.version || "";
|
|
7577
|
-
if (v) providerVersion = v;
|
|
7578
|
-
}
|
|
7579
|
-
const results = arrayOf(run2.results) ?? [];
|
|
7580
|
-
for (const resUnknown of results) {
|
|
7581
|
-
const res = asObject(resUnknown, "SARIF result");
|
|
7582
|
-
findings.push({
|
|
7583
|
-
providerRuleId: String(res.ruleId ?? "unknown"),
|
|
7584
|
-
providerSeverity: String(res.level ?? "warning"),
|
|
7585
|
-
message: extractSarifMessage(res),
|
|
7586
|
-
locations: extractSarifLocations(res)
|
|
7587
|
-
});
|
|
7588
|
-
}
|
|
7589
|
-
}
|
|
7590
|
-
degradedReasons.push("SARIF import: provider-specific fields may be lost vs JSON form");
|
|
7591
|
-
return {
|
|
7592
|
-
provider: "skillspector",
|
|
7593
|
-
providerVersion,
|
|
7594
|
-
scanMode: "static",
|
|
7595
|
-
coverage,
|
|
7596
|
-
findings,
|
|
7597
|
-
degradedReasons
|
|
7598
|
-
};
|
|
7599
|
-
}
|
|
7600
|
-
function asObject(v, what) {
|
|
7601
|
-
if (v && typeof v === "object" && !Array.isArray(v)) return v;
|
|
7602
|
-
throw new Error(`expected object for ${what}`);
|
|
7603
|
-
}
|
|
7604
|
-
function arrayOf(v) {
|
|
7605
|
-
return Array.isArray(v) ? v : void 0;
|
|
7606
|
-
}
|
|
7607
|
-
function extractLocations(o) {
|
|
7608
|
-
const loc = o.location ?? o.locations ?? o.path;
|
|
7609
|
-
if (typeof loc === "string") return [loc];
|
|
7610
|
-
if (Array.isArray(loc)) return loc.map((x) => String(x));
|
|
7611
|
-
return void 0;
|
|
7612
|
-
}
|
|
7613
|
-
function extractSarifMessage(res) {
|
|
7614
|
-
const msg = res.message;
|
|
7615
|
-
if (msg && typeof msg.text === "string") return msg.text;
|
|
7616
|
-
return void 0;
|
|
7617
|
-
}
|
|
7618
|
-
function extractSarifLocations(res) {
|
|
7619
|
-
const locs = arrayOf(res.locations);
|
|
7620
|
-
if (!locs) return void 0;
|
|
7621
|
-
const out = [];
|
|
7622
|
-
for (const l of locs) {
|
|
7623
|
-
const lo = l;
|
|
7624
|
-
const phys = lo?.physicalLocation;
|
|
7625
|
-
const art = phys?.artifactLocation;
|
|
7626
|
-
const uri = art && typeof art.uri === "string" ? art.uri : void 0;
|
|
7627
|
-
const region = phys?.region;
|
|
7628
|
-
const line = region && typeof region.startLine === "number" ? region.startLine : void 0;
|
|
7629
|
-
if (uri) out.push(line ? `${uri}:${line}` : uri);
|
|
7630
|
-
}
|
|
7631
|
-
return out.length > 0 ? out : void 0;
|
|
7632
|
-
}
|
|
7633
|
-
|
|
7634
|
-
// ../../packages/evidence/src/importEvidence.ts
|
|
7635
|
-
var ZERO_DIGEST = `sha256:${"0".repeat(64)}`;
|
|
7636
|
-
function detectProvider(raw, explicit) {
|
|
7637
|
-
if (explicit) return explicit;
|
|
7638
|
-
const r = raw;
|
|
7639
|
-
if (!r) return "unknown";
|
|
7640
|
-
const topTool = r.tool;
|
|
7641
|
-
const jsonName = typeof r.scanner === "string" && r.scanner || topTool && typeof topTool.name === "string" && topTool.name || "";
|
|
7642
|
-
let sarifName = "";
|
|
7643
|
-
const runs = r.runs;
|
|
7644
|
-
if (Array.isArray(runs) && runs.length > 0) {
|
|
7645
|
-
const run2 = runs[0];
|
|
7646
|
-
const tool = run2?.tool;
|
|
7647
|
-
const driver = tool?.driver;
|
|
7648
|
-
if (driver && typeof driver.name === "string") sarifName = driver.name;
|
|
7649
|
-
}
|
|
7650
|
-
if (/skillspector/i.test(String(jsonName)) || /skillspector/i.test(sarifName)) {
|
|
7651
|
-
return "skillspector";
|
|
7652
|
-
}
|
|
7653
|
-
return "unknown";
|
|
7654
|
-
}
|
|
7655
|
-
function importEvidence(rawText, opts = {}) {
|
|
7656
|
-
const format = opts.format ?? (looksLikeSarif(rawText) ? "sarif" : "json");
|
|
7657
|
-
const rawReportDigest = sha256(rawText);
|
|
7658
|
-
let parsed;
|
|
7659
|
-
try {
|
|
7660
|
-
parsed = JSON.parse(rawText);
|
|
7661
|
-
} catch {
|
|
7662
|
-
return failClosed(opts.provider ?? "unknown", rawReportDigest, [
|
|
7663
|
-
`report is not valid JSON (format=${format})`
|
|
7664
|
-
]);
|
|
7665
|
-
}
|
|
7666
|
-
const provider = detectProvider(parsed, opts.provider);
|
|
7667
|
-
let result;
|
|
7668
|
-
try {
|
|
7669
|
-
if (provider === "skillspector") {
|
|
7670
|
-
result = format === "sarif" ? parseSkillSpectorSarif(parsed) : parseSkillSpectorJson(parsed);
|
|
7671
|
-
} else {
|
|
7672
|
-
return failClosed(provider, rawReportDigest, [
|
|
7673
|
-
`no adapter for provider "${provider}"; evidence not interpreted`
|
|
7674
|
-
]);
|
|
7675
|
-
}
|
|
7676
|
-
} catch (err2) {
|
|
7677
|
-
return failClosed(provider, rawReportDigest, [
|
|
7678
|
-
`adapter error: ${err2.message}`
|
|
7679
|
-
]);
|
|
7680
|
-
}
|
|
7681
|
-
return finalizeEnvelope(result, rawReportDigest);
|
|
7682
|
-
}
|
|
7683
|
-
function failClosed(provider, rawReportDigest, reasons) {
|
|
7684
|
-
return {
|
|
7685
|
-
schema_version: EVIDENCE_SCHEMA_VERSION,
|
|
7686
|
-
provider: provider || "unknown",
|
|
7687
|
-
providerVersion: "unknown",
|
|
7688
|
-
artifactDigest: ZERO_DIGEST,
|
|
7689
|
-
scanMode: "static",
|
|
7690
|
-
coverage: [],
|
|
7691
|
-
completeness: "failed",
|
|
7692
|
-
findings: [],
|
|
7693
|
-
rawReportDigest,
|
|
7694
|
-
degradedReasons: reasons
|
|
7695
|
-
};
|
|
7696
|
-
}
|
|
7697
|
-
function finalizeEnvelope(r, rawReportDigest) {
|
|
7698
|
-
const degradedReasons = [...r.degradedReasons ?? []];
|
|
7699
|
-
const rank = { complete: 0, partial: 1, degraded: 2, failed: 3 };
|
|
7700
|
-
const rankToCompleteness = ["complete", "partial", "degraded", "failed"];
|
|
7701
|
-
let level = rank[r.completenessHint ?? (degradedReasons.length > 0 ? "degraded" : "complete")];
|
|
7702
|
-
let providerVersion = r.providerVersion?.trim() || "";
|
|
7703
|
-
if (!providerVersion) {
|
|
7704
|
-
providerVersion = "unknown";
|
|
7705
|
-
degradedReasons.push("provider version not pinned (no release/commit reported)");
|
|
7706
|
-
level = Math.max(level, rank.degraded);
|
|
7707
|
-
}
|
|
7708
|
-
const completeness = rankToCompleteness[level];
|
|
7709
|
-
return {
|
|
7710
|
-
schema_version: EVIDENCE_SCHEMA_VERSION,
|
|
7711
|
-
provider: r.provider,
|
|
7712
|
-
providerVersion,
|
|
7713
|
-
artifactDigest: r.artifactDigest ?? ZERO_DIGEST,
|
|
7714
|
-
scanMode: r.scanMode ?? "static",
|
|
7715
|
-
coverage: r.coverage ?? [],
|
|
7716
|
-
completeness,
|
|
7717
|
-
findings: r.findings,
|
|
7718
|
-
rawReportDigest,
|
|
7719
|
-
startedAt: r.startedAt,
|
|
7720
|
-
finishedAt: r.finishedAt,
|
|
7721
|
-
degradedReasons
|
|
7722
|
-
};
|
|
7723
|
-
}
|
|
7724
|
-
function looksLikeSarif(text) {
|
|
7725
|
-
return /"runs"\s*:/.test(text) && /sarif/i.test(text);
|
|
7726
|
-
}
|
|
7727
|
-
|
|
7728
|
-
// src/commands/evidence.ts
|
|
7729
7811
|
function evidenceCommand(args, deps) {
|
|
7730
7812
|
const subcommand = args.positionals[0];
|
|
7731
7813
|
if (!subcommand || subcommand === "help") {
|
|
@@ -8681,7 +8763,7 @@ function writePlanFile(plan, deps) {
|
|
|
8681
8763
|
writeFileSync10(file, JSON.stringify(plan, null, 2) + "\n", "utf8");
|
|
8682
8764
|
return file;
|
|
8683
8765
|
}
|
|
8684
|
-
function
|
|
8766
|
+
function loadEvidence2(file, deps, format, provider) {
|
|
8685
8767
|
let rawText;
|
|
8686
8768
|
try {
|
|
8687
8769
|
rawText = readFileSync18(resolvePath4(deps.cwd, file), "utf8");
|
|
@@ -8720,7 +8802,7 @@ function trustPrepare(args, deps) {
|
|
|
8720
8802
|
const providerFlag = typeof args.flags["provider"] === "string" ? args.flags["provider"] : void 0;
|
|
8721
8803
|
let evidence;
|
|
8722
8804
|
if (evidenceFile) {
|
|
8723
|
-
const imported =
|
|
8805
|
+
const imported = loadEvidence2(evidenceFile, deps, format, providerFlag);
|
|
8724
8806
|
if ("error" in imported) {
|
|
8725
8807
|
return { stdout: "", stderr: `Error: ${imported.error}`, exitCode: imported.exitCode };
|
|
8726
8808
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "calllint",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Evidence-backed security verdicts for MCP servers and agent tools. Lint agent tool-call risk before tools run — SAFE / REVIEW / BLOCK / UNKNOWN, with evidence. Never executes the server it judges.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|