calllint 1.7.1 → 1.7.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.
- package/README.md +3 -0
- package/dist/index.js +241 -3
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -64,6 +64,9 @@ will do, it says so and never silently upgrades to `SAFE`.
|
|
|
64
64
|
- Self-contained HTML report: `calllint scan <config> --html > report.html`
|
|
65
65
|
- Drift / rug-pull detection: `calllint baseline <config>` then `calllint verify <config> --ci`
|
|
66
66
|
- Policy-as-code: `calllint policy init`
|
|
67
|
+
- Continuous Guard: `calllint guard` re-decides the approved authority surface (silent when unchanged); `calllint guard install --host <git|git-pre-push|github|claude-code|copilot|gemini|vscode>` adds a session-start / commit / CI hook that only shells out to `calllint guard` — never a per-call blocker
|
|
68
|
+
- Install the preflight into your hosts: `calllint integrate` prints a reversible install plan (writes nothing); `--apply --approve <digest>` is the only writer and reuses the audited atomic-write-and-rollback engine
|
|
69
|
+
- Claude Code plugin: a `PreToolUse` hook that *recommends* scanning before an agent-tool config edit — advisory, non-blocking, always exits 0, runs no scan itself
|
|
67
70
|
|
|
68
71
|
CallLint is a heuristic, evidence-backed pre-flight check, **not a proof of
|
|
69
72
|
safety**. `No blockers observed` ≠ guaranteed safe. Full docs, security model,
|
package/dist/index.js
CHANGED
|
@@ -5510,7 +5510,7 @@ function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true
|
|
|
5510
5510
|
if (err2) return { stdout: "", stderr: err2, exitCode: EXIT.ERROR };
|
|
5511
5511
|
}
|
|
5512
5512
|
const exitCode = flagBool(args.flags, "ci") ? exitCodeFor(summary, policy) : EXIT.OK;
|
|
5513
|
-
return { stdout, exitCode };
|
|
5513
|
+
return { stdout, exitCode, telemetry: { verdict: summary.verdict } };
|
|
5514
5514
|
}
|
|
5515
5515
|
function writeReceiptFile(summary, text, configPath, policy, args, deps) {
|
|
5516
5516
|
const toolVersion = deps.toolVersion ?? "0.0.0-dev";
|
|
@@ -8985,11 +8985,63 @@ function windsurfServerEntry(server) {
|
|
|
8985
8985
|
return entry;
|
|
8986
8986
|
}
|
|
8987
8987
|
|
|
8988
|
+
// ../../packages/install-planner/src/adapters/claudeDesktop.ts
|
|
8989
|
+
var CLAUDE_DESKTOP_HOST_ID = "claude-desktop";
|
|
8990
|
+
var CLAUDE_DESKTOP_TIER = "A";
|
|
8991
|
+
var claudeDesktopAdapter = {
|
|
8992
|
+
id: CLAUDE_DESKTOP_HOST_ID,
|
|
8993
|
+
tier: CLAUDE_DESKTOP_TIER,
|
|
8994
|
+
createPlan(ctx, upstream) {
|
|
8995
|
+
return buildInstallPlan({ ...ctx, host: CLAUDE_DESKTOP_HOST_ID, tier: CLAUDE_DESKTOP_TIER }, upstream);
|
|
8996
|
+
},
|
|
8997
|
+
validatePlan(plan) {
|
|
8998
|
+
return validatePlan(plan);
|
|
8999
|
+
},
|
|
9000
|
+
applyPlan(plan, ctx) {
|
|
9001
|
+
return applyPlan({
|
|
9002
|
+
plan,
|
|
9003
|
+
approvalDigest: ctx.approvalDigest,
|
|
9004
|
+
configPath: ctx.configPath,
|
|
9005
|
+
backupPath: ctx.backupPath,
|
|
9006
|
+
lockPath: ctx.lockPath,
|
|
9007
|
+
fs: ctx.fs,
|
|
9008
|
+
now: ctx.now
|
|
9009
|
+
});
|
|
9010
|
+
}
|
|
9011
|
+
};
|
|
9012
|
+
|
|
9013
|
+
// ../../packages/install-planner/src/adapters/vscode.ts
|
|
9014
|
+
var VSCODE_HOST_ID = "vscode";
|
|
9015
|
+
var VSCODE_TIER = "A";
|
|
9016
|
+
var vscodeAdapter = {
|
|
9017
|
+
id: VSCODE_HOST_ID,
|
|
9018
|
+
tier: VSCODE_TIER,
|
|
9019
|
+
createPlan(ctx, upstream) {
|
|
9020
|
+
return buildInstallPlan({ ...ctx, host: VSCODE_HOST_ID, tier: VSCODE_TIER }, upstream);
|
|
9021
|
+
},
|
|
9022
|
+
validatePlan(plan) {
|
|
9023
|
+
return validatePlan(plan);
|
|
9024
|
+
},
|
|
9025
|
+
applyPlan(plan, ctx) {
|
|
9026
|
+
return applyPlan({
|
|
9027
|
+
plan,
|
|
9028
|
+
approvalDigest: ctx.approvalDigest,
|
|
9029
|
+
configPath: ctx.configPath,
|
|
9030
|
+
backupPath: ctx.backupPath,
|
|
9031
|
+
lockPath: ctx.lockPath,
|
|
9032
|
+
fs: ctx.fs,
|
|
9033
|
+
now: ctx.now
|
|
9034
|
+
});
|
|
9035
|
+
}
|
|
9036
|
+
};
|
|
9037
|
+
|
|
8988
9038
|
// ../../packages/install-planner/src/index.ts
|
|
8989
9039
|
var HOST_ADAPTERS = {
|
|
8990
9040
|
[claudeCodeAdapter.id]: claudeCodeAdapter,
|
|
8991
9041
|
[cursorAdapter.id]: cursorAdapter,
|
|
8992
|
-
[windsurfAdapter.id]: windsurfAdapter
|
|
9042
|
+
[windsurfAdapter.id]: windsurfAdapter,
|
|
9043
|
+
[claudeDesktopAdapter.id]: claudeDesktopAdapter,
|
|
9044
|
+
[vscodeAdapter.id]: vscodeAdapter
|
|
8993
9045
|
};
|
|
8994
9046
|
function getHostAdapter(host) {
|
|
8995
9047
|
return HOST_ADAPTERS[host] ?? null;
|
|
@@ -10495,8 +10547,192 @@ function usage(msg) {
|
|
|
10495
10547
|
return { stdout: "", stderr: msg, exitCode: EXIT.USAGE };
|
|
10496
10548
|
}
|
|
10497
10549
|
|
|
10550
|
+
// ../../packages/telemetry-contract/src/events.ts
|
|
10551
|
+
var ALLOWED_EVENTS = [
|
|
10552
|
+
"install_completed",
|
|
10553
|
+
"preflight_completed",
|
|
10554
|
+
"decision_safe",
|
|
10555
|
+
"decision_review",
|
|
10556
|
+
"decision_block",
|
|
10557
|
+
"decision_unknown",
|
|
10558
|
+
"approval_created",
|
|
10559
|
+
"apply_completed",
|
|
10560
|
+
"verify_completed",
|
|
10561
|
+
"rollback_completed",
|
|
10562
|
+
"guard_drift_detected",
|
|
10563
|
+
"trust_page_viewed",
|
|
10564
|
+
"trust_page_to_install",
|
|
10565
|
+
"partner_api_called",
|
|
10566
|
+
"badge_rendered"
|
|
10567
|
+
];
|
|
10568
|
+
var FORBIDDEN_FIELDS = [
|
|
10569
|
+
"rawConfig",
|
|
10570
|
+
"command",
|
|
10571
|
+
"environmentValue",
|
|
10572
|
+
"secret",
|
|
10573
|
+
"fileContents",
|
|
10574
|
+
"privateRepository",
|
|
10575
|
+
"userPrompt",
|
|
10576
|
+
"findingEvidenceText"
|
|
10577
|
+
];
|
|
10578
|
+
var SOURCES = ["cli", "ci", "server", "install"];
|
|
10579
|
+
var RESULTS2 = ["SAFE", "REVIEW", "BLOCK", "UNKNOWN"];
|
|
10580
|
+
var EVENT_VERSION = "1.0.0";
|
|
10581
|
+
|
|
10582
|
+
// ../../packages/telemetry-contract/src/tiers.ts
|
|
10583
|
+
var TIER_POLICY = {
|
|
10584
|
+
server: {
|
|
10585
|
+
source: "server",
|
|
10586
|
+
defaultEnabled: true,
|
|
10587
|
+
requiresNotice: false,
|
|
10588
|
+
rationale: "Own web/API property; no user machine involved (funnel top)."
|
|
10589
|
+
},
|
|
10590
|
+
install: {
|
|
10591
|
+
source: "install",
|
|
10592
|
+
defaultEnabled: true,
|
|
10593
|
+
requiresNotice: false,
|
|
10594
|
+
rationale: "Attributed at the click/redirect surface, not on the machine."
|
|
10595
|
+
},
|
|
10596
|
+
ci: {
|
|
10597
|
+
source: "ci",
|
|
10598
|
+
defaultEnabled: true,
|
|
10599
|
+
requiresNotice: true,
|
|
10600
|
+
rationale: "The installing org is the controller; standard for CI tooling; disableable."
|
|
10601
|
+
},
|
|
10602
|
+
cli: {
|
|
10603
|
+
source: "cli",
|
|
10604
|
+
defaultEnabled: false,
|
|
10605
|
+
requiresNotice: true,
|
|
10606
|
+
rationale: "Personal machine \u2192 consent-first (opt-in, default off, first-run consent)."
|
|
10607
|
+
}
|
|
10608
|
+
};
|
|
10609
|
+
function isEnabledByDefault(source) {
|
|
10610
|
+
return TIER_POLICY[source].defaultEnabled;
|
|
10611
|
+
}
|
|
10612
|
+
|
|
10613
|
+
// ../../packages/telemetry-contract/src/sanitize.ts
|
|
10614
|
+
function bucketDuration(ms) {
|
|
10615
|
+
if (ms == null || !Number.isFinite(ms) || ms < 0) return void 0;
|
|
10616
|
+
if (ms < 100) return "<100ms";
|
|
10617
|
+
if (ms < 500) return "100-500ms";
|
|
10618
|
+
if (ms < 2e3) return "500-2000ms";
|
|
10619
|
+
return ">2000ms";
|
|
10620
|
+
}
|
|
10621
|
+
var isNonEmptyString = (v) => typeof v === "string" && v.length > 0;
|
|
10622
|
+
function sanitizeEvent(input) {
|
|
10623
|
+
for (const f of FORBIDDEN_FIELDS) {
|
|
10624
|
+
if (f in input) {
|
|
10625
|
+
throw new Error(`telemetry: forbidden field "${f}" present on event \u2014 refusing to sanitize`);
|
|
10626
|
+
}
|
|
10627
|
+
}
|
|
10628
|
+
if (!ALLOWED_EVENTS.includes(input.eventName)) {
|
|
10629
|
+
throw new Error(`telemetry: unknown eventName "${input.eventName}"`);
|
|
10630
|
+
}
|
|
10631
|
+
if (!SOURCES.includes(input.source)) {
|
|
10632
|
+
throw new Error(`telemetry: unknown source "${input.source}"`);
|
|
10633
|
+
}
|
|
10634
|
+
if (input.result != null && !RESULTS2.includes(input.result)) {
|
|
10635
|
+
throw new Error(`telemetry: unknown result "${input.result}"`);
|
|
10636
|
+
}
|
|
10637
|
+
const out = {
|
|
10638
|
+
eventVersion: EVENT_VERSION,
|
|
10639
|
+
eventName: input.eventName,
|
|
10640
|
+
timestamp: isNonEmptyString(input.timestamp) ? input.timestamp : "",
|
|
10641
|
+
source: input.source
|
|
10642
|
+
};
|
|
10643
|
+
if (isNonEmptyString(input.hostFamily)) out.hostFamily = input.hostFamily;
|
|
10644
|
+
if (input.result != null) out.result = input.result;
|
|
10645
|
+
const bucket = bucketDuration(input.durationMs);
|
|
10646
|
+
if (bucket) out.durationBucket = bucket;
|
|
10647
|
+
if (isNonEmptyString(input.inputKind)) out.inputKind = input.inputKind;
|
|
10648
|
+
if (isNonEmptyString(input.anonymousInstallationId)) {
|
|
10649
|
+
out.anonymousInstallationId = input.anonymousInstallationId;
|
|
10650
|
+
}
|
|
10651
|
+
if (isNonEmptyString(input.productVersion)) out.productVersion = input.productVersion;
|
|
10652
|
+
return out;
|
|
10653
|
+
}
|
|
10654
|
+
|
|
10655
|
+
// ../../packages/telemetry-emit/src/gate.ts
|
|
10656
|
+
var DISABLED_VALUES = /* @__PURE__ */ new Set(["0", "false", "off", "no"]);
|
|
10657
|
+
function isTelemetryDisabledByEnv(env) {
|
|
10658
|
+
const v = env["CALLLINT_TELEMETRY"];
|
|
10659
|
+
return v != null && DISABLED_VALUES.has(v.trim().toLowerCase());
|
|
10660
|
+
}
|
|
10661
|
+
function shouldEmit(source, state = {}) {
|
|
10662
|
+
const env = state.env ?? {};
|
|
10663
|
+
if (isTelemetryDisabledByEnv(env)) return false;
|
|
10664
|
+
if (source === "cli") return state.consented === true;
|
|
10665
|
+
return isEnabledByDefault(source);
|
|
10666
|
+
}
|
|
10667
|
+
|
|
10668
|
+
// ../../packages/telemetry-emit/src/sink.ts
|
|
10669
|
+
var noopSink = {
|
|
10670
|
+
kind: "noop",
|
|
10671
|
+
write() {
|
|
10672
|
+
}
|
|
10673
|
+
};
|
|
10674
|
+
|
|
10675
|
+
// ../../packages/telemetry-emit/src/emitter.ts
|
|
10676
|
+
function createEmitter(config) {
|
|
10677
|
+
const sink = config.sink ?? noopSink;
|
|
10678
|
+
const gate = { consented: config.consented, env: config.env };
|
|
10679
|
+
return {
|
|
10680
|
+
source: config.source,
|
|
10681
|
+
emit(input) {
|
|
10682
|
+
if (!shouldEmit(config.source, gate)) {
|
|
10683
|
+
return { status: "gated", reason: "disabled-or-no-consent" };
|
|
10684
|
+
}
|
|
10685
|
+
let event;
|
|
10686
|
+
try {
|
|
10687
|
+
event = sanitizeEvent({ ...input, source: config.source });
|
|
10688
|
+
} catch (err2) {
|
|
10689
|
+
return { status: "dropped", reason: err2 instanceof Error ? err2.message : String(err2) };
|
|
10690
|
+
}
|
|
10691
|
+
sink.write(event);
|
|
10692
|
+
return { status: "emitted", event };
|
|
10693
|
+
}
|
|
10694
|
+
};
|
|
10695
|
+
}
|
|
10696
|
+
|
|
10697
|
+
// src/telemetry.ts
|
|
10698
|
+
var VERDICT_EVENT = {
|
|
10699
|
+
SAFE: "decision_safe",
|
|
10700
|
+
REVIEW: "decision_review",
|
|
10701
|
+
BLOCK: "decision_block",
|
|
10702
|
+
UNKNOWN: "decision_unknown"
|
|
10703
|
+
};
|
|
10704
|
+
function eventFor(signal) {
|
|
10705
|
+
if (signal.event) return signal.event;
|
|
10706
|
+
if (signal.verdict) return VERDICT_EVENT[signal.verdict];
|
|
10707
|
+
return null;
|
|
10708
|
+
}
|
|
10709
|
+
function buildCliEmitter(env, opts = {}) {
|
|
10710
|
+
return createEmitter({ source: "cli", env, sink: opts.sink, consented: opts.consented });
|
|
10711
|
+
}
|
|
10712
|
+
function emitCommandSignal(emitter, signal, productVersion) {
|
|
10713
|
+
if (!emitter || !signal) return;
|
|
10714
|
+
try {
|
|
10715
|
+
const eventName = eventFor(signal);
|
|
10716
|
+
if (!eventName) return;
|
|
10717
|
+
const input = {
|
|
10718
|
+
eventName,
|
|
10719
|
+
...signal.verdict ? { result: signal.verdict } : {},
|
|
10720
|
+
...signal.hostFamily ? { hostFamily: signal.hostFamily } : {},
|
|
10721
|
+
...signal.inputKind ? { inputKind: signal.inputKind } : {},
|
|
10722
|
+
...productVersion ? { productVersion } : {}
|
|
10723
|
+
};
|
|
10724
|
+
emitter.emit(input);
|
|
10725
|
+
} catch {
|
|
10726
|
+
}
|
|
10727
|
+
}
|
|
10728
|
+
|
|
10498
10729
|
// src/run.ts
|
|
10499
10730
|
function run(argv, deps) {
|
|
10731
|
+
const result = dispatch(argv, deps);
|
|
10732
|
+
emitCommandSignal(deps.emitter, result.telemetry, deps.toolVersion);
|
|
10733
|
+
return result;
|
|
10734
|
+
}
|
|
10735
|
+
function dispatch(argv, deps) {
|
|
10500
10736
|
const args = parseArgs(argv);
|
|
10501
10737
|
const cmd = args.command;
|
|
10502
10738
|
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
@@ -10914,6 +11150,7 @@ async function main() {
|
|
|
10914
11150
|
await breathe(argv);
|
|
10915
11151
|
} catch {
|
|
10916
11152
|
}
|
|
11153
|
+
const emitter = buildCliEmitter(process.env);
|
|
10917
11154
|
const result = run(argv, {
|
|
10918
11155
|
cwd: process.cwd(),
|
|
10919
11156
|
readStdin,
|
|
@@ -10921,7 +11158,8 @@ async function main() {
|
|
|
10921
11158
|
generatedAt,
|
|
10922
11159
|
online,
|
|
10923
11160
|
toolVersion: resolveToolVersion(),
|
|
10924
|
-
getChangedFilesDiff: () => gitChangedFiles(process.cwd())
|
|
11161
|
+
getChangedFilesDiff: () => gitChangedFiles(process.cwd()),
|
|
11162
|
+
emitter
|
|
10925
11163
|
});
|
|
10926
11164
|
if (result.stdout) process.stdout.write(result.stdout + "\n");
|
|
10927
11165
|
if (result.stderr) process.stderr.write(result.stderr + "\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "calllint",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
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",
|
|
@@ -63,6 +63,8 @@
|
|
|
63
63
|
"@calllint/report-renderer": "workspace:*",
|
|
64
64
|
"@calllint/resolver": "workspace:*",
|
|
65
65
|
"@calllint/signature": "workspace:*",
|
|
66
|
+
"@calllint/telemetry-contract": "workspace:*",
|
|
67
|
+
"@calllint/telemetry-emit": "workspace:*",
|
|
66
68
|
"@calllint/types": "workspace:*",
|
|
67
69
|
"esbuild": "^0.21.5"
|
|
68
70
|
}
|