🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@graneth/mcp-server

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@graneth/mcp-server - npm Package Compare versions

Comparing version
0.4.1
to
0.5.0
+110
-3
dist/index.js

@@ -161,4 +161,14 @@ #!/usr/bin/env node

const isNewPackage = firstVersionTime ? Date.now() - firstVersionTime.getTime() < NEW_PACKAGE_WINDOW_MS : false;
return { exists: true, isNewPackage, publishedAt: firstVersionTime };
return { exists: true, isNewPackage, publishedAt: firstVersionTime, ...parseNpmTrustSignals(data) };
}
function parseNpmTrustSignals(data) {
const latest = data["dist-tags"]?.latest ? data.versions?.[data["dist-tags"].latest] : null;
const scripts = latest?.scripts ?? {};
return {
hasInstallScripts: !!(scripts.preinstall || scripts.install || scripts.postinstall),
hasProvenance: !!latest?.dist?.attestations,
isDeprecated: typeof latest?.deprecated === "string" && latest.deprecated.length > 0,
hasRepository: !!data.repository?.url
};
}
async function fetchPypi(pkg) {

@@ -652,2 +662,73 @@ const url = `https://pypi.org/pypi/${encodeURIComponent(pkg)}/json`;

// ../core-checks/src/riskScore.ts
var LOW_ADOPTION = 1e3;
var VERY_LOW_ADOPTION = 50;
var HIGH_ADOPTION = 1e5;
var VERY_HIGH_ADOPTION = 1e6;
var ESTABLISHED_ADOPTION = 5e3;
function bandOf(score) {
if (score >= 75) return "critical";
if (score >= 50) return "high";
if (score >= 25) return "elevated";
return "minimal";
}
function isNew(s) {
return s.isNewPackage === true || s.ageDays != null && s.ageDays < 30;
}
function applyDominantSignals(s, add) {
if (s.isSecurityHolding) add("security_holding", 70, "Registry seized this name after a malware/typosquat incident.");
if (s.knownHallucination) add("known_hallucination", 45, "Name appears in the known-AI-hallucination corpus and has since been registered (slopsquat).");
if (s.typosquatDistance === 1) add("typosquat_1", 55, "One edit away from a hugely popular package name (typosquat shape).");
else if (s.typosquatDistance === 2) add("typosquat_2", 35, "Two edits away from a popular package name (possible typosquat).");
}
function applyCompoundingSignals(s, add, newPkg, dl) {
const lowAdoption = dl != null && dl < LOW_ADOPTION;
const veryLowAdoption = dl != null && dl < VERY_LOW_ADOPTION;
if (newPkg) add("new_package", 22, "Published within the last 30 days \u2014 the pre-registration attack window.");
if (s.hasInstallScripts) {
if (newPkg || lowAdoption) add("install_script_risk", 30, "Runs an install script AND is new / near-zero adoption \u2014 the npm dropper shape.");
else add("install_script", 5, "Runs an install script (common for native builds; low risk on an established package).");
}
if (s.isDeprecated) add("deprecated", 15, "Deprecated by its maintainer \u2014 unpatched and a name-takeover target.");
if (veryLowAdoption) add("very_low_adoption", 28, "Near-zero adoption \u2014 no crowd has vetted this dependency.");
else if (lowAdoption) add("low_adoption", 15, "Low adoption for its age.");
if (s.hasRepository === false) add("no_repository", 12, "No linked source repository to inspect.");
if (s.maintainersCount === 1) add("single_maintainer", 8, "Single maintainer \u2014 one account compromise ships to every consumer.");
}
function applyMitigators(s, add, newPkg, dl) {
if (s.hasProvenance) add("provenance", -25, "Registry-verified build provenance (public CI from a public repo) \u2014 strong legitimacy.");
if (dl != null && dl > VERY_HIGH_ADOPTION) add("very_high_adoption", -50, "Very high adoption \u2014 extensively used and watched.");
else if (dl != null && dl > HIGH_ADOPTION) add("high_adoption", -35, "High adoption \u2014 widely used.");
if (!newPkg && s.hasRepository === true && dl != null && dl >= ESTABLISHED_ADOPTION) {
add("established", -15, "Established: aged, repo-backed, with real adoption.");
}
}
function computeDependencyRisk(s) {
if (s.unreachable) return null;
if (!s.exists) {
return {
score: 100,
band: "critical",
factors: [{ signal: "nonexistent", points: 100, note: "Package does not exist in its registry (AI-hallucinated / ghost)." }]
};
}
const factors = [];
const add = (signal, points, note) => {
factors.push({ signal, points, note });
};
const newPkg = isNew(s);
const dl = s.weeklyDownloads;
applyDominantSignals(s, add);
applyCompoundingSignals(s, add, newPkg, dl);
applyMitigators(s, add, newPkg, dl);
const raw = factors.reduce((sum, f) => sum + f.points, 0);
const score = Math.max(0, Math.min(100, raw));
return { score, band: bandOf(score), factors };
}
function isStackedRisk(risk) {
if (!risk || risk.band !== "high" && risk.band !== "critical") return false;
const maxPositive = Math.max(0, ...risk.factors.filter((f) => f.points > 0).map((f) => f.points));
return maxPositive < 50;
}
// ../core-checks/src/preflight.ts

@@ -816,2 +897,4 @@ var TEST_FIXTURE_PATH_RE = /(^|[\\/])(__tests__|__mocks__|fixtures?|testdata|tests?)[\\/]|\.(test|spec)\.[^\\/]+$|(^|[\\/])test_[^\\/]*\.py$|_test\.py$|(^|[\\/])conftest\.py$/i;

}
const riskFinding = dependencyRiskShapeFinding(ref, signal);
if (riskFinding) findings.push(riskFinding);
}

@@ -821,2 +904,26 @@ }

}
function dependencyRiskShapeFinding(ref, signal) {
if (!signal.exists || signal.unreachable) return null;
const risk = computeDependencyRisk({
exists: true,
isNewPackage: signal.isNewPackage,
ageDays: signal.publishedAt ? Math.floor((Date.now() - signal.publishedAt.getTime()) / 864e5) : null,
hasRepository: signal.hasRepository,
hasProvenance: signal.hasProvenance,
hasInstallScripts: signal.hasInstallScripts,
isDeprecated: signal.isDeprecated
});
if (!isStackedRisk(risk)) return null;
const shape = risk.factors.filter((f) => f.points > 0).map((f) => f.note).join(" ");
return {
type: "dependency_risk_shape",
severity: "warning",
title: `"${ref.pkg}" has a compounded supply-chain risk shape (${risk.score}/100)`,
description: `"${ref.pkg}" exists, but several individually-weak signals compound into an elevated risk shape \u2014 the pattern of a low-adoption malicious package or patient squat that trips no single alarm. ${shape} Advisory assessment from package metadata, NOT a malware detection. Found in \`${ref.filename}\` at line ${ref.line}.`,
file: ref.filename,
line: ref.line,
recommendation: `Review "${ref.pkg}" before trusting it \u2014 inspect its repository, maintainer history and recent releases. If your agent chose it and you can't justify it, prefer a well-established alternative.`,
cve: "CWE-1357"
};
}
async function preFlightCheck(files) {

@@ -843,4 +950,4 @@ const [secretFindings, packageFindings] = await Promise.all([

// src/index.ts
var VERSION = "0.4.1";
var PRE_FLIGHT_CHECK_DESCRIPTION = "Security pre-flight check for local file changes BEFORE committing. Run this whenever you are about to suggest `git commit`, `git push`, or open a pull request \u2014 especially when changes add package imports or dependencies (package.json / requirements.txt / Cargo.toml / go.mod / Gemfile / composer.json / import statements) or could contain secrets. Detects AI-hallucinated (non-existent) packages by live-checking six registries (npm, PyPI, crates.io, RubyGems, Go module proxy, Packagist), plus hardcoded credentials via pattern + entropy analysis. Always free, no account required. Returns CLEAR, REVIEW_REQUIRED, or BLOCKED with specific findings and remediation.";
var VERSION = "0.5.0";
var PRE_FLIGHT_CHECK_DESCRIPTION = "Security pre-flight check for local file changes BEFORE committing. Run this whenever you are about to suggest `git commit`, `git push`, or open a pull request \u2014 especially when changes add package imports or dependencies (package.json / requirements.txt / Cargo.toml / go.mod / Gemfile / composer.json / import statements) or could contain secrets. Detects AI-hallucinated (non-existent) packages by live-checking six registries (npm, PyPI, crates.io, RubyGems, Go module proxy, Packagist); risk-scores dependencies an AI agent introduced by compounding metadata signals (new + install-scripts + low-adoption + no-provenance = an attack shape no single check flags); and finds hardcoded credentials via pattern + entropy analysis. Always free, no account required. Returns CLEAR, REVIEW_REQUIRED, or BLOCKED with specific findings and remediation.";
var PRE_FLIGHT_CHECK_SCHEMA = {

@@ -847,0 +954,0 @@ files: z.array(

+2
-2
{
"name": "@graneth/mcp-server",
"version": "0.4.1",
"description": "Account-free MCP server: catch AI-hallucinated packages (npm, PyPI, crates.io, RubyGems, Go, Packagist) and hardcoded secrets before you commit. Exposes the free pre_flight_check tool over stdio.",
"version": "0.5.0",
"description": "Account-free MCP server: catch AI-hallucinated packages (npm, PyPI, crates.io, RubyGems, Go, Packagist), risk-score the dependencies an AI agent introduces, and find hardcoded secrets before you commit. Exposes the free pre_flight_check tool over stdio.",
"type": "module",

@@ -6,0 +6,0 @@ "bin": {

@@ -24,2 +24,9 @@ # @graneth/mcp-server

slopsquatting attack window. → **REVIEW_REQUIRED**.
- **Risk-score the dependencies your AI agent introduced** — existence is only
the start. A *real* package can still carry the shape attackers exploit:
brand-new **and** running an install script **and** near-zero adoption **and**
no provenance attestation. None of those trips a single alarm; compounded they
do. An advisory `dependency_risk_shape` warning surfaces that stacked shape at
generation time — explicitly a risk **assessment**, never a malware claim.
→ **REVIEW_REQUIRED**.
- **Catch hardcoded secrets** — known credential patterns (AWS, GitHub, Slack,

@@ -26,0 +33,0 @@ OpenAI `sk-…`/`sk-proj-…`, Anthropic `sk-ant-…`, Stripe `sk_live_…`, PEM