agent-security-scanner-mcp 3.0.0 → 3.2.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/README.md +451 -739
- package/analyzer.py +51 -7
- package/index.js +42 -2697
- package/package.json +7 -6
- package/regex_fallback.py +66 -0
- package/rules/__init__.py +124 -36
- package/rules/generic/secrets/gitleaks/adafruit-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/adobe-client-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/adobe-client-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/age-secret-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/airtable-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/algolia-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/alibaba-access-key-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/alibaba-secret-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/asana-client-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/asana-client-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/atlassian-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/authress-service-client-access-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/aws-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/beamer-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/bitbucket-client-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/bitbucket-client-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/bittrex-access-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/bittrex-secret-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/clojars-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/cloudflare-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/cloudflare-global-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/cloudflare-origin-ca-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/codecov-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/coinbase-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/confluent-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/confluent-secret-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/contentful-delivery-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/databricks-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/datadog-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/defined-networking-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/digitalocean-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/digitalocean-pat.yaml +27 -0
- package/rules/generic/secrets/gitleaks/digitalocean-refresh-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/discord-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/discord-client-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/discord-client-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/doppler-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/droneci-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/dropbox-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/dropbox-long-lived-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/dropbox-short-lived-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/duffel-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/dynatrace-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/easypost-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/easypost-test-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/etsy-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/facebook-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/facebook-page-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/facebook-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/facebook.yaml +27 -0
- package/rules/generic/secrets/gitleaks/fastly-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/finicity-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/finicity-client-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/finnhub-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/flickr-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/flutterwave-encryption-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/flutterwave-public-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/flutterwave-secret-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/frameio-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/freshbooks-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/gcp-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/generic-api-key.yaml +76 -0
- package/rules/generic/secrets/gitleaks/github-app-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/github-fine-grained-pat.yaml +27 -0
- package/rules/generic/secrets/gitleaks/github-oauth.yaml +27 -0
- package/rules/generic/secrets/gitleaks/github-pat.yaml +27 -0
- package/rules/generic/secrets/gitleaks/github-refresh-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/gitlab-pat.yaml +27 -0
- package/rules/generic/secrets/gitleaks/gitlab-ptt.yaml +27 -0
- package/rules/generic/secrets/gitleaks/gitlab-rrt.yaml +27 -0
- package/rules/generic/secrets/gitleaks/gitter-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/gocardless-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/grafana-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/grafana-cloud-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/grafana-service-account-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/harness-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/hashicorp-tf-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/hashicorp-tf-password.yaml +31 -0
- package/rules/generic/secrets/gitleaks/heroku-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/hubspot-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/huggingface-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/huggingface-organization-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/infracost-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/intercom-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/intra42-client-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/jfrog-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/jfrog-identity-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/jwt-base64.yaml +27 -0
- package/rules/generic/secrets/gitleaks/jwt.yaml +27 -0
- package/rules/generic/secrets/gitleaks/kraken-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/kucoin-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/kucoin-secret-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/launchdarkly-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/linear-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/linear-client-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/linkedin-client-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/linkedin-client-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/lob-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/lob-pub-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/mailchimp-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/mailgun-private-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/mailgun-pub-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/mailgun-signing-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/mapbox-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/mattermost-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/messagebird-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/messagebird-client-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/microsoft-teams-webhook.yaml +27 -0
- package/rules/generic/secrets/gitleaks/netlify-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/new-relic-browser-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/new-relic-insert-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/new-relic-user-api-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/new-relic-user-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/npm-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/nytimes-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/okta-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/openai-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/plaid-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/plaid-client-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/plaid-secret-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/planetscale-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/planetscale-oauth-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/planetscale-password.yaml +27 -0
- package/rules/generic/secrets/gitleaks/postman-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/prefect-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/private-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/pulumi-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/pypi-upload-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/rapidapi-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/readme-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/rubygems-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/scalingo-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/sendbird-access-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/sendbird-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/sendgrid-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/sendinblue-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/sentry-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/shippo-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/shopify-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/shopify-custom-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/shopify-private-app-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/shopify-shared-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/sidekiq-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/sidekiq-sensitive-url.yaml +27 -0
- package/rules/generic/secrets/gitleaks/slack-app-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/slack-bot-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/slack-config-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/slack-config-refresh-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/slack-legacy-bot-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/slack-legacy-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/slack-legacy-workspace-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/slack-user-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/slack-webhook-url.yaml +27 -0
- package/rules/generic/secrets/gitleaks/snyk-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/square-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/squarespace-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/stripe-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/sumologic-access-id.yaml +27 -0
- package/rules/generic/secrets/gitleaks/sumologic-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/telegram-bot-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/travisci-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/twilio-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/twitch-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/twitter-access-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/twitter-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/twitter-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/twitter-api-secret.yaml +27 -0
- package/rules/generic/secrets/gitleaks/twitter-bearer-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/typeform-api-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/vault-batch-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/vault-service-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/yandex-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/yandex-api-key.yaml +27 -0
- package/rules/generic/secrets/gitleaks/yandex-aws-access-token.yaml +27 -0
- package/rules/generic/secrets/gitleaks/zendesk-secret-key.yaml +27 -0
- package/rules/generic/secrets/security/detected-amazon-mws-auth-token.yaml +26 -0
- package/rules/generic/secrets/security/detected-artifactory-password.yaml +47 -0
- package/rules/generic/secrets/security/detected-artifactory-token.yaml +44 -0
- package/rules/generic/secrets/security/detected-aws-access-key-id-value.yaml +29 -0
- package/rules/generic/secrets/security/detected-aws-account-id.yaml +58 -0
- package/rules/generic/secrets/security/detected-aws-appsync-graphql-key.yaml +27 -0
- package/rules/generic/secrets/security/detected-aws-secret-access-key.yaml +30 -0
- package/rules/generic/secrets/security/detected-aws-session-token.yaml +31 -0
- package/rules/generic/secrets/security/detected-bcrypt-hash.yaml +25 -0
- package/rules/generic/secrets/security/detected-codeclimate.yaml +27 -0
- package/rules/generic/secrets/security/detected-etc-shadow.yaml +27 -0
- package/rules/generic/secrets/security/detected-facebook-access-token.yaml +29 -0
- package/rules/generic/secrets/security/detected-facebook-oauth.yaml +27 -0
- package/rules/generic/secrets/security/detected-generic-api-key.yaml +29 -0
- package/rules/generic/secrets/security/detected-generic-secret.yaml +30 -0
- package/rules/generic/secrets/security/detected-github-token.yaml +47 -0
- package/rules/generic/secrets/security/detected-google-api-key.yaml +29 -0
- package/rules/generic/secrets/security/detected-google-cloud-api-key.yaml +27 -0
- package/rules/generic/secrets/security/detected-google-gcm-service-account.yaml +27 -0
- package/rules/generic/secrets/security/detected-google-oauth-access-token.yaml +26 -0
- package/rules/generic/secrets/security/detected-google-oauth.yaml +26 -0
- package/rules/generic/secrets/security/detected-heroku-api-key.yaml +27 -0
- package/rules/generic/secrets/security/detected-hockeyapp.yaml +27 -0
- package/rules/generic/secrets/security/detected-jwt-token.yaml +25 -0
- package/rules/generic/secrets/security/detected-kolide-api-key.yaml +25 -0
- package/rules/generic/secrets/security/detected-mailchimp-api-key.yaml +26 -0
- package/rules/generic/secrets/security/detected-mailgun-api-key.yaml +26 -0
- package/rules/generic/secrets/security/detected-npm-registry-auth-token.yaml +33 -0
- package/rules/generic/secrets/security/detected-onfido-live-api-token.yaml +20 -0
- package/rules/generic/secrets/security/detected-outlook-team.yaml +27 -0
- package/rules/generic/secrets/security/detected-paypal-braintree-access-token.yaml +27 -0
- package/rules/generic/secrets/security/detected-pgp-private-key-block.yaml +28 -0
- package/rules/generic/secrets/security/detected-picatic-api-key.yaml +26 -0
- package/rules/generic/secrets/security/detected-private-key.yaml +39 -0
- package/rules/generic/secrets/security/detected-sauce-token.yaml +27 -0
- package/rules/generic/secrets/security/detected-sendgrid-api-key.yaml +27 -0
- package/rules/generic/secrets/security/detected-slack-token.yaml +28 -0
- package/rules/generic/secrets/security/detected-slack-webhook.yaml +27 -0
- package/rules/generic/secrets/security/detected-snyk-api-key.yaml +26 -0
- package/rules/generic/secrets/security/detected-softlayer-api-key.yaml +27 -0
- package/rules/generic/secrets/security/detected-sonarqube-docs-api-key.yaml +40 -0
- package/rules/generic/secrets/security/detected-square-access-token.yaml +26 -0
- package/rules/generic/secrets/security/detected-square-oauth-secret.yaml +27 -0
- package/rules/generic/secrets/security/detected-ssh-password.yaml +27 -0
- package/rules/generic/secrets/security/detected-stripe-api-key.yaml +26 -0
- package/rules/generic/secrets/security/detected-stripe-restricted-api-key.yaml +26 -0
- package/rules/generic/secrets/security/detected-telegram-bot-api-key.yaml +30 -0
- package/rules/generic/secrets/security/detected-twilio-api-key.yaml +26 -0
- package/rules/generic/secrets/security/detected-username-and-password-in-uri.yaml +35 -0
- package/rules/generic/secrets/security/google-maps-apikeyleak.yaml +25 -0
- package/rules/prompt-injection.security.yaml +4 -0
- package/rules/python/flask/security/injection/flask-injection-sinks.yaml +352 -0
- package/src/analyzer.py +119 -0
- package/src/cli/demo.js +238 -0
- package/src/cli/doctor.js +273 -0
- package/src/cli/init.js +288 -0
- package/src/fix-patterns.js +698 -0
- package/src/tools/check-package.js +169 -0
- package/src/tools/fix-security.js +115 -0
- package/src/tools/scan-packages.js +154 -0
- package/src/tools/scan-prompt.js +570 -0
- package/src/tools/scan-security.js +117 -0
- package/src/utils.js +153 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import bloomFilters from "bloom-filters";
|
|
6
|
+
const { BloomFilter } = bloomFilters;
|
|
7
|
+
|
|
8
|
+
// Handle both ESM and CJS bundling (Smithery bundles to CJS)
|
|
9
|
+
let __dirname;
|
|
10
|
+
try {
|
|
11
|
+
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
} catch {
|
|
13
|
+
__dirname = process.cwd();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Load legitimate package lists into memory (hash sets for O(1) lookup)
|
|
17
|
+
const LEGITIMATE_PACKAGES = {
|
|
18
|
+
dart: new Set(),
|
|
19
|
+
perl: new Set(),
|
|
20
|
+
raku: new Set(),
|
|
21
|
+
npm: new Set(),
|
|
22
|
+
pypi: new Set(),
|
|
23
|
+
rubygems: new Set(),
|
|
24
|
+
crates: new Set()
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Bloom filters for large package lists (memory-efficient probabilistic lookup)
|
|
28
|
+
const BLOOM_FILTERS = {
|
|
29
|
+
npm: null,
|
|
30
|
+
pypi: null,
|
|
31
|
+
rubygems: null
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Load package lists on startup
|
|
35
|
+
export function loadPackageLists() {
|
|
36
|
+
const packagesDir = join(__dirname, '..', '..', 'packages');
|
|
37
|
+
|
|
38
|
+
for (const ecosystem of Object.keys(LEGITIMATE_PACKAGES)) {
|
|
39
|
+
const filePath = join(packagesDir, `${ecosystem}.txt`);
|
|
40
|
+
try {
|
|
41
|
+
if (existsSync(filePath)) {
|
|
42
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
43
|
+
const packages = content.split('\n').filter(p => p.trim());
|
|
44
|
+
LEGITIMATE_PACKAGES[ecosystem] = new Set(packages);
|
|
45
|
+
console.error(`Loaded ${packages.length} ${ecosystem} packages`);
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(`Warning: Could not load ${ecosystem} packages: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Load bloom filters for large ecosystems (npm, pypi, rubygems)
|
|
53
|
+
for (const ecosystem of Object.keys(BLOOM_FILTERS)) {
|
|
54
|
+
const bloomPath = join(packagesDir, `${ecosystem}-bloom.json`);
|
|
55
|
+
try {
|
|
56
|
+
if (existsSync(bloomPath)) {
|
|
57
|
+
const bloomData = JSON.parse(readFileSync(bloomPath, 'utf-8'));
|
|
58
|
+
BLOOM_FILTERS[ecosystem] = BloomFilter.fromJSON(bloomData);
|
|
59
|
+
console.error(`Loaded ${ecosystem} bloom filter (${bloomData._size} bits)`);
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`Warning: Could not load ${ecosystem} bloom filter: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check if a package is hallucinated
|
|
68
|
+
export function isHallucinated(packageName, ecosystem) {
|
|
69
|
+
const legitPackages = LEGITIMATE_PACKAGES[ecosystem];
|
|
70
|
+
|
|
71
|
+
// First check Set-based lookup (exact match)
|
|
72
|
+
if (legitPackages && legitPackages.size > 0) {
|
|
73
|
+
return { hallucinated: !legitPackages.has(packageName) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Fall back to bloom filter for large ecosystems (npm, pypi, rubygems)
|
|
77
|
+
const bloomFilter = BLOOM_FILTERS[ecosystem];
|
|
78
|
+
if (bloomFilter) {
|
|
79
|
+
// Bloom filter: false = definitely not in set, true = probably in set
|
|
80
|
+
const mightExist = bloomFilter.has(packageName);
|
|
81
|
+
return {
|
|
82
|
+
hallucinated: !mightExist,
|
|
83
|
+
bloomFilter: true,
|
|
84
|
+
note: mightExist ? "Package likely exists (bloom filter match)" : "Package not found in bloom filter"
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { unknown: true, reason: `No package list loaded for ${ecosystem}` };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Get total packages count for an ecosystem
|
|
92
|
+
export function getTotalPackages(ecosystem) {
|
|
93
|
+
return LEGITIMATE_PACKAGES[ecosystem]?.size || 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Get all package stats
|
|
97
|
+
export function getPackageStats() {
|
|
98
|
+
const stats = Object.entries(LEGITIMATE_PACKAGES).map(([ecosystem, packages]) => {
|
|
99
|
+
const bloomFilter = BLOOM_FILTERS[ecosystem];
|
|
100
|
+
const setSize = packages.size;
|
|
101
|
+
const hasBloom = !!bloomFilter;
|
|
102
|
+
return {
|
|
103
|
+
ecosystem,
|
|
104
|
+
packages_loaded: setSize,
|
|
105
|
+
bloom_filter_loaded: hasBloom,
|
|
106
|
+
status: setSize > 0 ? 'ready' : hasBloom ? 'ready (bloom filter)' : 'not loaded'
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const totalSet = stats.reduce((sum, s) => sum + s.packages_loaded, 0);
|
|
111
|
+
const bloomEcosystems = stats.filter(s => s.bloom_filter_loaded).map(s => s.ecosystem);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
package_lists: stats,
|
|
115
|
+
total_packages: totalSet,
|
|
116
|
+
bloom_filter_ecosystems: bloomEcosystems,
|
|
117
|
+
note: bloomEcosystems.length > 0
|
|
118
|
+
? `Bloom filters provide coverage for: ${bloomEcosystems.join(', ')} (not counted in total_packages)`
|
|
119
|
+
: undefined
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Schema for check_package tool
|
|
124
|
+
export const checkPackageSchema = {
|
|
125
|
+
package_name: z.string().describe("The package name to verify"),
|
|
126
|
+
ecosystem: z.enum(["dart", "perl", "raku", "npm", "pypi", "rubygems", "crates"]).describe("The package ecosystem (dart=pub.dev, perl=CPAN, raku=raku.land, npm=npmjs, pypi=PyPI, rubygems=RubyGems, crates=crates.io)")
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Handler for check_package tool
|
|
130
|
+
export async function checkPackage({ package_name, ecosystem }) {
|
|
131
|
+
const result = isHallucinated(package_name, ecosystem);
|
|
132
|
+
|
|
133
|
+
if (result.unknown) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{
|
|
136
|
+
type: "text",
|
|
137
|
+
text: JSON.stringify({
|
|
138
|
+
package: package_name,
|
|
139
|
+
ecosystem,
|
|
140
|
+
status: "unknown",
|
|
141
|
+
reason: result.reason,
|
|
142
|
+
suggestion: "Load package list or verify manually at the package registry"
|
|
143
|
+
}, null, 2)
|
|
144
|
+
}]
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const exists = !result.hallucinated;
|
|
149
|
+
const confidence = result.bloomFilter ? "medium" : "high";
|
|
150
|
+
const totalPackages = getTotalPackages(ecosystem);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
content: [{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: JSON.stringify({
|
|
156
|
+
package: package_name,
|
|
157
|
+
ecosystem,
|
|
158
|
+
legitimate: exists,
|
|
159
|
+
hallucinated: !exists,
|
|
160
|
+
confidence,
|
|
161
|
+
bloom_filter: !!result.bloomFilter,
|
|
162
|
+
total_known_packages: totalPackages,
|
|
163
|
+
recommendation: exists
|
|
164
|
+
? "Package exists in registry - safe to use"
|
|
165
|
+
: "⚠️ POTENTIAL HALLUCINATION - Package not found in registry. Verify before using!"
|
|
166
|
+
}, null, 2)
|
|
167
|
+
}]
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// src/tools/fix-security.js
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
import { detectLanguage, runAnalyzer, generateFix } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
export const fixSecuritySchema = {
|
|
7
|
+
file_path: z.string().describe("Path to the file to fix"),
|
|
8
|
+
verbosity: z.enum(['minimal', 'compact', 'full']).optional().describe("Response detail level: 'minimal' (summary only), 'compact' (default), 'full' (includes fixed_content)")
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// Verbosity formatters
|
|
12
|
+
function formatFixMinimal(file_path, fixes) {
|
|
13
|
+
return {
|
|
14
|
+
file: file_path,
|
|
15
|
+
fixes_applied: fixes.length,
|
|
16
|
+
message: fixes.length > 0
|
|
17
|
+
? `Applied ${fixes.length} fix(es). Use verbosity='compact' for details.`
|
|
18
|
+
: "No fixes needed."
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatFixCompact(file_path, fixes) {
|
|
23
|
+
return {
|
|
24
|
+
file: file_path,
|
|
25
|
+
fixes_applied: fixes.length,
|
|
26
|
+
fixes: fixes.map(f => ({
|
|
27
|
+
line: f.line,
|
|
28
|
+
rule: f.rule,
|
|
29
|
+
description: f.description
|
|
30
|
+
}))
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatFixFull(file_path, fixes, fixedContent) {
|
|
35
|
+
return {
|
|
36
|
+
file: file_path,
|
|
37
|
+
fixes_applied: fixes.length,
|
|
38
|
+
fixes: fixes,
|
|
39
|
+
fixed_content: fixedContent
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function fixSecurity({ file_path, verbosity }) {
|
|
44
|
+
if (!existsSync(file_path)) {
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: JSON.stringify({ error: "File not found" }) }]
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const issues = runAnalyzer(file_path);
|
|
51
|
+
|
|
52
|
+
if (issues.error || !Array.isArray(issues) || issues.length === 0) {
|
|
53
|
+
return {
|
|
54
|
+
content: [{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: JSON.stringify({
|
|
57
|
+
message: issues.error ? "Error scanning file" : "No security issues found",
|
|
58
|
+
details: issues
|
|
59
|
+
})
|
|
60
|
+
}]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Read and fix the file
|
|
65
|
+
const content = readFileSync(file_path, 'utf-8');
|
|
66
|
+
const lines = content.split('\n');
|
|
67
|
+
const language = detectLanguage(file_path);
|
|
68
|
+
const fixes = [];
|
|
69
|
+
|
|
70
|
+
// Apply fixes (process in reverse order to preserve line numbers)
|
|
71
|
+
const sortedIssues = [...issues].sort((a, b) => b.line - a.line);
|
|
72
|
+
|
|
73
|
+
for (const issue of sortedIssues) {
|
|
74
|
+
const lineIndex = issue.line;
|
|
75
|
+
if (lineIndex >= 0 && lineIndex < lines.length) {
|
|
76
|
+
const originalLine = lines[lineIndex];
|
|
77
|
+
const fix = generateFix(issue, originalLine, language);
|
|
78
|
+
|
|
79
|
+
if (fix.fixed && fix.fixed !== originalLine) {
|
|
80
|
+
lines[lineIndex] = fix.fixed;
|
|
81
|
+
fixes.push({
|
|
82
|
+
line: lineIndex + 1,
|
|
83
|
+
rule: issue.ruleId,
|
|
84
|
+
original: originalLine.trim(),
|
|
85
|
+
fixed: fix.fixed.trim(),
|
|
86
|
+
description: fix.description
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Determine verbosity (default: compact)
|
|
93
|
+
const level = verbosity || 'compact';
|
|
94
|
+
const fixedContent = lines.join('\n');
|
|
95
|
+
|
|
96
|
+
let result;
|
|
97
|
+
switch (level) {
|
|
98
|
+
case 'minimal':
|
|
99
|
+
result = formatFixMinimal(file_path, fixes);
|
|
100
|
+
break;
|
|
101
|
+
case 'full':
|
|
102
|
+
result = formatFixFull(file_path, fixes, fixedContent);
|
|
103
|
+
break;
|
|
104
|
+
case 'compact':
|
|
105
|
+
default:
|
|
106
|
+
result = formatFixCompact(file_path, fixes);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
content: [{
|
|
111
|
+
type: "text",
|
|
112
|
+
text: JSON.stringify(result, null, 2)
|
|
113
|
+
}]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { isHallucinated, getTotalPackages } from './check-package.js';
|
|
4
|
+
|
|
5
|
+
// Package import patterns by ecosystem
|
|
6
|
+
const IMPORT_PATTERNS = {
|
|
7
|
+
dart: [
|
|
8
|
+
/import\s+['"]package:([^\/'"]+)/g,
|
|
9
|
+
/dependencies:\s*\n(?:\s+(\w+):\s*[\^~]?[\d.]+\n)+/g
|
|
10
|
+
],
|
|
11
|
+
perl: [
|
|
12
|
+
/use\s+([\w:]+)/g,
|
|
13
|
+
/require\s+([\w:]+)/g
|
|
14
|
+
],
|
|
15
|
+
raku: [
|
|
16
|
+
/use\s+([\w:]+)/g,
|
|
17
|
+
/need\s+([\w:]+)/g
|
|
18
|
+
],
|
|
19
|
+
npm: [
|
|
20
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
21
|
+
/from\s+['"]([^'"]+)['"]/g,
|
|
22
|
+
/import\s+['"]([^'"]+)['"]/g
|
|
23
|
+
],
|
|
24
|
+
pypi: [
|
|
25
|
+
/^import\s+([\w]+)/gm,
|
|
26
|
+
/^from\s+([\w]+)/gm
|
|
27
|
+
],
|
|
28
|
+
rubygems: [
|
|
29
|
+
/require\s+['"]([^'"]+)['"]/g,
|
|
30
|
+
/gem\s+['"]([^'"]+)['"]/g,
|
|
31
|
+
/require_relative\s+['"]([^'"]+)['"]/g
|
|
32
|
+
],
|
|
33
|
+
crates: [
|
|
34
|
+
/use\s+([\w_]+)/g,
|
|
35
|
+
/extern\s+crate\s+([\w_]+)/g,
|
|
36
|
+
/^\s*[\w_-]+\s*=/gm // Cargo.toml dependencies
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Extract package names from code
|
|
41
|
+
export function extractPackages(code, ecosystem) {
|
|
42
|
+
const packages = new Set();
|
|
43
|
+
const patterns = IMPORT_PATTERNS[ecosystem] || [];
|
|
44
|
+
|
|
45
|
+
for (const pattern of patterns) {
|
|
46
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
47
|
+
let match;
|
|
48
|
+
while ((match = regex.exec(code)) !== null) {
|
|
49
|
+
const pkg = match[1];
|
|
50
|
+
if (pkg && !pkg.startsWith('.') && !pkg.startsWith('/')) {
|
|
51
|
+
// Normalize package name (handle scoped packages, subpaths)
|
|
52
|
+
const basePkg = pkg.split('/')[0].replace(/^@/, '');
|
|
53
|
+
packages.add(basePkg);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Array.from(packages);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Schema for scan_packages tool
|
|
62
|
+
export const scanPackagesSchema = {
|
|
63
|
+
file_path: z.string().describe("Path to the file to scan"),
|
|
64
|
+
ecosystem: z.enum(["dart", "perl", "raku", "npm", "pypi", "rubygems", "crates"]).describe("The package ecosystem (dart=pub.dev, perl=CPAN, raku=raku.land, npm=npmjs, pypi=PyPI, rubygems=RubyGems, crates=crates.io)"),
|
|
65
|
+
verbosity: z.enum(['minimal', 'compact', 'full']).optional().describe("Response detail level: 'minimal' (counts only), 'compact' (default), 'full' (all details)")
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Handler for scan_packages tool
|
|
69
|
+
export async function scanPackages({ file_path, ecosystem, verbosity }) {
|
|
70
|
+
if (!existsSync(file_path)) {
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "text", text: JSON.stringify({ error: "File not found" }) }]
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const code = readFileSync(file_path, 'utf-8');
|
|
77
|
+
const packages = extractPackages(code, ecosystem);
|
|
78
|
+
|
|
79
|
+
const results = packages.map(pkg => {
|
|
80
|
+
const check = isHallucinated(pkg, ecosystem);
|
|
81
|
+
if (check.unknown) {
|
|
82
|
+
return { package: pkg, status: "unknown", reason: check.reason };
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
package: pkg,
|
|
86
|
+
legitimate: !check.hallucinated,
|
|
87
|
+
hallucinated: check.hallucinated,
|
|
88
|
+
bloom_filter: !!check.bloomFilter,
|
|
89
|
+
confidence: check.bloomFilter ? "medium" : "high"
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const hallucinated = results.filter(r => r.hallucinated);
|
|
94
|
+
const legitimate = results.filter(r => r.legitimate);
|
|
95
|
+
const unknown = results.filter(r => r.status === "unknown");
|
|
96
|
+
const totalKnown = getTotalPackages(ecosystem);
|
|
97
|
+
|
|
98
|
+
// Determine verbosity (default: compact)
|
|
99
|
+
const level = verbosity || 'compact';
|
|
100
|
+
|
|
101
|
+
const recommendation = hallucinated.length > 0
|
|
102
|
+
? `Found ${hallucinated.length} potentially hallucinated package(s): ${hallucinated.map(r => r.package).join(', ')}`
|
|
103
|
+
: unknown.length > 0
|
|
104
|
+
? `${unknown.length} package(s) could not be verified (no data available for ${ecosystem})`
|
|
105
|
+
: "All packages verified as legitimate";
|
|
106
|
+
|
|
107
|
+
let result;
|
|
108
|
+
switch (level) {
|
|
109
|
+
case 'minimal':
|
|
110
|
+
result = {
|
|
111
|
+
file: file_path,
|
|
112
|
+
ecosystem,
|
|
113
|
+
total: packages.length,
|
|
114
|
+
hallucinated: hallucinated.length,
|
|
115
|
+
legitimate: legitimate.length,
|
|
116
|
+
unknown: unknown.length,
|
|
117
|
+
message: recommendation
|
|
118
|
+
};
|
|
119
|
+
break;
|
|
120
|
+
case 'full':
|
|
121
|
+
result = {
|
|
122
|
+
file: file_path,
|
|
123
|
+
ecosystem,
|
|
124
|
+
total_packages_found: packages.length,
|
|
125
|
+
legitimate_count: legitimate.length,
|
|
126
|
+
hallucinated_count: hallucinated.length,
|
|
127
|
+
unknown_count: unknown.length,
|
|
128
|
+
known_packages_in_registry: totalKnown,
|
|
129
|
+
hallucinated_packages: hallucinated.map(r => r.package),
|
|
130
|
+
legitimate_packages: legitimate.map(r => r.package),
|
|
131
|
+
all_results: results,
|
|
132
|
+
recommendation
|
|
133
|
+
};
|
|
134
|
+
break;
|
|
135
|
+
case 'compact':
|
|
136
|
+
default:
|
|
137
|
+
result = {
|
|
138
|
+
file: file_path,
|
|
139
|
+
ecosystem,
|
|
140
|
+
total_packages_found: packages.length,
|
|
141
|
+
hallucinated_count: hallucinated.length,
|
|
142
|
+
hallucinated_packages: hallucinated.map(r => r.package),
|
|
143
|
+
legitimate_count: legitimate.length,
|
|
144
|
+
recommendation
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
content: [{
|
|
150
|
+
type: "text",
|
|
151
|
+
text: JSON.stringify(result, null, 2)
|
|
152
|
+
}]
|
|
153
|
+
};
|
|
154
|
+
}
|