fix(security): harden auth and request limits

This commit is contained in:
shuaiplus
2026-07-05 23:43:49 +08:00
parent 12af18e3a3
commit d9a36fefe6
12 changed files with 315 additions and 30 deletions
+50 -6
View File
@@ -227,6 +227,50 @@
return out;
}
function trustedParentOrigin() {
var parent = decodeRepeated(params.get("parent"));
if (!parent) return "";
try {
var parentUrl = new URL(parent);
if (
parentUrl.protocol === "chrome-extension:" ||
parentUrl.protocol === "moz-extension:" ||
parentUrl.protocol === "safari-web-extension:"
) {
return parentUrl.protocol + "//" + parentUrl.host;
}
if (parentUrl.origin === window.location.origin) {
return parentUrl.origin;
}
} catch (_error) {
return "";
}
return "";
}
function safeShallowCopy(source) {
var copy = {};
if (!source || typeof source !== "object") return copy;
Object.keys(source).forEach(function (key) {
if (key === "__proto__" || key === "prototype" || key === "constructor") return;
copy[key] = source[key];
});
return copy;
}
function postResult(message) {
var parentOrigin = trustedParentOrigin();
if (parentOrigin) {
if (window.opener && !window.opener.closed) {
window.opener.postMessage(message, parentOrigin);
}
if (window.parent && window.parent !== window) {
window.parent.postMessage(message, parentOrigin);
}
}
window.postMessage(message, window.location.origin);
}
function showMessage(kind, message) {
msgEl.textContent = String(message || "");
msgEl.className = "msg show " + kind;
@@ -279,13 +323,13 @@
function normalizeOptions(options) {
if (!options || typeof options !== "object") throw new Error("Cannot parse data.");
var copy = Object.assign({}, options);
var copy = safeShallowCopy(options);
copy.challenge = bytesFromBase64Url(copy.challenge);
if (Array.isArray(copy.allowCredentials)) {
copy.allowCredentials = copy.allowCredentials.map(function (credential) {
return Object.assign({}, credential, {
id: bytesFromBase64Url(credential.id),
});
var next = safeShallowCopy(credential);
next.id = bytesFromBase64Url(credential && credential.id);
return next;
});
}
return copy;
@@ -327,11 +371,11 @@
if (!(credential instanceof PublicKeyCredential)) {
throw new Error("No security key was selected.");
}
window.postMessage({
postResult({
command: "webAuthnResult",
data: credentialToDataString(credential),
remember: rememberEl.checked,
}, "*");
});
sentSuccess = true;
showMessage("success", text.success);
} catch (error) {
+9 -7
View File
@@ -94,6 +94,14 @@ export function loadSession(): SessionState | null {
const raw = localStorage.getItem(SESSION_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw) as Partial<SessionState> & Partial<PersistedSessionState>;
if (parsed.email && (parsed.accessToken || parsed.refreshToken)) {
const authMode = parsed.authMode === 'web-cookie' ? 'web-cookie' : 'token';
saveSession({ email: parsed.email, authMode });
return {
email: parsed.email,
authMode,
};
}
if (parsed.authMode === 'web-cookie' && parsed.email) {
return {
email: parsed.email,
@@ -106,13 +114,7 @@ export function loadSession(): SessionState | null {
authMode: 'token',
};
}
if (!parsed.accessToken || !parsed.refreshToken || !parsed.email) return null;
return {
accessToken: parsed.accessToken,
refreshToken: parsed.refreshToken,
email: parsed.email,
authMode: 'token',
};
return null;
} catch {
return null;
}
+24 -2
View File
@@ -19,6 +19,28 @@ const VAULT_CORE_STORE = 'vault-core';
let dbPromise: Promise<IDBDatabase | null> | null = null;
function stripDecryptedCacheFields<T>(value: T): T {
if (Array.isArray(value)) {
return value.map((item) => stripDecryptedCacheFields(item)) as T;
}
if (!value || typeof value !== 'object') return value;
const source = value as Record<string, unknown>;
const out: Record<string, unknown> = {};
for (const [key, item] of Object.entries(source)) {
if (/^dec[A-Z]/.test(key) || key === 'shareUrl') continue;
out[key] = stripDecryptedCacheFields(item);
}
return out as T;
}
function sanitizeSnapshotForCache(snapshot: VaultCoreSnapshot): VaultCoreSnapshot {
return {
ciphers: stripDecryptedCacheFields(Array.isArray(snapshot.ciphers) ? snapshot.ciphers : []),
folders: stripDecryptedCacheFields(Array.isArray(snapshot.folders) ? snapshot.folders : []),
sends: stripDecryptedCacheFields(Array.isArray(snapshot.sends) ? snapshot.sends : []),
};
}
function supportsIndexedDb(): boolean {
return typeof indexedDB !== 'undefined';
}
@@ -72,7 +94,7 @@ export async function loadCachedVaultCoreSnapshot(cacheKey: string): Promise<Vau
const request = store.get(normalized);
request.onsuccess = () => {
const record = request.result as VaultCoreCacheRecord | undefined;
resolve(record || null);
resolve(record ? { ...record, snapshot: sanitizeSnapshotForCache(record.snapshot) } : null);
};
request.onerror = () => resolve(null);
}));
@@ -90,7 +112,7 @@ export async function saveCachedVaultCoreSnapshot(
cacheKey: normalized,
revisionStamp,
savedAt: Date.now(),
snapshot,
snapshot: sanitizeSnapshotForCache(snapshot),
};
const request = store.put(record);
request.onsuccess = () => resolve();