tokentracker-cli 0.23.2 → 0.24.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/dashboard/dist/assets/{Card-WqeqJCTJ.js → Card-B3zit51o.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-DfXV-T4E.js → DashboardPage-p0PukR6H.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-CKQwUIFV.js → DevicePage-cYRFC4jD.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-BvXHu5jo.js → FadeIn-BeLgcj1F.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-BYgV73_y.js → HeaderGithubStar-DJJ-z5Wo.js} +1 -1
- package/dashboard/dist/assets/IpCheckPage-CxswyJC6.js +15 -0
- package/dashboard/dist/assets/{LandingPage-Dymt6fsi.js → LandingPage-Bc1ahbkE.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BWJX1oGI.js → LeaderboardPage-khmZdEvK.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-DmNl8aoL.js → LeaderboardProfilePage-NdMct8UA.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-D_q9Z5nB.js → LimitsPage-Dc-Rgfn2.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-BvwX7KJg.js → LoginPage-nhgDDL4s.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-CrBQ_8vY.js → PopoverPopup-D7c8Tufb.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-Cb-YMfcK.js → ProviderIcon-B79ytQj1.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-u30V0Mqy.js → SettingsPage-Dv5A3ADR.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-ssktZGFE.js → SkillsPage-CJdMytgb.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-_geExCIR.js → WidgetsPage-PteVAvZa.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-C5Wcs8L4.js → WrappedPage-r7rchUJS.js} +1 -1
- package/dashboard/dist/assets/check-CQ9GtDMA.js +1 -0
- package/dashboard/dist/assets/{chevron-down-BbPjwLP4.js → chevron-down-CNvz7vRS.js} +1 -1
- package/dashboard/dist/assets/{download-iIollDJi.js → download-HiydOOCL.js} +1 -1
- package/dashboard/dist/assets/{info-DjS23Duh.js → info-CWqFjI4Z.js} +1 -1
- package/dashboard/dist/assets/leaderboard-columns-BWyNquUf.js +1 -0
- package/dashboard/dist/assets/{main-Cb6vMAZa.js → main-C1bPtjN7.js} +142 -17
- package/dashboard/dist/assets/main-JP_EYeq-.css +1 -0
- package/dashboard/dist/assets/{use-limits-display-prefs-DZ9hgvef.js → use-limits-display-prefs-CTnU4ZMo.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-DS1TfSil.js → use-native-settings-DkTJDNZG.js} +1 -1
- package/dashboard/dist/assets/{use-reduced-motion-D82galUK.js → use-reduced-motion-DJE4Frme.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-B-svr8YY.js → use-usage-limits-OjbraegI.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-uA9QPua4.js → useCurrency-DJA7tRVi.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/cli.js +1 -2
- package/src/commands/serve.js +2 -57
- package/src/lib/local-api.js +45 -79
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/dashboard/dist/assets/IpCheckPage-BjuJDGa8.js +0 -1
- package/dashboard/dist/assets/check-Cu-lm-wz.js +0 -1
- package/dashboard/dist/assets/leaderboard-columns-DTnRH3EZ.js +0 -1
- package/dashboard/dist/assets/main-0bSXJNLn.css +0 -1
package/src/lib/local-api.js
CHANGED
|
@@ -457,45 +457,13 @@ function isLoopbackHostname(hostname) {
|
|
|
457
457
|
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1" || hostname === "[::1]";
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
function
|
|
461
|
-
if (typeof value !== "string") return null;
|
|
462
|
-
const raw = value.trim();
|
|
463
|
-
if (!raw || raw.includes("*") || /\s/.test(raw)) return null;
|
|
464
|
-
try {
|
|
465
|
-
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
466
|
-
const url = new URL(withScheme);
|
|
467
|
-
if (!url.hostname || url.username || url.password) return null;
|
|
468
|
-
return url.hostname.toLowerCase();
|
|
469
|
-
} catch (_e) {
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function normalizeAllowedHosts(values) {
|
|
475
|
-
const raw = Array.isArray(values) ? values : [values];
|
|
476
|
-
const out = [];
|
|
477
|
-
const seen = new Set();
|
|
478
|
-
for (const item of raw) {
|
|
479
|
-
const host = normalizeAllowedHost(item);
|
|
480
|
-
if (!host || seen.has(host)) continue;
|
|
481
|
-
seen.add(host);
|
|
482
|
-
out.push(host);
|
|
483
|
-
}
|
|
484
|
-
return out;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function hasAllowedLoopbackOrigin(headers = {}, allowedHosts = []) {
|
|
488
|
-
const allowed = new Set(normalizeAllowedHosts(allowedHosts));
|
|
460
|
+
function hasAllowedLoopbackOrigin(headers = {}) {
|
|
489
461
|
const candidates = [headers.origin, headers.referer];
|
|
490
462
|
for (const raw of candidates) {
|
|
491
463
|
if (raw == null || raw === "") continue;
|
|
492
464
|
try {
|
|
493
465
|
const url = new URL(String(raw));
|
|
494
|
-
if (
|
|
495
|
-
const host = url.hostname.toLowerCase();
|
|
496
|
-
if (url.protocol === "http:" && isLoopbackHostname(host)) continue;
|
|
497
|
-
if (allowed.has(host)) continue;
|
|
498
|
-
return false;
|
|
466
|
+
if (url.protocol !== "http:" || !isLoopbackHostname(url.hostname)) return false;
|
|
499
467
|
} catch (_e) {
|
|
500
468
|
return false;
|
|
501
469
|
}
|
|
@@ -673,36 +641,24 @@ function json(res, data, status) {
|
|
|
673
641
|
}
|
|
674
642
|
|
|
675
643
|
// ---------------------------------------------------------------------------
|
|
676
|
-
// IP check proxy
|
|
677
|
-
//
|
|
678
|
-
// /
|
|
679
|
-
//
|
|
680
|
-
//
|
|
644
|
+
// IP check API proxy: dashboard/src/pages/IpCheckPage.jsx is a native React
|
|
645
|
+
// page that calls ip.net.coffee's data endpoints (/api/iprisk, /api/geoip,
|
|
646
|
+
// /api/dns/result, /favicons, /claude/status.json). Browser-side fetch can't
|
|
647
|
+
// hit them cross-origin from the dashboard, so we reverse-proxy /proxy/ipcheck/*
|
|
648
|
+
// to https://ip.net.coffee/* and strip embedding-hostile headers.
|
|
649
|
+
// (Previously this proxy also served the upstream HTML page for an iframe;
|
|
650
|
+
// the iframe and its HTML-rewrite path have been removed.)
|
|
681
651
|
// ---------------------------------------------------------------------------
|
|
682
652
|
|
|
683
653
|
const IP_CHECK_PROXY_PREFIX = "/proxy/ipcheck";
|
|
684
654
|
const IP_CHECK_TARGET = "https://ip.net.coffee";
|
|
685
655
|
|
|
686
|
-
function rewriteIpCheckHtml(html) {
|
|
687
|
-
const prefix = IP_CHECK_PROXY_PREFIX;
|
|
688
|
-
return html
|
|
689
|
-
.replace(
|
|
690
|
-
/(\s(?:href|src|action)\s*=\s*["'])\/(?!\/|proxy\/ipcheck\/)/g,
|
|
691
|
-
`$1${prefix}/`,
|
|
692
|
-
)
|
|
693
|
-
.replace(
|
|
694
|
-
/(fetch\s*\(\s*["'`])\/(?!\/|proxy\/ipcheck\/)/g,
|
|
695
|
-
`$1${prefix}/`,
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
|
-
|
|
699
656
|
// ---------------------------------------------------------------------------
|
|
700
657
|
// Main handler factory
|
|
701
658
|
// ---------------------------------------------------------------------------
|
|
702
659
|
|
|
703
|
-
function createLocalApiHandler({ queuePath
|
|
660
|
+
function createLocalApiHandler({ queuePath }) {
|
|
704
661
|
const qp = queuePath || resolveQueuePath();
|
|
705
|
-
const allowedLocalOriginHosts = normalizeAllowedHosts(allowedHosts);
|
|
706
662
|
|
|
707
663
|
// Server-side cookie relay: captures auth cookies from InsForge cloud responses
|
|
708
664
|
// so that both browser and WKWebView share the same login session via the proxy.
|
|
@@ -874,7 +830,7 @@ function createLocalApiHandler({ queuePath, allowedHosts = [] } = {}) {
|
|
|
874
830
|
? headerToken.trim()
|
|
875
831
|
: cookieToken || "";
|
|
876
832
|
if (!token || token !== localAuthToken) return false;
|
|
877
|
-
return hasAllowedLoopbackOrigin(req?.headers || {}
|
|
833
|
+
return hasAllowedLoopbackOrigin(req?.headers || {});
|
|
878
834
|
}
|
|
879
835
|
|
|
880
836
|
return async function handleLocalApi(req, res, url) {
|
|
@@ -1015,34 +971,49 @@ function createLocalApiHandler({ queuePath, allowedHosts = [] } = {}) {
|
|
|
1015
971
|
return true;
|
|
1016
972
|
}
|
|
1017
973
|
|
|
1018
|
-
// --- ip-check proxy: reverse-proxy ip.net.coffee
|
|
974
|
+
// --- ip-check proxy: reverse-proxy ip.net.coffee (issue #81) ---
|
|
975
|
+
// Lock-down: GET/HEAD only, restricted path prefixes, do not forward
|
|
976
|
+
// browser credentials or fingerprintable headers. Without these limits
|
|
977
|
+
// /proxy/ipcheck is an open reverse-proxy any local process can abuse
|
|
978
|
+
// (exfiltrate dashboard cookies, anonymously POST through user IP).
|
|
1019
979
|
if (p.startsWith(`${IP_CHECK_PROXY_PREFIX}/`) || p === IP_CHECK_PROXY_PREFIX) {
|
|
980
|
+
const method = String(req.method || "GET").toUpperCase();
|
|
981
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
982
|
+
json(res, { error: "Method Not Allowed" }, 405);
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
1020
985
|
const targetPath = p === IP_CHECK_PROXY_PREFIX
|
|
1021
986
|
? "/"
|
|
1022
987
|
: p.slice(IP_CHECK_PROXY_PREFIX.length) || "/";
|
|
988
|
+
const ALLOWED_PREFIXES = [
|
|
989
|
+
"/api/geoip/",
|
|
990
|
+
"/api/geoip-batch",
|
|
991
|
+
"/api/iprisk/",
|
|
992
|
+
"/api/dns/result/",
|
|
993
|
+
"/claude/status.json",
|
|
994
|
+
"/favicons/",
|
|
995
|
+
"/ip/",
|
|
996
|
+
];
|
|
997
|
+
if (!ALLOWED_PREFIXES.some((prefix) => targetPath.startsWith(prefix))) {
|
|
998
|
+
json(res, { error: "Path not allowed" }, 403);
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
1023
1001
|
const targetUrl = `${IP_CHECK_TARGET}${targetPath}${url.search || ""}`;
|
|
1024
1002
|
try {
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
let body;
|
|
1036
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
1037
|
-
const chunks = [];
|
|
1038
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
1039
|
-
if (chunks.length > 0) body = Buffer.concat(chunks);
|
|
1040
|
-
}
|
|
1003
|
+
// Whitelist forwarded headers — no cookies, no auth, no fingerprintable
|
|
1004
|
+
// identity. Only what the upstream needs to negotiate content.
|
|
1005
|
+
const proxyHeaders = {
|
|
1006
|
+
host: "ip.net.coffee",
|
|
1007
|
+
accept: req.headers["accept"] || "*/*",
|
|
1008
|
+
"accept-language": req.headers["accept-language"] || "en",
|
|
1009
|
+
"accept-encoding": req.headers["accept-encoding"] || "gzip",
|
|
1010
|
+
"user-agent": "TokenTracker/IPCheck (https://www.tokentracker.cc)",
|
|
1011
|
+
referer: `${IP_CHECK_TARGET}${targetPath}`,
|
|
1012
|
+
};
|
|
1041
1013
|
|
|
1042
1014
|
const proxyRes = await fetch(targetUrl, {
|
|
1043
1015
|
method,
|
|
1044
1016
|
headers: proxyHeaders,
|
|
1045
|
-
body,
|
|
1046
1017
|
redirect: "manual",
|
|
1047
1018
|
});
|
|
1048
1019
|
|
|
@@ -1061,12 +1032,7 @@ function createLocalApiHandler({ queuePath, allowedHosts = [] } = {}) {
|
|
|
1061
1032
|
([k]) => !stripped.has(k.toLowerCase()),
|
|
1062
1033
|
);
|
|
1063
1034
|
|
|
1064
|
-
const
|
|
1065
|
-
let resBody = Buffer.from(await proxyRes.arrayBuffer());
|
|
1066
|
-
if (contentType.toLowerCase().includes("text/html")) {
|
|
1067
|
-
resBody = Buffer.from(rewriteIpCheckHtml(resBody.toString("utf8")), "utf8");
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1035
|
+
const resBody = Buffer.from(await proxyRes.arrayBuffer());
|
|
1070
1036
|
res.writeHead(proxyRes.status, Object.fromEntries(responseHeaders));
|
|
1071
1037
|
res.end(resBody);
|
|
1072
1038
|
} catch (e) {
|