@@ -9,10 +9,12 @@ import (
99
1010// cargoAuditReport is the top-level JSON output from `cargo audit --json`.
1111type cargoAuditReport struct {
12- Lockfile struct { DependencyCount int `json:"dependency-count"` } `json:"lockfile"`
12+ Lockfile struct {
13+ DependencyCount int `json:"dependency-count"`
14+ } `json:"lockfile"`
1315 Vulnerabilities struct {
14- Count int `json:"count"`
15- List []cargoAuditVulnEntry `json:"list"`
16+ Count int `json:"count"`
17+ List []cargoAuditVulnEntry `json:"list"`
1618 } `json:"vulnerabilities"`
1719 Warnings map [string ][]cargoAuditWarningEntry `json:"warnings"`
1820}
@@ -31,112 +33,143 @@ type cargoAuditWarningEntry struct {
3133}
3234
3335type cargoAuditAdvisory struct {
34- ID string `json:"id"`
35- Package string `json:"package"`
36- Title string `json:"title"`
37- Informational * string `json:"informational"`
36+ ID string `json:"id"`
37+ Title string `json:"title"`
3838}
3939
4040type cargoAuditPackage struct {
4141 Name string `json:"name"`
4242 Version string `json:"version"`
4343}
4444
45- // RunCargoAudit checks for security vulnerabilities.
46- func RunCargoAudit (ctx * CheckContext ) (CheckResult , error ) {
47- // Check if cargo-audit is installed
48- if ! CommandExists ("cargo-audit" ) {
49- installCmd := exec .Command ("cargo" , "install" , "cargo-audit" )
50- if _ , err := RunCommand (installCmd , true ); err != nil {
51- return CheckResult {}, fmt .Errorf ("failed to install cargo-audit: %w" , err )
52- }
53- }
54-
55- // Ignore advisories for upstream Tauri dependencies with no fix available:
56- // RUSTSEC-2023-0071: rsa timing sidechannel (via sspi → smb, no fix released)
57- // RUSTSEC-2024-0413..0416: gtk-rs GTK3 bindings unmaintained (used by wry/tao)
58- // RUSTSEC-2024-0421..0424: gtk-sys unmaintained variants
59- cmd := exec .Command ("cargo" , "audit" , "--json" ,
60- "--ignore" , "RUSTSEC-2023-0071" ,
61- "--ignore" , "RUSTSEC-2024-0413" ,
62- "--ignore" , "RUSTSEC-2024-0414" ,
63- "--ignore" , "RUSTSEC-2024-0415" ,
64- "--ignore" , "RUSTSEC-2024-0416" ,
65- "--ignore" , "RUSTSEC-2024-0421" ,
66- "--ignore" , "RUSTSEC-2024-0422" ,
67- "--ignore" , "RUSTSEC-2024-0423" ,
68- "--ignore" , "RUSTSEC-2024-0424" ,
69- )
70- cmd .Dir = ctx .RootDir
71- output , _ := RunCommand (cmd , true )
72- // cargo audit exits non-zero when vulns are found, so we ignore the error
73- // and parse the JSON output instead.
74-
75- var report cargoAuditReport
76- if err := json .Unmarshal ([]byte (output ), & report ); err != nil {
77- return CheckResult {}, fmt .Errorf ("failed to parse cargo-audit JSON output: %w\n %s" , err , indentOutput (output ))
78- }
79-
80- // Count warnings across all categories
81- totalWarnings := 0
82- for _ , entries := range report .Warnings {
83- totalWarnings += len (entries )
84- }
45+ // Advisories for upstream transitive dependencies with no fix available.
46+ // All of these are pulled in by Tauri/wry/tao and can't be resolved by us.
47+ var cargoAuditIgnoredAdvisories = []string {
48+ "RUSTSEC-2023-0071" , // rsa timing sidechannel (via sspi → smb, no fix released)
49+ "RUSTSEC-2024-0370" , // proc-macro-error unmaintained (via gtk3-macros, glib-macros)
50+ "RUSTSEC-2024-0411" , // gdkwayland-sys unmaintained
51+ "RUSTSEC-2024-0412" , // gdk unmaintained
52+ "RUSTSEC-2024-0413" , // gtk-rs GTK3 unmaintained
53+ "RUSTSEC-2024-0414" , // gtk-rs GTK3 unmaintained
54+ "RUSTSEC-2024-0415" , // gtk-rs GTK3 unmaintained
55+ "RUSTSEC-2024-0416" , // gtk-rs GTK3 unmaintained
56+ "RUSTSEC-2024-0417" , // gdkx11 unmaintained
57+ "RUSTSEC-2024-0418" , // gdk-sys unmaintained
58+ "RUSTSEC-2024-0419" , // gtk3-macros unmaintained
59+ "RUSTSEC-2024-0420" , // gtk-sys unmaintained
60+ "RUSTSEC-2024-0421" , // gtk-sys unmaintained
61+ "RUSTSEC-2024-0422" , // gtk-sys unmaintained
62+ "RUSTSEC-2024-0423" , // gtk-sys unmaintained
63+ "RUSTSEC-2024-0424" , // gtk-sys unmaintained
64+ "RUSTSEC-2024-0429" , // glib unsound VariantStrIter (via GTK3 bindings)
65+ "RUSTSEC-2024-0436" , // paste unmaintained (via rav1e → ravif → image)
66+ "RUSTSEC-2025-0052" , // async-std unmaintained (dev-dep only, not shipped)
67+ "RUSTSEC-2025-0057" , // fxhash unmaintained (via tauri-utils → kuchikiki)
68+ "RUSTSEC-2025-0075" , // unic-char-range unmaintained (via tauri-utils → urlpattern)
69+ "RUSTSEC-2025-0080" , // unic-common unmaintained
70+ "RUSTSEC-2025-0081" , // unic-char-property unmaintained
71+ "RUSTSEC-2025-0098" , // unic-ucd-version unmaintained
72+ "RUSTSEC-2025-0100" , // unic-ucd-ident unmaintained
73+ "RUSTSEC-2026-0097" , // rand unsound (0.7/0.8, not using rand::rng())
74+ }
8575
86- // No issues at all
87- if report .Vulnerabilities .Count == 0 && totalWarnings == 0 {
88- return Success (fmt .Sprintf ("Scanned %d %s" ,
89- report .Lockfile .DependencyCount ,
90- Pluralize (report .Lockfile .DependencyCount , "crate" , "crates" ),
91- )), nil
76+ // buildCargoAuditCmd constructs the cargo audit command with --json and all ignores.
77+ func buildCargoAuditCmd () * exec.Cmd {
78+ args := []string {"audit" , "--json" }
79+ for _ , id := range cargoAuditIgnoredAdvisories {
80+ args = append (args , "--ignore" , id )
9281 }
82+ return exec .Command ("cargo" , args ... )
83+ }
9384
94- // Build compact summary
85+ // formatAuditReport builds a compact one-line-per-advisory summary.
86+ func formatAuditReport (report cargoAuditReport ) string {
9587 var lines []string
9688
97- // Header line
89+ // Header
9890 var parts []string
9991 if report .Vulnerabilities .Count > 0 {
10092 parts = append (parts , fmt .Sprintf ("%d %s" ,
10193 report .Vulnerabilities .Count ,
102- Pluralize (report .Vulnerabilities .Count , "vulnerability" , "vulnerabilities" ),
103- ))
94+ Pluralize (report .Vulnerabilities .Count , "vulnerability" , "vulnerabilities" )))
10495 }
96+ totalWarnings := countAuditWarnings (report )
10597 if totalWarnings > 0 {
10698 parts = append (parts , fmt .Sprintf ("%d %s" ,
107- totalWarnings ,
108- Pluralize (totalWarnings , "warning" , "warnings" ),
109- ))
99+ totalWarnings , Pluralize (totalWarnings , "warning" , "warnings" )))
110100 }
111- lines = append (lines , strings .Join (parts , ", " ))
112- lines = append (lines , "" )
101+ lines = append (lines , strings .Join (parts , ", " ), "" )
113102
114- // Vulnerabilities
115103 for _ , v := range report .Vulnerabilities .List {
116- lines = append (lines , fmt .Sprintf ("VULN %s %s — %s" ,
117- v .Package .Name , v .Package .Version , v .Advisory .Title ))
104+ lines = append (lines , fmt .Sprintf ("VULN %s %s — %s" , v .Package .Name , v .Package .Version , v .Advisory .Title ))
118105 fix := "no fix available"
119106 if len (v .Versions .Patched ) > 0 {
120107 fix = "upgrade to " + strings .Join (v .Versions .Patched , " or " )
121108 }
122109 lines = append (lines , fmt .Sprintf (" %s | %s" , fix , v .Advisory .ID ))
123110 }
124-
125- // Warnings by category
126111 for kind , entries := range report .Warnings {
127112 for _ , w := range entries {
128113 lines = append (lines , fmt .Sprintf ("WARN %s %s — %s: %s (%s)" ,
129114 w .Package .Name , w .Package .Version , kind , w .Advisory .Title , w .Advisory .ID ))
130115 }
131116 }
117+ return strings .Join (lines , "\n " )
118+ }
132119
133- summary := strings .Join (lines , "\n " )
120+ func countAuditWarnings (report cargoAuditReport ) int {
121+ total := 0
122+ for _ , entries := range report .Warnings {
123+ total += len (entries )
124+ }
125+ return total
126+ }
127+
128+ // parseAuditJSON extracts and parses the JSON object from cargo-audit output.
129+ // RunCommand concatenates stderr after stdout, so we find the JSON boundaries.
130+ func parseAuditJSON (output string ) (cargoAuditReport , error ) {
131+ jsonStart := strings .Index (output , "{" )
132+ jsonEnd := strings .LastIndex (output , "}" )
133+ if jsonStart < 0 || jsonEnd < 0 || jsonEnd < jsonStart {
134+ return cargoAuditReport {}, fmt .Errorf ("no JSON found in cargo-audit output\n %s" , indentOutput (output ))
135+ }
136+ var report cargoAuditReport
137+ if err := json .Unmarshal ([]byte (output [jsonStart :jsonEnd + 1 ]), & report ); err != nil {
138+ return cargoAuditReport {}, fmt .Errorf ("failed to parse cargo-audit JSON: %w\n %s" , err , indentOutput (output ))
139+ }
140+ return report , nil
141+ }
142+
143+ // RunCargoAudit checks for security vulnerabilities.
144+ func RunCargoAudit (ctx * CheckContext ) (CheckResult , error ) {
145+ if ! CommandExists ("cargo-audit" ) {
146+ installCmd := exec .Command ("cargo" , "install" , "cargo-audit" )
147+ if _ , err := RunCommand (installCmd , true ); err != nil {
148+ return CheckResult {}, fmt .Errorf ("failed to install cargo-audit: %w" , err )
149+ }
150+ }
151+
152+ cmd := buildCargoAuditCmd ()
153+ cmd .Dir = ctx .RootDir
154+ output , _ := RunCommand (cmd , true )
155+
156+ report , err := parseAuditJSON (output )
157+ if err != nil {
158+ return CheckResult {}, err
159+ }
160+
161+ if report .Vulnerabilities .Count == 0 && countAuditWarnings (report ) == 0 {
162+ return Success (fmt .Sprintf ("Scanned %d %s" ,
163+ report .Lockfile .DependencyCount ,
164+ Pluralize (report .Lockfile .DependencyCount , "crate" , "crates" ))), nil
165+ }
134166
167+ summary := formatAuditReport (report )
135168 if report .Vulnerabilities .Count > 0 {
136169 return CheckResult {}, fmt .Errorf ("security vulnerabilities found\n %s" , indentOutput (summary ))
137170 }
138171
139- // Warnings only — not a failure
172+ totalWarnings := countAuditWarnings ( report )
140173 return CheckResult {
141174 Code : ResultWarning ,
142175 Message : fmt .Sprintf ("%d %s\n %s" , totalWarnings , Pluralize (totalWarnings , "warning" , "warnings" ), indentOutput (summary )),
0 commit comments