Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions src/services/profile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Host, PacScript, SimpleProxyServer } from "@/adapters";
import { deepClone } from "./utils";

export type ProxyAuthInfo = {
username: string;
Expand Down Expand Up @@ -103,7 +104,9 @@ export function onProfileUpdate(callback: (p: ProfilesStorage) => void) {
}

async function overwriteProfiles(profiles: ProfilesStorage) {
await Host.set(keyProfileStorage, profiles);
// Deep clone to remove any Proxy objects before saving
const clonedProfiles = deepClone(profiles);
await Host.set(keyProfileStorage, clonedProfiles);
onProfileUpdateListeners.map((cb) => cb(profiles));
}

Expand All @@ -115,14 +118,16 @@ async function overwriteProfiles(profiles: ProfilesStorage) {
*/
export async function saveProfile(profile: ProxyProfile) {
const data = await listProfiles();
data[profile.profileID] = profile;
// Deep clone the profile to remove any Proxy objects before saving
data[profile.profileID] = deepClone(profile);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

This line is vulnerable to Prototype Pollution due to the use of user-controlled profile.profileID as a key in data. An attacker could use "__proto__" to inject properties into Object.prototype, which is then persistently saved. Furthermore, the deepClone call here is redundant, as the overwriteProfiles function, which is called subsequently, already performs a deep clone on the entire profiles object before saving.

  if (profile.profileID !== '__proto__') {
    data[profile.profileID] = deepClone(profile);
  }

await overwriteProfiles(data);
}

export async function saveManyProfiles(profiles: ProxyProfile[]) {
let data = await listProfiles();
profiles.forEach((p) => {
data[p.profileID] = p;
// Deep clone each profile to remove any Proxy objects before saving
data[p.profileID] = deepClone(p);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Similar to the saveProfile function, this line is vulnerable to Prototype Pollution if p.profileID is "__proto__". Additionally, the deepClone call within this forEach loop is redundant, as overwriteProfiles handles cloning the entire data structure.

    if (p.profileID !== '__proto__') {
      data[p.profileID] = deepClone(p);
    }

});
await overwriteProfiles(data);
}
Expand Down
4 changes: 3 additions & 1 deletion src/services/proxy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { ProxySettingResultDetails } from "@/adapters";
import { ProfileConverter } from "./profile2config";
import { ProfileAuthProvider } from "./auth";
import { deepClone } from "../utils";

export type ProxySetting = {
activeProfile?: ProxyProfile;
Expand Down Expand Up @@ -56,7 +57,8 @@ export async function setProxy(val: ProxyProfile) {
break;
}

await Host.set<ProxyProfile>(keyActiveProfile, val);
// Deep clone to remove any Proxy objects before saving
await Host.set<ProxyProfile>(keyActiveProfile, deepClone(val));
}

/**
Expand Down
8 changes: 8 additions & 0 deletions src/services/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Deep clone an object to remove all Proxy objects (e.g., from Vue reactivity).
* This is necessary because chrome.storage and browser.storage use structured clone
* which cannot clone Proxy objects.
*/
export function deepClone<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
Comment on lines +6 to +8

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using JSON.parse(JSON.stringify(obj)) is a concise way to deep clone an object and remove Vue's reactivity proxies. However, this method has important limitations and can lead to data loss or type changes for non-JSON-serializable data (e.g., Date objects, undefined, functions) and will throw an error on circular references. While it works for the current ProxyProfile data structures, this fragility could lead to bugs if the data models evolve. Please consider adding a comment detailing these limitations for future maintainers.