1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub enum RiskLevel {
19 Low,
22 Medium,
26 High,
29 Critical,
34}
35
36impl RiskLevel {
37 pub fn label(self) -> &'static str {
45 match self {
46 RiskLevel::Low => "LOW",
47 RiskLevel::Medium => "MEDIUM",
48 RiskLevel::High => "HIGH",
49 RiskLevel::Critical => "CRITICAL",
50 }
51 }
52
53 pub fn summary(self) -> &'static str {
55 match self {
56 RiskLevel::Low => "Low risk: scoped or sandboxed access with no broad reach.",
57 RiskLevel::Medium => {
58 "Medium risk: meaningful access to a category of user data or a named site."
59 }
60 RiskLevel::High => {
61 "High risk: broad, persistent cross-origin or sensitive-system access."
62 }
63 RiskLevel::Critical => {
64 "Critical risk: effectively total control of pages, sessions, or the machine."
65 }
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct PermissionEntry {
74 pub token: &'static str,
76 pub level: RiskLevel,
78 pub description: &'static str,
80}
81
82#[rustfmt::skip]
89pub const RISK_DATABASE: &[PermissionEntry] = &[
90 PermissionEntry {
94 token: "activeTab",
95 level: RiskLevel::Low,
96 description: "Grants temporary access to the current tab only when the user \
97 explicitly clicks the extension action. No persistent background \
98 access to any site.",
99 },
100 PermissionEntry {
101 token: "contextMenus",
102 level: RiskLevel::Low,
103 description: "Adds items to the browser's right-click context menu. No page \
104 content access by itself.",
105 },
106 PermissionEntry {
107 token: "storage",
108 level: RiskLevel::Low,
109 description: "Stores extension data locally (synced/local). Confined to the \
110 extension's own sandbox; cannot read site data.",
111 },
112 PermissionEntry {
113 token: "alarms",
114 level: RiskLevel::Low,
115 description: "Schedules code to run at a future time. No content access by \
116 itself.",
117 },
118 PermissionEntry {
119 token: "idle",
120 level: RiskLevel::Low,
121 description: "Detects when the machine is idle or locked. No page content \
122 access.",
123 },
124 PermissionEntry {
125 token: "notifications",
126 level: RiskLevel::Low,
127 description: "Shows desktop notifications. No page content access.",
128 },
129 PermissionEntry {
130 token: "offscreen",
131 level: RiskLevel::Low,
132 description: "Creates offscreen documents for DOM work without a visible page. \
133 No extra site access by itself.",
134 },
135 PermissionEntry {
136 token: "power",
137 level: RiskLevel::Low,
138 description: "Keeps the screen or system awake. No content access.",
139 },
140 PermissionEntry {
141 token: "sidePanel",
142 level: RiskLevel::Low,
143 description: "Shows content in the browser side panel. No extra host access by \
144 itself.",
145 },
146 PermissionEntry {
147 token: "tts",
148 level: RiskLevel::Low,
149 description: "Text-to-speech output. No content access.",
150 },
151 PermissionEntry {
152 token: "unlimitedStorage",
153 level: RiskLevel::Low,
154 description: "Removes the extension storage quota. No extra site access.",
155 },
156 PermissionEntry {
157 token: "userScripts",
158 level: RiskLevel::Low,
159 description: "Registers user-authored scripts (the MV3 user-script API). \
160 Powerful if a user installs a hostile script, but grants no \
161 host access the user did not already consent to.",
162 },
163 PermissionEntry {
164 token: "declarativeNetRequest",
165 level: RiskLevel::Low,
166 description: "Blocks or modifies network requests via static rules (the MV3 \
167 content-blocker path). Less powerful than webRequest blocking, \
168 but can still read request URLs.",
169 },
170 PermissionEntry {
171 token: "declarativeNetRequestFeedback",
172 level: RiskLevel::Low,
173 description: "Observes which declarativeNetRequest rules matched. No extra \
174 host access.",
175 },
176 PermissionEntry {
177 token: "gcm",
178 level: RiskLevel::Low,
179 description: "Receives push messages via Google Cloud Messaging. No content \
180 access.",
181 },
182 PermissionEntry {
183 token: "action",
184 level: RiskLevel::Low,
185 description: "Configures the extension's toolbar action icon. No host access.",
186 },
187 PermissionEntry {
188 token: "favicon",
189 level: RiskLevel::Low,
190 description: "Reads favicons for URLs. No page content access.",
191 },
192 PermissionEntry {
193 token: "declarativeContent",
194 level: RiskLevel::Low,
195 description: "Reacts to page URL/CSS state via declarative rules. No arbitrary \
196 script execution.",
197 },
198
199 PermissionEntry {
203 token: "bookmarks",
204 level: RiskLevel::Medium,
205 description: "Reads and modifies the user's full bookmark tree.",
206 },
207 PermissionEntry {
208 token: "history",
209 level: RiskLevel::Medium,
210 description: "Reads and clears the user's full browsing history.",
211 },
212 PermissionEntry {
213 token: "downloads",
214 level: RiskLevel::Medium,
215 description: "Initiates, monitors, and opens downloads; can open arbitrary \
216 files from the download shelf.",
217 },
218 PermissionEntry {
219 token: "downloads.open",
220 level: RiskLevel::Medium,
221 description: "Opens downloaded files on disk. Combined with a hostile download \
222 this can execute local content.",
223 },
224 PermissionEntry {
225 token: "downloads.shelf",
226 level: RiskLevel::Medium,
227 description: "Hides or shows the download shelf; can mask a stealthy \
228 download.",
229 },
230 PermissionEntry {
231 token: "downloads.ui",
232 level: RiskLevel::Medium,
233 description: "Controls the downloads UI surface.",
234 },
235 PermissionEntry {
236 token: "geolocation",
237 level: RiskLevel::Medium,
238 description: "Reads the user's GPS / IP-derived location (subject to a per-site \
239 permission prompt).",
240 },
241 PermissionEntry {
242 token: "clipboardWrite",
243 level: RiskLevel::Medium,
244 description: "Writes to the system clipboard. Can overwrite a copied password \
245 or inject pasted content.",
246 },
247 PermissionEntry {
248 token: "clipboardRead",
249 level: RiskLevel::Medium,
250 description: "Reads the system clipboard, which frequently contains copied \
251 passwords, tokens, or private text.",
252 },
253 PermissionEntry {
254 token: "identity",
255 level: RiskLevel::Medium,
256 description: "Triggers OAuth sign-in and obtains the user's signed-in account \
257 email / profile and an auth token.",
258 },
259 PermissionEntry {
260 token: "identity.email",
261 level: RiskLevel::Medium,
262 description: "Returns the user's signed-in email address directly.",
263 },
264 PermissionEntry {
265 token: "management",
266 level: RiskLevel::Medium,
267 description: "Lists, enables, disables, and uninstalls other installed \
268 extensions.",
269 },
270 PermissionEntry {
271 token: "tabs",
272 level: RiskLevel::Medium,
273 description: "Reads the URL and title of every open tab and receives tab-update \
274 events. Effectively full browsing-session visibility.",
275 },
276 PermissionEntry {
277 token: "tabGroups",
278 level: RiskLevel::Medium,
279 description: "Reads and modifies tab groups, exposing which sites the user \
280 clusters together.",
281 },
282 PermissionEntry {
283 token: "topSites",
284 level: RiskLevel::Medium,
285 description: "Reads the user's most-visited sites (the new-tab shortcuts).",
286 },
287 PermissionEntry {
288 token: "sessions",
289 level: RiskLevel::Medium,
290 description: "Reads recently closed tabs and windows across devices.",
291 },
292 PermissionEntry {
293 token: "pageCapture",
294 level: RiskLevel::Medium,
295 description: "Saves the current page as an MHTML archive, capturing rendered \
296 content.",
297 },
298 PermissionEntry {
299 token: "search",
300 level: RiskLevel::Medium,
301 description: "Sets the default search provider and issues queries.",
302 },
303 PermissionEntry {
304 token: "browsingData",
305 level: RiskLevel::Medium,
306 description: "Clears cookies, cache, history, and other browsing data — can \
307 wipe a user's session.",
308 },
309 PermissionEntry {
310 token: "fontSettings",
311 level: RiskLevel::Medium,
312 description: "Changes browser font settings.",
313 },
314 PermissionEntry {
315 token: "readingList",
316 level: RiskLevel::Medium,
317 description: "Reads and modifies the user's reading list.",
318 },
319
320 PermissionEntry {
324 token: "cookies",
325 level: RiskLevel::High,
326 description: "Reads and modifies all cookies for any site the extension has \
327 host access to, including session and auth cookies.",
328 },
329 PermissionEntry {
330 token: "webRequest",
331 level: RiskLevel::High,
332 description: "Observes (and in MV2 could block) every network request and \
333 response, exposing full URLs, headers, and bodies including \
334 credentials.",
335 },
336 PermissionEntry {
337 token: "webRequestBlocking",
338 level: RiskLevel::High,
339 description: "MV2-only blocking webRequest — full request interception and \
340 modification. Removed from MV3.",
341 },
342 PermissionEntry {
343 token: "debugger",
344 level: RiskLevel::High,
345 description: "Attaches the Chrome DevTools Protocol to a tab, giving full DOM, \
346 network, and JS execution control — effectively total control \
347 of the page.",
348 },
349 PermissionEntry {
350 token: "nativeMessaging",
351 level: RiskLevel::High,
352 description: "Talks to a native application installed on the user's machine. \
353 Escapes the browser sandbox entirely.",
354 },
355 PermissionEntry {
356 token: "fileSystem",
357 level: RiskLevel::High,
358 description: "Reads and writes files outside the browser sandbox (where granted \
359 by the platform).",
360 },
361 PermissionEntry {
362 token: "fileBrowserHandler",
363 level: RiskLevel::High,
364 description: "Reads and writes files via the ChromeOS file browser.",
365 },
366 PermissionEntry {
367 token: "proxy",
368 level: RiskLevel::High,
369 description: "Configures the browser's proxy settings. A hostile extension can \
370 redirect all traffic through an attacker-controlled server.",
371 },
372 PermissionEntry {
373 token: "privacy",
374 level: RiskLevel::High,
375 description: "Reads and changes privacy-related browser settings (do-not-track, \
376 third-party cookies, hyperlink auditing).",
377 },
378 PermissionEntry {
379 token: "system.cpu",
380 level: RiskLevel::High,
381 description: "Reads detailed CPU metadata useful for device fingerprinting.",
382 },
383 PermissionEntry {
384 token: "system.memory",
385 level: RiskLevel::High,
386 description: "Reads physical memory capacity, a device-fingerprinting signal.",
387 },
388 PermissionEntry {
389 token: "system.storage",
390 level: RiskLevel::High,
391 description: "Reads attached storage device metadata and can eject devices.",
392 },
393 PermissionEntry {
394 token: "system.network",
395 level: RiskLevel::High,
396 description: "Reads network interface metadata exposing the local network \
397 topology.",
398 },
399 PermissionEntry {
400 token: "system.display",
401 level: RiskLevel::High,
402 description: "Reads display metadata (resolution, DPI) usable for \
403 fingerprinting.",
404 },
405 PermissionEntry {
406 token: "system.audio",
407 level: RiskLevel::High,
408 description: "Reads audio device metadata.",
409 },
410 PermissionEntry {
411 token: "vpnProvider",
412 level: RiskLevel::High,
413 description: "Configures a VPN (ChromeOS), which can redirect and intercept \
414 all network traffic.",
415 },
416 PermissionEntry {
417 token: "enterprise.networkingAttributes",
418 level: RiskLevel::High,
419 description: "Reads detailed network attributes (ChromeOS enterprise), exposing \
420 internal network identity.",
421 },
422 PermissionEntry {
423 token: "enterprise.deviceAttributes",
424 level: RiskLevel::High,
425 description: "Reads ChromeOS enterprise device identity attributes.",
426 },
427 PermissionEntry {
428 token: "webNavigation",
429 level: RiskLevel::High,
430 description: "Receives the full navigation events of every frame in every tab, \
431 including the URL of every frame and redirect.",
432 },
433 PermissionEntry {
434 token: "scripting",
435 level: RiskLevel::High,
436 description: "Injects arbitrary JavaScript into pages for which the extension \
437 has host permission — full page control at runtime (MV3 \
438 successor to tabs.executeScript).",
439 },
440 PermissionEntry {
441 token: "contentSettings",
442 level: RiskLevel::High,
443 description: "Reads and modifies per-site content settings (cookies, \
444 javascript, plugins, mic, camera) for every origin.",
445 },
446 PermissionEntry {
447 token: "usbDevices",
448 level: RiskLevel::High,
449 description: "Accesses listed USB devices directly, bypassing the page.",
450 },
451 PermissionEntry {
452 token: "serial",
453 level: RiskLevel::High,
454 description: "Reads and writes to serial ports.",
455 },
456 PermissionEntry {
457 token: "bluetoothLowEnergy",
458 level: RiskLevel::High,
459 description: "Discovers and talks to BLE devices.",
460 },
461 PermissionEntry {
462 token: "hid",
463 level: RiskLevel::High,
464 description: "Talks to raw HID devices (keyboards, security keys).",
465 },
466 PermissionEntry {
467 token: "audio",
468 level: RiskLevel::High,
469 description: "Captures and renders audio from / to the system.",
470 },
471 PermissionEntry {
472 token: "audioModem",
473 level: RiskLevel::High,
474 description: "Encodes/decodes data over audio (ultrasonic pairing).",
475 },
476 PermissionEntry {
477 token: "bluetooth",
478 level: RiskLevel::High,
479 description: "Discovers and connects to Bluetooth devices.",
480 },
481 PermissionEntry {
482 token: "mdns",
483 level: RiskLevel::High,
484 description: "Discovers services on the local network via mDNS.",
485 },
486 PermissionEntry {
487 token: "platformInfo",
488 level: RiskLevel::High,
489 description: "Reads OS / arch / platform metadata for fingerprinting.",
490 },
491 PermissionEntry {
492 token: "processes",
493 level: RiskLevel::High,
494 description: "Observes browser process metadata and CPU usage per tab.",
495 },
496 PermissionEntry {
497 token: "networking.onc",
498 level: RiskLevel::High,
499 description: "Configures ChromeOS network connections (Open Network \
500 Configuration).",
501 },
502 PermissionEntry {
503 token: "networking.config",
504 level: RiskLevel::High,
505 description: "Configures network credentials on ChromeOS.",
506 },
507 PermissionEntry {
508 token: "documentScan",
509 level: RiskLevel::High,
510 description: "Reads from attached document scanners.",
511 },
512 PermissionEntry {
513 token: "accessibilityFeatures.modify",
514 level: RiskLevel::High,
515 description: "Modifies accessibility settings (can be abused to alter input \
516 handling).",
517 },
518 PermissionEntry {
519 token: "input",
520 level: RiskLevel::High,
521 description: "ChromeOS IME input — can observe keystrokes.",
522 },
523 PermissionEntry {
524 token: "languageSettings",
525 level: RiskLevel::High,
526 description: "Reads and changes the user's language settings.",
527 },
528 PermissionEntry {
529 token: "wallpaper",
530 level: RiskLevel::High,
531 description: "Sets the ChromeOS wallpaper.",
532 },
533 PermissionEntry {
534 token: "enterprise.hardwarePlatform",
535 level: RiskLevel::High,
536 description: "Reads hardware platform identity (enterprise).",
537 },
538 PermissionEntry {
539 token: "clipboard",
540 level: RiskLevel::High,
541 description: "Reads and writes the system clipboard (combined read+write).",
542 },
543
544 PermissionEntry {
548 token: "<all_urls>",
549 level: RiskLevel::Critical,
550 description: "Requests host access to every site on the web. Combined with \
551 content scripts or scripting this means any page's content, \
552 forms, and credentials can be read and modified at will — the \
553 canonical spyware grant.",
554 },
555 PermissionEntry {
556 token: "*://*/*",
557 level: RiskLevel::Critical,
558 description: "Match pattern granting host access to every http/https URL. \
559 Equivalent in effect to <all_urls> for normal browsing.",
560 },
561 PermissionEntry {
562 token: "http://*/*",
563 level: RiskLevel::Critical,
564 description: "Host access to every plain-http URL, including login pages and \
565 intranet sites.",
566 },
567 PermissionEntry {
568 token: "https://*/*",
569 level: RiskLevel::Critical,
570 description: "Host access to every secure URL on the web.",
571 },
572 PermissionEntry {
573 token: "*://*",
574 level: RiskLevel::Critical,
575 description: "Truncated but still blanket http/https host access.",
576 },
577 PermissionEntry {
578 token: "file:///*",
579 level: RiskLevel::Critical,
580 description: "Host access to local files via the file:// scheme, subject to \
581 the 'allow access to file URLs' toggle. Reads files on disk.",
582 },
583 PermissionEntry {
584 token: "urn:*",
585 level: RiskLevel::Critical,
586 description: "Host access to URN resources (broad scheme grant).",
587 },
588];
589
590pub fn find_permission(token: &str) -> Option<&'static PermissionEntry> {
604 RISK_DATABASE.iter().find(|e| e.token == token)
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610
611 #[test]
612 fn database_is_comprehensive() {
613 assert!(RISK_DATABASE.len() >= 60, "only {} entries", RISK_DATABASE.len());
616 let crit = RISK_DATABASE.iter().filter(|e| e.level == RiskLevel::Critical).count();
617 let high = RISK_DATABASE.iter().filter(|e| e.level == RiskLevel::High).count();
618 let med = RISK_DATABASE.iter().filter(|e| e.level == RiskLevel::Medium).count();
619 let low = RISK_DATABASE.iter().filter(|e| e.level == RiskLevel::Low).count();
620 assert!(crit >= 5, "need >=5 Critical entries, got {crit}");
621 assert!(high >= 25, "need >=25 High entries, got {high}");
622 assert!(med >= 12, "need >=12 Medium entries, got {med}");
623 assert!(low >= 12, "need >=12 Low entries, got {low}");
624 }
625
626 #[test]
627 fn every_entry_round_trips_through_lookup() {
628 for entry in RISK_DATABASE {
629 let found = find_permission(entry.token);
630 assert!(found.is_some(), "{:?} not found", entry.token);
631 assert_eq!(found.unwrap().level, entry.level);
632 assert!(entry.description.len() >= 25, "thin description for {:?}", entry.token);
633 }
634 }
635
636 #[test]
637 fn canonical_high_risk_tokens_present() {
638 for tok in ["cookies", "webRequest", "debugger", "nativeMessaging", "scripting", "webNavigation"] {
639 assert!(find_permission(tok).is_some(), "missing {tok}");
640 }
641 }
642
643 #[test]
644 fn canonical_critical_tokens_present() {
645 for tok in ["<all_urls>", "*://*/*", "https://*/*", "file:///*"] {
646 assert!(find_permission(tok).is_some(), "missing {tok}");
647 }
648 }
649
650 #[test]
651 fn unknown_token_is_none_not_critical() {
652 assert!(find_permission("totally-fake-xyz").is_none());
654 assert!(find_permission("").is_none());
655 }
656
657 #[test]
658 fn risk_level_ordering() {
659 assert!(RiskLevel::Low < RiskLevel::Medium);
660 assert!(RiskLevel::Medium < RiskLevel::High);
661 assert!(RiskLevel::High < RiskLevel::Critical);
662 }
663
664 #[test]
665 fn labels_and_summaries() {
666 for lvl in [RiskLevel::Low, RiskLevel::Medium, RiskLevel::High, RiskLevel::Critical] {
667 assert!(!lvl.label().is_empty());
668 assert!(lvl.summary().len() > 20);
669 }
670 }
671}