Skip to content

Commit 40ca824

Browse files
authored
✨ Detect Firefox remoting name on macOS (#5902)
Signed-off-by: Christian Zunker <christian@mondoo.com>
1 parent 536762b commit 40ca824

File tree

3 files changed

+114
-16
lines changed

3 files changed

+114
-16
lines changed

providers/os/resources/packages/macos_packages.go

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ package packages
66
import (
77
"fmt"
88
"io"
9+
"path/filepath"
910
"strings"
1011

1112
"github.com/cockroachdb/errors"
13+
"github.com/rs/zerolog/log"
1214
"go.mondoo.com/cnquery/v12/providers-sdk/v1/inventory"
1315
"go.mondoo.com/cnquery/v12/providers/os/connection/shared"
16+
"go.mondoo.com/cnquery/v12/providers/os/resources/parsers"
1417
"go.mondoo.com/cnquery/v12/providers/os/resources/purl"
1518
plist "howett.net/plist"
1619
)
@@ -19,8 +22,18 @@ const (
1922
MacosPkgFormat = "macos"
2023
)
2124

25+
type sysProfilerItem struct {
26+
Name string `plist:"_name"`
27+
Version string `plist:"version"`
28+
Path string `plist:"path"`
29+
}
30+
31+
type sysProfiler struct {
32+
Items []sysProfilerItem `plist:"_items"`
33+
}
34+
2235
// parse macos system version property list
23-
func ParseMacOSPackages(platform *inventory.Platform, input io.Reader) ([]Package, error) {
36+
func ParseMacOSPackages(conn shared.Connection, platform *inventory.Platform, input io.Reader) ([]Package, error) {
2437
var r io.ReadSeeker
2538
r, ok := input.(io.ReadSeeker)
2639

@@ -33,16 +46,6 @@ func ParseMacOSPackages(platform *inventory.Platform, input io.Reader) ([]Packag
3346
r = strings.NewReader(string(packageList))
3447
}
3548

36-
type sysProfilerItems struct {
37-
Name string `plist:"_name"`
38-
Version string `plist:"version"`
39-
Path string `plist:"path"`
40-
}
41-
42-
type sysProfiler struct {
43-
Items []sysProfilerItems `plist:"_items"`
44-
}
45-
4649
var data []sysProfiler
4750
decoder := plist.NewDecoder(r)
4851
err := decoder.Decode(&data)
@@ -56,13 +59,15 @@ func ParseMacOSPackages(platform *inventory.Platform, input io.Reader) ([]Packag
5659

5760
pkgs := make([]Package, len(data[0].Items))
5861
for i, entry := range data[0].Items {
62+
// We need a special handling for Firefox to determine ESR installations
63+
purlQualifiers := getPurlQualifiers(conn, entry)
5964
pkgs[i].Name = entry.Name
6065
pkgs[i].Version = entry.Version
6166
pkgs[i].Format = MacosPkgFormat
6267
pkgs[i].FilesAvailable = PkgFilesIncluded
6368
pkgs[i].Arch = platform.Arch
6469
pkgs[i].PUrl = purl.NewPackageURL(
65-
platform, purl.TypeMacos, entry.Name, entry.Version,
70+
platform, purl.TypeMacos, entry.Name, entry.Version, purl.WithQualifiers(purlQualifiers),
6671
).String()
6772
if entry.Path != "" {
6873
pkgs[i].Files = []FileRecord{
@@ -96,7 +101,7 @@ func (mpm *MacOSPkgManager) List() ([]Package, error) {
96101
return nil, fmt.Errorf("could not read package list")
97102
}
98103

99-
return ParseMacOSPackages(mpm.platform, cmd.Stdout)
104+
return ParseMacOSPackages(mpm.conn, mpm.platform, cmd.Stdout)
100105
}
101106

102107
func (mpm *MacOSPkgManager) Available() (map[string]PackageUpdate, error) {
@@ -107,3 +112,38 @@ func (mpm *MacOSPkgManager) Files(name string, version string, arch string) ([]F
107112
// nothing extra to be done here since the list is already included in the package list
108113
return nil, nil
109114
}
115+
116+
func getPurlQualifiers(conn shared.Connection, entry sysProfilerItem) map[string]string {
117+
qualifiers := make(map[string]string)
118+
if entry.Name == "Firefox" {
119+
appIni := ""
120+
if entry.Path != "" {
121+
appIni = filepath.Join(entry.Path, "Contents", "Resources", "application.ini")
122+
}
123+
if appIni != "" {
124+
f, err := conn.FileSystem().Open(appIni)
125+
if err != nil {
126+
log.Debug().Err(err).Msg("could not open application.ini")
127+
return nil
128+
}
129+
defer f.Close()
130+
content, err := io.ReadAll(f)
131+
if err != nil {
132+
log.Debug().Err(err).Msg("could not read application.ini")
133+
return nil
134+
}
135+
ini := parsers.ParseIni(string(content), "=")
136+
if ini != nil {
137+
if data, ok := ini.Fields["App"]; ok {
138+
fields, ok := data.(map[string]any)
139+
if ok {
140+
if name, ok := fields["RemotingName"]; ok {
141+
qualifiers["remoting-name"] = name.(string)
142+
}
143+
}
144+
}
145+
}
146+
}
147+
}
148+
return qualifiers
149+
}

providers/os/resources/packages/macos_packages_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ func TestMacOsXPackageParser(t *testing.T) {
2929
Arch: "x86_64",
3030
Family: []string{"darwin", "bsd", "unix", "os"},
3131
}
32-
m, err := packages.ParseMacOSPackages(pf, c.Stdout)
32+
m, err := packages.ParseMacOSPackages(mock, pf, c.Stdout)
3333
assert.Nil(t, err)
34-
assert.Equal(t, 2, len(m), "detected the right amount of packages")
34+
assert.Equal(t, 3, len(m), "detected the right amount of packages")
3535

3636
assert.Equal(t, "Preview", m[0].Name, "pkg name detected")
3737
assert.Equal(t, "10.0", m[0].Version, "pkg version detected")
@@ -47,4 +47,10 @@ func TestMacOsXPackageParser(t *testing.T) {
4747
assert.Equal(t, packages.PkgFilesIncluded, m[1].FilesAvailable)
4848
assert.Equal(t, "pkg:macos/macos/Contacts@11.0?arch=x86_64", m[1].PUrl)
4949
assert.Equal(t, []packages.FileRecord{{Path: "/Applications/Contacts.app"}}, m[1].Files)
50+
51+
assert.Equal(t, "Firefox", m[2].Name, "pkg name detected")
52+
assert.Equal(t, "128.12.0", m[2].Version, "pkg version detected")
53+
assert.Equal(t, packages.MacosPkgFormat, m[2].Format, "pkg format detected")
54+
assert.Equal(t, "pkg:macos/macos/Firefox@128.12.0?arch=x86_64&remoting-name=firefox-esr", m[2].PUrl)
55+
assert.Equal(t, []packages.FileRecord{{Path: "/Applications/Firefox.app"}}, m[2].Files)
5056
}

providers/os/resources/packages/testdata/packages_macos.toml

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,62 @@ stdout = """<?xml version="1.0" encoding="UTF-8"?>
6969
<key>version</key>
7070
<string>11.0</string>
7171
</dict>
72+
<dict>
73+
<key>_name</key>
74+
<string>Firefox</string>
75+
<key>arch_kind</key>
76+
<string>arch_arm_i64</string>
77+
<key>info</key>
78+
<string>Firefox 128.12.0</string>
79+
<key>lastModified</key>
80+
<date>2025-07-02T08:20:57Z</date>
81+
<key>obtained_from</key>
82+
<string>identified_developer</string>
83+
<key>path</key>
84+
<string>/Applications/Firefox.app</string>
85+
<key>signed_by</key>
86+
<array>
87+
<string>Developer ID Application: Mozilla Corporation (43AQ936H96)</string>
88+
<string>Developer ID Certification Authority</string>
89+
<string>Apple Root CA</string>
90+
</array>
91+
<key>version</key>
92+
<string>128.12.0</string>
93+
</dict>
7294
</array>
7395
<key>_parentDataType</key>
7496
<string>SPSoftwareDataType</string>
7597
</dict>
7698
</array>
7799
</plist>
78-
"""
100+
"""
101+
102+
[files."/Applications/Firefox.app/Contents/Resources/application.ini"]
103+
content = """
104+
; This file is not used. If you modify it and want the application to use
105+
; your modifications, move it under the browser/ subdirectory and start with
106+
; the "-app /path/to/browser/application.ini" argument.
107+
[App]
108+
Vendor=Mozilla
109+
Name=Firefox
110+
RemotingName=firefox-esr
111+
Version=128.14.0
112+
BuildID=20250811113756
113+
SourceRepository=https://hg.mozilla.org/releases/mozilla-esr128
114+
SourceStamp=df0b4a4887880a1b267ca2b4902afee84e80f6e9
115+
ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
116+
117+
[Gecko]
118+
MinVersion=128.14.0
119+
MaxVersion=128.14.0
120+
121+
[XRE]
122+
EnableProfileMigrator=1
123+
124+
[Crash Reporter]
125+
Enabled=1
126+
ServerURL=https://crash-reports.mozilla.com/submit?id={ec8030f7-c20a-464f-9b0e-13a3a9e97384}&version=128.14.0&buildid=20250811113756
127+
128+
[AppUpdate]
129+
URL=https://aus5.mozilla.org/update/6/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%SYSTEM_CAPABILITIES%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml
130+
"""

0 commit comments

Comments
 (0)