-
Notifications
You must be signed in to change notification settings - Fork 78
Expand file tree
/
Copy pathchromiumFlags.ts
More file actions
177 lines (165 loc) · 5.73 KB
/
Copy pathchromiumFlags.ts
File metadata and controls
177 lines (165 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import type { ChromiumFlagsType } from '@irdashies/types';
import { DEFAULT_CHROMIUM_FLAGS } from '@irdashies/types';
import logger from '../logger';
import { readData, writeData } from './storage';
const STORAGE_KEY = 'chromiumFlags';
const ANGLE_BACKENDS: ReadonlySet<
NonNullable<ChromiumFlagsType['angleBackend']>
> = new Set(['default', 'gl', 'd3d11', 'd3d9', 'metal', 'vulkan']);
/**
* Allowlist of Chromium switch names accepted in the `customSwitches` field.
*
* Anything outside this set is silently dropped at save/load time with a
* warning logged. The list is intentionally narrow and focused on GPU,
* rendering, scaling, and power switches that have a legitimate diagnostic
* use. Switches that affect network behaviour, debugger exposure, JS engine
* tuning, Chrome extensions, profile directories, or the same-origin policy
* are deliberately excluded — they are the renderer-compromise attack surface
* called out as finding S1 in docs/ARCHITECTURE_REVIEW.md.
*
* Add entries to this list only after confirming the switch cannot be abused
* to weaken the security posture of the app on next launch.
*/
const CUSTOM_SWITCH_ALLOWLIST: ReadonlySet<string> = new Set([
// GPU control
'use-gl',
'use-angle',
'disable-gpu',
'disable-gpu-compositing',
'disable-gpu-driver-bug-workarounds',
'disable-software-rasterizer',
'ignore-gpu-blocklist',
'ignore-gpu-blacklist', // deprecated alias still accepted by Chromium
// Rasterization
'enable-gpu-rasterization',
'disable-gpu-rasterization',
'force-gpu-rasterization',
'enable-zero-copy',
'disable-zero-copy',
// VSync / frame rate
'disable-gpu-vsync',
'disable-frame-rate-limit',
// Color / scaling
'force-color-profile',
'force-device-scale-factor',
'high-dpi-support',
// Compositing
'disable-direct-composition',
// Locale
'lang',
// Power / backgrounding
'disable-renderer-backgrounding',
'disable-background-timer-throttling',
'disable-backgrounding-occluded-windows',
// High-performance hints (both syntaxes seen in the wild)
'force_high_performance_gpu',
'force-high-performance-gpu',
// Safe diagnostic logging
'enable-logging',
'log-level',
'v',
'vmodule',
]);
export interface CustomSwitchEntry {
name: string;
value?: string;
}
/**
* Parses the freeform `customSwitches` text into structured entries.
*
* Each non-empty, non-comment line is `switch-name` or `switch-name=value`.
* A leading `--` is tolerated and stripped. Blank lines and lines starting
* with `#` are ignored. This is the single source of truth for the parse —
* both storage filtering and overlayManager application route through here.
*/
export function parseCustomSwitches(text: string): CustomSwitchEntry[] {
if (!text) return [];
const entries: CustomSwitchEntry[] = [];
for (const rawLine of text.split(/\r?\n/)) {
const line = rawLine.trim().replace(/^--/, '');
if (!line || line.startsWith('#')) continue;
const eq = line.indexOf('=');
if (eq === -1) {
entries.push({ name: line });
} else {
const name = line.slice(0, eq).trim();
const value = line.slice(eq + 1).trim();
if (name) entries.push({ name, value });
}
}
return entries;
}
/**
* Filters the freeform `customSwitches` text against the allowlist. Returns
* the sanitized string (only allowed entries, one per line) and the list of
* dropped switch names. Caller is expected to log the dropped names.
*
* Comments are deliberately not preserved: the persisted form is the result
* of a save round-trip, and round-tripping is expected to normalise. Users
* can still write comments — they are stripped only at save time, not from
* the UI form state.
*/
export function filterCustomSwitches(text: string): {
sanitized: string;
dropped: string[];
} {
const entries = parseCustomSwitches(text);
const kept: CustomSwitchEntry[] = [];
const dropped: string[] = [];
for (const entry of entries) {
if (CUSTOM_SWITCH_ALLOWLIST.has(entry.name)) {
kept.push(entry);
} else {
dropped.push(entry.name);
}
}
const sanitized = kept
.map((e) => (e.value === undefined ? e.name : `${e.name}=${e.value}`))
.join('\n');
return { sanitized, dropped };
}
function toStringArray(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value
.filter((v): v is string => typeof v === 'string')
.map((v) => v.trim())
.filter(Boolean);
}
function normalizeFlags(input: unknown): ChromiumFlagsType {
const src = (input && typeof input === 'object' ? input : {}) as Record<
string,
unknown
>;
const angleBackend = ANGLE_BACKENDS.has(
src.angleBackend as NonNullable<ChromiumFlagsType['angleBackend']>
)
? (src.angleBackend as ChromiumFlagsType['angleBackend'])
: 'default';
const rawCustomSwitches =
typeof src.customSwitches === 'string' ? src.customSwitches : '';
const { sanitized: customSwitches, dropped } =
filterCustomSwitches(rawCustomSwitches);
if (dropped.length > 0) {
logger.warn(
'[chromiumFlags] Dropped custom switches not in allowlist:',
dropped
);
}
return {
disableNativeWinOcclusion: src.disableNativeWinOcclusion === true,
angleBackend,
disableDirectComposition: src.disableDirectComposition === true,
disableFeatures: toStringArray(src.disableFeatures),
enableFeatures: toStringArray(src.enableFeatures),
customSwitches,
};
}
export function getChromiumFlags(): ChromiumFlagsType {
const stored = readData<unknown>(STORAGE_KEY);
return { ...DEFAULT_CHROMIUM_FLAGS, ...normalizeFlags(stored) };
}
export function saveChromiumFlags(flags: ChromiumFlagsType): ChromiumFlagsType {
const merged = { ...DEFAULT_CHROMIUM_FLAGS, ...normalizeFlags(flags) };
writeData(STORAGE_KEY, merged);
return merged;
}