Check trusted location before from_webstore in chromiumSideloaded
Resolves #50706 chromiumSideloaded returned true as soon as from_webstore was 0, never reaching the trusted-location check. First-party browser components are installed by the browser itself and so always report from_webstore:false, which made the location exemption unreachable and flagged components like Edge Copilot Bridge as sideloaded_unverified. Check location for Internal/Component first. The existing collector fixture asserted the buggy behaviour and is updated to a genuinely sideloaded location, with a first-party component case added.
This commit is contained in:
@@ -40,18 +40,34 @@ func TestHasBroadHostPerms(t *testing.T) {
|
||||
|
||||
func TestChromiumSideloaded(t *testing.T) {
|
||||
// fromWebstore: -1 unknown, 0 no, 1 yes. location: 0 unknown, 1 internal, 4 unpacked, 5 component, 10 external.
|
||||
// Exhaustive over those two sets. Keep it that way — the outcome turns on
|
||||
// which of the two signals is consulted first, so a reordering can silently
|
||||
// change a case no test covers.
|
||||
cases := []struct {
|
||||
fw, loc int
|
||||
want bool
|
||||
}{
|
||||
{1, 1, false}, // store + internal
|
||||
{0, 1, true}, // explicitly not webstore
|
||||
{1, 5, false}, // store + component
|
||||
{1, 0, false}, // store-flagged, location unreadable -> nothing anomalous
|
||||
{-1, 1, false}, // internal, store signal unknown
|
||||
{-1, 4, true}, // unpacked
|
||||
{-1, 10, true}, // external/policy
|
||||
{-1, 5, false}, // component
|
||||
{-1, 0, false}, // both unknown -> conservative, no flag
|
||||
{1, 4, true}, // store-flagged but unpacked location -> still anomalous
|
||||
{1, 10, true}, // store-flagged but external/policy location -> still anomalous
|
||||
|
||||
// A trusted location wins over from_webstore: the browser installs its
|
||||
// own first-party components itself, so they always report
|
||||
// from_webstore:false and must not be called sideloaded.
|
||||
{0, 1, false}, // first-party internal component
|
||||
{0, 5, false}, // first-party component (e.g. Edge Copilot Bridge)
|
||||
|
||||
// from_webstore:false still flags anything without a trusted location.
|
||||
{0, 0, true}, // not from the store, location unknown
|
||||
{0, 4, true}, // not from the store, unpacked
|
||||
{0, 10, true}, // not from the store, external/policy
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := chromiumSideloaded(c.fw, c.loc); got != c.want {
|
||||
@@ -129,12 +145,21 @@ func TestCollectChromiumProfile(t *testing.T) {
|
||||
`{"name":"Claude Dev","version":"9.9.9","manifest_version":3,"permissions":["<all_urls>"]}`)
|
||||
unpackedID := "cccccccccccccccccccccccccccccccc"
|
||||
|
||||
// Secure Preferences: AI ext not-from-webstore (sideloaded), unpacked entry.
|
||||
// First-party browser component: installed by the browser itself, so
|
||||
// from_webstore is false and location is 5 (component). Preferences-only,
|
||||
// like a real built-in. Must NOT be flagged sideloaded. Carries broad host
|
||||
// perms so the assertion below can tell "sideloaded token suppressed" apart
|
||||
// from "risk never computed at all".
|
||||
componentID := "dddddddddddddddddddddddddddddddd"
|
||||
|
||||
// Secure Preferences: AI ext not-from-webstore and externally installed
|
||||
// (sideloaded), unpacked entry, and a trusted first-party component.
|
||||
prefs := map[string]any{
|
||||
"extensions": map[string]any{
|
||||
"settings": map[string]any{
|
||||
aiID: map[string]any{"from_webstore": false, "location": 1},
|
||||
unpackedID: map[string]any{"location": 4, "path": unpackedSrc, "manifest": map[string]any{"name": "Claude Dev", "version": "9.9.9", "permissions": []string{"<all_urls>"}}},
|
||||
aiID: map[string]any{"from_webstore": false, "location": 10},
|
||||
unpackedID: map[string]any{"location": 4, "path": unpackedSrc, "manifest": map[string]any{"name": "Claude Dev", "version": "9.9.9", "permissions": []string{"<all_urls>"}}},
|
||||
componentID: map[string]any{"from_webstore": false, "location": 5, "manifest": map[string]any{"name": "Edge Copilot Bridge", "version": "1.2.3", "permissions": []string{"<all_urls>"}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -175,6 +200,16 @@ func TestCollectChromiumProfile(t *testing.T) {
|
||||
if up.Name != "Claude Dev" || up.SHA256 == "" || !contains(up.RiskFlags, "sideloaded_unverified") {
|
||||
t.Errorf("unpacked ext wrong: %+v", up)
|
||||
}
|
||||
comp, ok := by[componentID]
|
||||
if !ok {
|
||||
t.Fatal("first-party component (Preferences-only) not recovered")
|
||||
}
|
||||
// Exact match, not a negated substring check: the component must lose the
|
||||
// sideloaded token and keep every other flag it earns. A negated contains()
|
||||
// would also pass if risk were never computed for this row at all.
|
||||
if comp.RiskFlags != "broad_host_permissions" {
|
||||
t.Errorf("RiskFlags=%q want %q — a trusted first-party component drops only the sideloaded token", comp.RiskFlags, "broad_host_permissions")
|
||||
}
|
||||
}
|
||||
|
||||
func contains(haystack, needle string) bool { return strings.Contains(haystack, needle) }
|
||||
|
||||
@@ -20,8 +20,11 @@ func hasBroadHostPerms(patterns []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Chromium Manifest::Location values we treat as trusted-origin.
|
||||
// Chromium Manifest::Location values we act on: kInvalidLocation — which is also
|
||||
// the Go zero value, so it covers a Preferences entry with no "location" key —
|
||||
// and the two we treat as trusted-origin.
|
||||
const (
|
||||
chromiumLocUnknown = 0
|
||||
chromiumLocInternal = 1
|
||||
chromiumLocComponent = 5
|
||||
)
|
||||
@@ -29,14 +32,23 @@ const (
|
||||
// chromiumSideloaded reports whether a Chromium extension was installed outside
|
||||
// the Web Store (unpacked/dev, external, or policy-forced). Conservative: when
|
||||
// both signals are unknown it returns false to avoid false positives.
|
||||
//
|
||||
// A trusted location is checked first and wins over from_webstore. The browser
|
||||
// installs its own first-party components (Edge Copilot Bridge, for instance),
|
||||
// so by definition they are never web-store-installed and always report
|
||||
// from_webstore:false — checking that signal first would make the trusted-origin
|
||||
// exemption unreachable and flag every built-in component as sideloaded.
|
||||
func chromiumSideloaded(fromWebstore, location int) bool {
|
||||
if location == chromiumLocInternal || location == chromiumLocComponent {
|
||||
return false
|
||||
}
|
||||
if fromWebstore == 0 { // explicitly not from the web store
|
||||
return true
|
||||
}
|
||||
if location != 0 && location != chromiumLocInternal && location != chromiumLocComponent {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// The trusted locations already returned above, so any location we can read
|
||||
// at this point is one an ordinary install does not land in; only an
|
||||
// unreadable location is left alone.
|
||||
return location != chromiumLocUnknown
|
||||
}
|
||||
|
||||
// geckoSideloaded reports whether a Gecko addon is unsigned/temporary or was
|
||||
|
||||
Reference in New Issue
Block a user