diff --git a/src/dashboard/i18n/en.json b/src/dashboard/i18n/en.json
index 21d72627..d7ad044e 100644
--- a/src/dashboard/i18n/en.json
+++ b/src/dashboard/i18n/en.json
@@ -862,6 +862,7 @@
"ERR_IDTOKEN_REQUIRED": "ID token is required",
"ERR_PROXY_PRIVATE_IP": "Proxy cannot point to private/local address",
"ERR_PROXY_HTTP_ERROR": "Proxy returned HTTP error",
+ "ERR_PROXY_PRIVATE_HOST": "Private/local host is not allowed",
"ERR_CONNECTION_FAILED": "Connection failed",
"ERR_TIMEOUT": "Timeout (10s)",
"ERR_TLS_TUNNEL_ERROR": "TLS tunnel established but returned abnormal content",
diff --git a/src/dashboard/i18n/zh-CN.json b/src/dashboard/i18n/zh-CN.json
index 2e966cc5..24f19173 100644
--- a/src/dashboard/i18n/zh-CN.json
+++ b/src/dashboard/i18n/zh-CN.json
@@ -862,6 +862,7 @@
"ERR_IDTOKEN_REQUIRED": "缺少 idToken",
"ERR_PROXY_PRIVATE_IP": "代理地址不能指向内网/本机",
"ERR_PROXY_HTTP_ERROR": "代理返回 HTTP 错误",
+ "ERR_PROXY_PRIVATE_HOST": "内网/本地主机不允许",
"ERR_CONNECTION_FAILED": "连接失败",
"ERR_TIMEOUT": "超时(10s)",
"ERR_TLS_TUNNEL_ERROR": "TLS 隧道建立但返回内容异常",
diff --git a/src/dashboard/index.html b/src/dashboard/index.html
index 942b256e..20033ade 100644
--- a/src/dashboard/index.html
+++ b/src/dashboard/index.html
@@ -1521,8 +1521,8 @@
`);
return;
}
+ const errKey = r.error ? `error.${r.error}` : null;
+ const errMsg = (errKey && I18n.t(errKey) !== errKey) ? I18n.t(errKey) : (r.error || I18n.t('error.unknown'));
this.showWindsurfLoginResult(`
- ${this.esc(r.error || I18n.t('error.unknown'))}
${this.getWindsurfLoginFailActions(r)}
`);
+ ${this.esc(errMsg)}
${this.getWindsurfLoginFailActions(r)}
`);
},
renderBatchWindsurfLoginResult(results, autoAdd) {
@@ -2808,7 +2829,7 @@ 控制台登录
${r.success ? I18n.t('batch.success') : I18n.t('batch.fail')} |
${r.success
? `${this.esc(r.apiKey_masked || (r.apiKey ? r.apiKey.slice(0, 16) + '...' : '-'))}${r.account ? ` · ${I18n.t('table.header.account')} ${this.esc(r.account.id)}` : ''}`
- : `${this.esc(r.error || I18n.t('error.unknown'))}`} |
+ : (() => { const errKey = r.error ? `error.${r.error}` : null; return `${this.esc((errKey && I18n.t(errKey) !== errKey) ? I18n.t(errKey) : (r.error || I18n.t('error.unknown')))}`; })()}
`).join('')}
@@ -2880,11 +2901,13 @@ 控制台登录
} else {
entry.status = 'error: ' + (r.error || 'unknown');
this.renderSingleWindsurfLoginResult(r, autoAdd);
- this.toast(r.error || I18n.t('toast.loginFailed'), 'error');
+ const errKey = r.error ? `error.${r.error}` : null;
+ this.toast((errKey && I18n.t(errKey) !== errKey) ? I18n.t(errKey) : (r.error || I18n.t('toast.loginFailed')), 'error');
}
} catch (err) {
entry.status = 'error: ' + err.message;
- this.toast(err.message, 'error');
+ const errKey = err.message ? `error.${err.message}` : null;
+ this.toast((errKey && I18n.t(errKey) !== errKey) ? I18n.t(errKey) : err.message, 'error');
}
this.pushLoginHistory(entry);
@@ -2912,12 +2935,14 @@ 控制台登录
try {
const r = await this.api('POST', '/batch-import', { text: input, autoAdd });
if (!r.success || !Array.isArray(r.results)) {
+ const errKey = r.error ? `error.${r.error}` : null;
this.showWindsurfLoginResult(`
- ${this.esc(r.error || I18n.t('error.unknown'))}
`);
- return this.toast(r.error || I18n.t('batch.importFailed'), 'error');
+ ${this.esc((errKey && I18n.t(errKey) !== errKey) ? I18n.t(errKey) : (r.error || I18n.t('error.unknown')))}
`);
+ const translatedErr = errKey && I18n.t(errKey) !== errKey ? I18n.t(errKey) : r.error;
+ return this.toast(translatedErr || I18n.t('batch.importFailed'), 'error');
}
this.renderBatchWindsurfLoginResult(r.results, autoAdd);
@@ -2934,12 +2959,14 @@ 控制台登录
document.getElementById('wl-batch-input').value = '';
}
} catch (err) {
+ const errKey = err.message ? `error.${err.message}` : null;
this.showWindsurfLoginResult(`
- `);
- this.toast(err.message, 'error');
+ ${this.esc((errKey && I18n.t(errKey) !== errKey) ? I18n.t(errKey) : err.message)}
`);
+ const translatedErr = (errKey && I18n.t(errKey) !== errKey) ? I18n.t(errKey) : err.message;
+ this.toast(translatedErr, 'error');
} finally {
btn.disabled = false;
btn.innerHTML = ' ' + I18n.t('action.batchImport');
@@ -2956,7 +2983,7 @@ 控制台登录
${this.esc(h.email)} |
${ok ? I18n.t('batch.success') : I18n.t('batch.fail')}
- ${!ok ? ` ${this.esc((h.status||'').replace('error: ',''))} ` : ''}
+ ${!ok ? (() => { const errCode = (h.status||'').replace('error: ',''); const errKey = `error.${errCode}`; const errMsg = I18n.t(errKey) !== errKey ? I18n.t(errKey) : errCode; return `${this.esc(errMsg)} `; })() : ''}
|
${this.esc(h.proxy || '-')} |
|
diff --git a/src/net-safety.js b/src/net-safety.js
index 3332fc13..4984bc7c 100644
--- a/src/net-safety.js
+++ b/src/net-safety.js
@@ -88,9 +88,9 @@ export function isPrivateIp(address) {
export async function resolvePublicAddresses(hostname, lookupFn = dnsLookup) {
const host = String(hostname || '').replace(/^\[|\]$/g, '');
- if (!host || host.toLowerCase() === 'localhost') throw new Error('ERR_PRIVATE_HOST');
+ if (!host || host.toLowerCase() === 'localhost') throw new Error('ERR_PROXY_PRIVATE_HOST');
if (net.isIP(host)) {
- if (isPrivateIp(host)) throw new Error('ERR_PRIVATE_IP');
+ if (isPrivateIp(host)) throw new Error('ERR_PROXY_PRIVATE_IP');
return [{ address: host, family: net.isIP(host) }];
}
const result = await new Promise((resolve, reject) => {
@@ -98,7 +98,7 @@ export async function resolvePublicAddresses(hostname, lookupFn = dnsLookup) {
});
const addrs = Array.isArray(result) ? result : [result];
for (const a of addrs) {
- if (isPrivateIp(a.address)) throw new Error('ERR_PRIVATE_IP');
+ if (isPrivateIp(a.address)) throw new Error('ERR_PROXY_PRIVATE_IP');
}
return addrs;
}
diff --git a/test/ssrf.test.js b/test/ssrf.test.js
index f4b5f8eb..3c87c418 100644
--- a/test/ssrf.test.js
+++ b/test/ssrf.test.js
@@ -20,7 +20,7 @@ describe('SSRF private address detection', () => {
it('rejects hostnames after DNS resolution to private IPs', async () => {
const lookup = (host, opts, cb) => cb(null, [{ address: '127.0.0.1', family: 4 }]);
- await assert.rejects(() => resolvePublicAddresses('evil.example', lookup), /ERR_PRIVATE_IP/);
+ await assert.rejects(() => resolvePublicAddresses('evil.example', lookup), /ERR_PROXY_PRIVATE_IP/);
});
it('rejects oversized generic data URLs', () => {
@@ -28,4 +28,3 @@ describe('SSRF private address detection', () => {
assert.throws(() => parseGenericDataUrl(tooLarge), /Data URL exceeds/);
});
});
-