# permission-auditor
[](https://crates.io/crates/permission-auditor)
[](https://docs.rs/permission-auditor)
Audit a list of Chrome / **Manifest V3** extension permissions against a curated
risk database and produce a structured per-extension report:
- a **risk level** (`Low` / `Medium` / `High` / `Critical`) for each permission,
plus a short plain-English description of what it grants,
- recognition for **host-access patterns** (`<all_urls>`, scheme wildcards,
scoped match-patterns) and broad-vs-scoped classification,
- an **overall verdict** for the whole extension, with the count of each
severity and escalation to `Critical` for the canonical spyware capability
set (arbitrary host access + code injection / cookie access).
Pure Rust, **zero dependencies**, `#![forbid(unsafe_code)]`, fully tested.
This is the audit engine behind the [**zovo.one**](https://zovo.one/)
Chrome-extension privacy & security scanner. It is a more comprehensive
companion to [`ext-permission-risk`](https://crates.io/crates/ext-permission-risk):
it covers the full MV3 permission surface, adds a `Critical` tier for the truly
dangerous combinations, and returns a structured report rather than a single
lookup.
## Why audit permissions
The permission list in an extension's `manifest.json` is the single best
signal for whether it is safe to install. Two extensions that both "just
change your new tab page" can request wildly different access: one asks for
`storage`, the other for `<all_urls>` + `cookies` + `scripting`. The latter
can read every password you type, on every site, forever. This crate turns
that signal into a labelled, countable, explainable report — the input a
scanner UI renders to a user before they install.
## Install
```toml
[dependencies]
permission-auditor = "0.1"
```
## Quick start
```rust
use permission_auditor::{audit, RiskLevel};
// A canonical surveillance-set manifest.
let report = audit(&[
"activeTab",
"storage",
"tabs",
"<all_urls>",
"scripting",
"cookies",
]);
// activeTab + storage are Low; tabs is Medium; <all_urls> is Critical;
// scripting + cookies are High. The broad-host + code combo escalates to Critical.
assert_eq!(report.overall, RiskLevel::Critical);
assert!(report.critical_count >= 1);
assert_eq!(report.findings.len(), 6);
println!("{}", report.summary());
// "CRITICAL overall (1 critical, 2 high, 1 medium, 2 low)"
// The "what should I worry about" view.
let worry = report.findings_at_or_above(RiskLevel::High);
A benign manifest stays Low:
```rust
use permission_auditor::{audit, RiskLevel};
let r = audit(&["activeTab", "storage", "sidePanel"]);
assert_eq!(r.overall, RiskLevel::Low);
```
## API surface
| `audit(permissions)` | Audit an iterable of permission tokens into an `AuditReport`. |
| `audit_with_manifest_version(permissions, mv)` | Same, recording the manifest version on the report. |
| `AuditReport` | `{ findings, overall, critical_count, high_count, medium_count, low_count, manifest_version }`. |
| `AuditReport::summary()` | One-line `CRITICAL overall (1 critical, 2 high, ...)` string. |
| `AuditReport::findings_at_or_above(level)` | Findings at or above a severity, for a "what to worry about" view. |
| `Finding` | `{ token, level, description, kind }` — one audited permission. |
| `FindingKind` | `Known` (in DB) / `HostPattern` (synthesised) / `Unknown`. |
| `RiskLevel` | `Low` / `Medium` / `High` / `Critical`, ordered and comparable. |
| `find_permission(token)` | Direct database lookup; `None` if unknown. |
| `RISK_DATABASE` | The static `&[PermissionEntry]` constant (60+ MV3 tokens). |
| `classify_host_pattern(token)` | Structural scope of a host pattern: `All` / `Scheme` / `Scoped` / `Unknown`. |
| `is_host_access_pattern(token)` | Heuristic: does this look like a host match-pattern? |
## How classification works
For each token, in order:
1. **Named database hit** — the entry's level + description are used directly.
2. **Host match-pattern** — its [`HostScope`] is classified: blanket
(`<all_urls>`, `*://*/*`) and scheme-wide (`https://*/*`, `file:///*`)
patterns are `Critical`; a scoped per-site pattern (`*://*.example.com/*`)
is `Medium`.
3. **Unknown token** — surfaced as a Low `Unknown` finding with a
"review manually" note. Unknown tokens are **never** silently escalated.
The overall report level is the highest finding's level, with one escalation
rule: an extension requesting **any blanket host access** together with
**`scripting`, `debugger`, `cookies`, or `webRequest`** is the canonical
spyware capability set and is escalated to `Critical` regardless of how the
individual tokens classified.
## Design choices
- **Unknown ≠ Critical.** A token not in the database is surfaced as
unclassified (Low, "review manually"), never silently escalated. False
alarms erode trust; the scanner tells you what it knows.
- **Four tiers, not three.** A separate `Critical` tier keeps the truly
dangerous grants (every site, native process, total tab control) distinct
from merely "high" ones (history, bookmarks), so a UI can render them
differently.
- **Combination awareness.** `<all_urls>` alone is bad; `<all_urls>` +
`scripting` + `cookies` is the full surveillance tool. The auditor
recognises the combination and escalates accordingly.
- **Comprehensive MV3 coverage.** 60+ named tokens — every permission in the
Chrome Extensions reference plus the host-access pattern tokens.
- **Zero dependencies, no `unsafe`.** `#![forbid(unsafe_code)]`; nothing added
to the build graph.
## License
MIT.
## Links
- **zovo.one** — the Chrome-extension privacy & security scanner powered
by this engine: <https://zovo.one/>
- Source (zovo monorepo): <https://github.com/theluckystrike/zovo>
- Full API docs: <https://docs.rs/permission-auditor>