detached 27813e6

Disable WebAuthn in CEF OSR panes

commit
27813e6347404700a2d0d25cb969feecf37b3abb
author
brice · · 2026-04-29 17:23 UTC
parent
4613d9c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +221 -6
M docs/reference/permissions.md +8 -0
@@ -23,6 +23,14 @@ These permissions require explicit user approval:
2323
2424 When both microphone and camera are requested together, a combined dialog is shown.
2525
26+## WebAuthn / passkeys / security keys
27+
28+Dumber currently **disables** WebAuthn (`PublicKeyCredential`, passkeys, FIDO2/security keys) in embedded CEF panes. The current CEF integration uses windowless/OSR Alloy browsers and does not expose the Chromium WebAuthn request UI/delegate needed for PIN, account selection, or transport UI.
29+
30+If a site requires passkeys or hardware security keys, use the system browser for that auth flow until a dedicated Chrome-runtime auth-window prototype exists.
31+
32+> ⚠️ **Developer override (unsupported)**: Set `DUMBER_CEF_ENABLE_WEBAUTHN_UNSAFE=1` to temporarily re-enable WebAuthn. This is unsupported and may crash.
33+
2634 ## Permission Dialog
2735
2836 When a site requests mic/camera access, a dialog appears with four options:
@@ -23,6 +23,14 @@ These permissions require explicit user approval:
23 23
24 When both microphone and camera are requested together, a combined dialog is shown.24 When both microphone and camera are requested together, a combined dialog is shown.
25 25
26+## WebAuthn / passkeys / security keys
27+
28+Dumber currently **disables** WebAuthn (`PublicKeyCredential`, passkeys, FIDO2/security keys) in embedded CEF panes. The current CEF integration uses windowless/OSR Alloy browsers and does not expose the Chromium WebAuthn request UI/delegate needed for PIN, account selection, or transport UI.
29+
30+If a site requires passkeys or hardware security keys, use the system browser for that auth flow until a dedicated Chrome-runtime auth-window prototype exists.
31+
32+> ⚠️ **Developer override (unsupported)**: Set `DUMBER_CEF_ENABLE_WEBAUTHN_UNSAFE=1` to temporarily re-enable WebAuthn. This is unsupported and may crash.
33+
26 ## Permission Dialog34 ## Permission Dialog
27 35
28 When a site requests mic/camera access, a dialog appears with four options:36 When a site requests mic/camera access, a dialog appears with four options:
M internal/infrastructure/cef/app.go +72 -1
@@ -8,7 +8,18 @@ import (
88 "github.com/bnema/dumber/internal/logging"
99 )
1010
11-const dumbSchemeName = "dumb"
11+const (
12+ dumbSchemeName = "dumb"
13+ chromiumDisableFeaturesSwitch = "disable-features"
14+ chromiumDisableBlinkFeaturesSwitch = "disable-blink-features"
15+)
16+
17+// Chromium's core Blink runtime flag for the Web Authentication API is
18+// "WebAuth" (PublicKeyCredential IDL uses RuntimeEnabled=WebAuth). The
19+// longer "WebAuthentication*" names are separate subfeatures/metrics.
20+var cefWebAuthnFeaturesDisabledByPolicy = []string{
21+ "WebAuth",
22+}
1223
1324 func dumbSchemeOptions() int32 {
1425 return purecef.SchemeOptionsSchemeOptionStandard |
@@ -38,6 +49,52 @@ func configureCommandLine(commandLine purecef.CommandLine) {
3849 // Reddit autoplay muted videos in the feed; without this policy
3950 // Chromium blocks them, showing an infinite spinner.
4051 commandLine.AppendSwitchWithValue("autoplay-policy", "no-user-gesture-required")
52+
53+ configureWebAuthnFeaturePolicy(commandLine)
54+}
55+
56+func configureWebAuthnFeaturePolicy(commandLine purecef.CommandLine) {
57+ if commandLine == nil || cefWebAuthnUnsafeEnabled() {
58+ return
59+ }
60+ appendUniqueCommaSeparatedSwitchValues(commandLine, chromiumDisableFeaturesSwitch, cefWebAuthnFeaturesDisabledByPolicy...)
61+ appendUniqueCommaSeparatedSwitchValues(commandLine, chromiumDisableBlinkFeaturesSwitch, cefWebAuthnFeaturesDisabledByPolicy...)
62+}
63+
64+func appendUniqueCommaSeparatedSwitchValues(commandLine purecef.CommandLine, name string, values ...string) {
65+ if commandLine == nil || len(values) == 0 {
66+ return
67+ }
68+
69+ seen := make(map[string]struct{}, len(values))
70+ combined := make([]string, 0, len(values))
71+ for _, value := range strings.Split(commandLine.GetSwitchValue(name), ",") {
72+ value = strings.TrimSpace(value)
73+ if value == "" {
74+ continue
75+ }
76+ if _, ok := seen[value]; ok {
77+ continue
78+ }
79+ seen[value] = struct{}{}
80+ combined = append(combined, value)
81+ }
82+ for _, value := range values {
83+ value = strings.TrimSpace(value)
84+ if value == "" {
85+ continue
86+ }
87+ if _, ok := seen[value]; ok {
88+ continue
89+ }
90+ seen[value] = struct{}{}
91+ combined = append(combined, value)
92+ }
93+
94+ if len(combined) == 0 {
95+ return
96+ }
97+ commandLine.AppendSwitchWithValue(name, strings.Join(combined, ","))
4198 }
4299
43100 // dumberApp implements purecef.App to provide custom scheme registration,
@@ -74,6 +131,19 @@ func (a *dumberApp) OnBeforeCommandLineProcessing(processType string, commandLin
74131 if commandLine != nil {
75132 configureCommandLine(commandLine)
76133
134+ if processType == "" {
135+ if cefWebAuthnUnsafeEnabled() {
136+ log.Warn().
137+ Str("env_var", cefEnableWebAuthnUnsafeEnvVar).
138+ Msg("cef: WebAuthn enabled via unsupported developer override")
139+ } else {
140+ log.Info().
141+ Strs("disabled_features", cefWebAuthnFeaturesDisabledByPolicy).
142+ Str("override_env_var", cefEnableWebAuthnUnsafeEnvVar).
143+ Msg("cef: WebAuthn disabled for CEF windowless/Alloy runtime")
144+ }
145+ }
146+
77147 cmdline := commandLine.GetCommandLineString()
78148 if len(cmdline) > maxCmdLineLogLen {
79149 runes := []rune(cmdline)
@@ -124,6 +194,7 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine)
124194 useAngle := ""
125195 ozonePlatform := ""
126196 if commandLine != nil {
197+ configureWebAuthnFeaturePolicy(commandLine)
127198 appendSwitchIfMissing(commandLine, "no-zygote")
128199 processType = commandLine.GetSwitchValue("type")
129200 commandLineString = commandLine.GetCommandLineString()
@@ -8,7 +8,18 @@ import (
8 "github.com/bnema/dumber/internal/logging"8 "github.com/bnema/dumber/internal/logging"
9 )9 )
10 10
11-const dumbSchemeName = "dumb"11+const (
12+ dumbSchemeName = "dumb"
13+ chromiumDisableFeaturesSwitch = "disable-features"
14+ chromiumDisableBlinkFeaturesSwitch = "disable-blink-features"
15+)
16+
17+// Chromium's core Blink runtime flag for the Web Authentication API is
18+// "WebAuth" (PublicKeyCredential IDL uses RuntimeEnabled=WebAuth). The
19+// longer "WebAuthentication*" names are separate subfeatures/metrics.
20+var cefWebAuthnFeaturesDisabledByPolicy = []string{
21+ "WebAuth",
22+}
12 23
13 func dumbSchemeOptions() int32 {24 func dumbSchemeOptions() int32 {
14 return purecef.SchemeOptionsSchemeOptionStandard |25 return purecef.SchemeOptionsSchemeOptionStandard |
@@ -38,6 +49,52 @@ func configureCommandLine(commandLine purecef.CommandLine) {
38 // Reddit autoplay muted videos in the feed; without this policy49 // Reddit autoplay muted videos in the feed; without this policy
39 // Chromium blocks them, showing an infinite spinner.50 // Chromium blocks them, showing an infinite spinner.
40 commandLine.AppendSwitchWithValue("autoplay-policy", "no-user-gesture-required")51 commandLine.AppendSwitchWithValue("autoplay-policy", "no-user-gesture-required")
52+
53+ configureWebAuthnFeaturePolicy(commandLine)
54+}
55+
56+func configureWebAuthnFeaturePolicy(commandLine purecef.CommandLine) {
57+ if commandLine == nil || cefWebAuthnUnsafeEnabled() {
58+ return
59+ }
60+ appendUniqueCommaSeparatedSwitchValues(commandLine, chromiumDisableFeaturesSwitch, cefWebAuthnFeaturesDisabledByPolicy...)
61+ appendUniqueCommaSeparatedSwitchValues(commandLine, chromiumDisableBlinkFeaturesSwitch, cefWebAuthnFeaturesDisabledByPolicy...)
62+}
63+
64+func appendUniqueCommaSeparatedSwitchValues(commandLine purecef.CommandLine, name string, values ...string) {
65+ if commandLine == nil || len(values) == 0 {
66+ return
67+ }
68+
69+ seen := make(map[string]struct{}, len(values))
70+ combined := make([]string, 0, len(values))
71+ for _, value := range strings.Split(commandLine.GetSwitchValue(name), ",") {
72+ value = strings.TrimSpace(value)
73+ if value == "" {
74+ continue
75+ }
76+ if _, ok := seen[value]; ok {
77+ continue
78+ }
79+ seen[value] = struct{}{}
80+ combined = append(combined, value)
81+ }
82+ for _, value := range values {
83+ value = strings.TrimSpace(value)
84+ if value == "" {
85+ continue
86+ }
87+ if _, ok := seen[value]; ok {
88+ continue
89+ }
90+ seen[value] = struct{}{}
91+ combined = append(combined, value)
92+ }
93+
94+ if len(combined) == 0 {
95+ return
96+ }
97+ commandLine.AppendSwitchWithValue(name, strings.Join(combined, ","))
41 }98 }
42 99
43 // dumberApp implements purecef.App to provide custom scheme registration,100 // dumberApp implements purecef.App to provide custom scheme registration,
@@ -74,6 +131,19 @@ func (a *dumberApp) OnBeforeCommandLineProcessing(processType string, commandLin
74 if commandLine != nil {131 if commandLine != nil {
75 configureCommandLine(commandLine)132 configureCommandLine(commandLine)
76 133
134+ if processType == "" {
135+ if cefWebAuthnUnsafeEnabled() {
136+ log.Warn().
137+ Str("env_var", cefEnableWebAuthnUnsafeEnvVar).
138+ Msg("cef: WebAuthn enabled via unsupported developer override")
139+ } else {
140+ log.Info().
141+ Strs("disabled_features", cefWebAuthnFeaturesDisabledByPolicy).
142+ Str("override_env_var", cefEnableWebAuthnUnsafeEnvVar).
143+ Msg("cef: WebAuthn disabled for CEF windowless/Alloy runtime")
144+ }
145+ }
146+
77 cmdline := commandLine.GetCommandLineString()147 cmdline := commandLine.GetCommandLineString()
78 if len(cmdline) > maxCmdLineLogLen {148 if len(cmdline) > maxCmdLineLogLen {
79 runes := []rune(cmdline)149 runes := []rune(cmdline)
@@ -124,6 +194,7 @@ func (h *dumberBPH) OnBeforeChildProcessLaunch(commandLine purecef.CommandLine)
124 useAngle := ""194 useAngle := ""
125 ozonePlatform := ""195 ozonePlatform := ""
126 if commandLine != nil {196 if commandLine != nil {
197+ configureWebAuthnFeaturePolicy(commandLine)
127 appendSwitchIfMissing(commandLine, "no-zygote")198 appendSwitchIfMissing(commandLine, "no-zygote")
128 processType = commandLine.GetSwitchValue("type")199 processType = commandLine.GetSwitchValue("type")
129 commandLineString = commandLine.GetCommandLineString()200 commandLineString = commandLine.GetCommandLineString()
M internal/infrastructure/cef/app_test.go +102 -4
@@ -2,6 +2,7 @@ package cef
22
33 import (
44 "context"
5+ "strings"
56 "testing"
67 "unsafe"
78
@@ -175,8 +176,6 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardNonBrowse(t *testin
175176 }
176177
177178 func TestConfigureCommandLine_AppendsExpectedSwitches(t *testing.T) {
178- t.Parallel()
179-
180179 commandLine := newMutableCommandLineStub()
181180 configureCommandLine(commandLine)
182181
@@ -188,9 +187,90 @@ func TestConfigureCommandLine_AppendsExpectedSwitches(t *testing.T) {
188187 }
189188 }
190189
191-func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) {
192- t.Parallel()
190+func TestConfigureCommandLine_DisablesWebAuthnByDefault(t *testing.T) {
191+ commandLine := newMutableCommandLineStub()
192+ configureCommandLine(commandLine)
193+
194+ want := "WebAuth"
195+ if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != want {
196+ t.Fatalf("disable-features = %q, want %q", got, want)
197+ }
198+ if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != want {
199+ t.Fatalf("disable-blink-features = %q, want %q", got, want)
200+ }
201+}
202+
203+func TestConfigureCommandLine_WebAuthnUnsafeOptInDoesNotDisableWebAuth(t *testing.T) {
204+ t.Setenv(cefEnableWebAuthnUnsafeEnvVar, "1")
193205
206+ commandLine := newMutableCommandLineStub()
207+ configureCommandLine(commandLine)
208+
209+ if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); strings.Contains(got, "WebAuth") {
210+ t.Fatalf("disable-features = %q, should not contain WebAuth", got)
211+ }
212+ if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); strings.Contains(got, "WebAuth") {
213+ t.Fatalf("disable-blink-features = %q, should not contain WebAuth", got)
214+ }
215+}
216+
217+func TestConfigureCommandLine_WebAuthnDisablePreservesExistingDisableFeatures(t *testing.T) {
218+ commandLine := newMutableCommandLineStub()
219+ commandLine.AppendSwitchWithValue(chromiumDisableFeaturesSwitch, "ExistingFeature,WebAuth")
220+ commandLine.AppendSwitchWithValue(chromiumDisableBlinkFeaturesSwitch, "ExistingBlinkFeature,WebAuth")
221+
222+ configureCommandLine(commandLine)
223+
224+ if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != "ExistingFeature,WebAuth" {
225+ t.Fatalf("disable-features = %q, want %q", got, "ExistingFeature,WebAuth")
226+ }
227+ if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != "ExistingBlinkFeature,WebAuth" {
228+ t.Fatalf("disable-blink-features = %q, want %q", got, "ExistingBlinkFeature,WebAuth")
229+ }
230+}
231+
232+func TestAppendUniqueCommaSeparatedSwitchValues(t *testing.T) {
233+ t.Run("trims whitespace and skips empty existing values", func(t *testing.T) {
234+ commandLine := newMutableCommandLineStub()
235+ commandLine.AppendSwitchWithValue("disable-features", " , ExistingFeature , ")
236+
237+ appendUniqueCommaSeparatedSwitchValues(commandLine, "disable-features", "FeatureA")
238+
239+ want := "ExistingFeature,FeatureA"
240+ if got := commandLine.GetSwitchValue("disable-features"); got != want {
241+ t.Fatalf("disable-features = %q, want %q", got, want)
242+ }
243+ })
244+
245+ t.Run("deduplicates existing and appended values", func(t *testing.T) {
246+ commandLine := newMutableCommandLineStub()
247+ commandLine.AppendSwitchWithValue("disable-features", "FeatureA,FeatureA")
248+
249+ appendUniqueCommaSeparatedSwitchValues(commandLine, "disable-features", "FeatureA", "FeatureB")
250+
251+ want := "FeatureA,FeatureB"
252+ if got := commandLine.GetSwitchValue("disable-features"); got != want {
253+ t.Fatalf("disable-features = %q, want %q", got, want)
254+ }
255+ })
256+
257+ t.Run("empty appended values are no-op", func(t *testing.T) {
258+ commandLine := newMutableCommandLineStub()
259+ commandLine.AppendSwitchWithValue("disable-features", "ExistingFeature")
260+
261+ appendUniqueCommaSeparatedSwitchValues(commandLine, "disable-features")
262+
263+ if got := commandLine.GetSwitchValue("disable-features"); got != "ExistingFeature" {
264+ t.Fatalf("disable-features = %q, want ExistingFeature", got)
265+ }
266+ })
267+
268+ t.Run("nil command line is no-op", func(_ *testing.T) {
269+ appendUniqueCommaSeparatedSwitchValues(nil, "disable-features", "FeatureA")
270+ })
271+}
272+
273+func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) {
194274 commandLine := newMutableCommandLineStub()
195275 commandLine.AppendSwitchWithValue("type", "renderer")
196276
@@ -200,3 +280,21 @@ func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) {
200280 t.Fatal("expected no-zygote switch to be appended for child processes")
201281 }
202282 }
283+
284+func TestDumberBPH_OnBeforeChildProcessLaunch_DisablesWebAuthnByDefault(t *testing.T) {
285+ commandLine := newMutableCommandLineStub()
286+ commandLine.AppendSwitchWithValue("type", "renderer")
287+
288+ (&dumberBPH{engine: &Engine{ctx: context.Background()}}).OnBeforeChildProcessLaunch(commandLine)
289+
290+ want := "WebAuth"
291+ if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != want {
292+ t.Fatalf("disable-features = %q, want %q", got, want)
293+ }
294+ if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != want {
295+ t.Fatalf("disable-blink-features = %q, want %q", got, want)
296+ }
297+ if !commandLine.HasSwitch("no-zygote") {
298+ t.Fatal("expected no-zygote switch to be appended")
299+ }
300+}
@@ -2,6 +2,7 @@ package cef
2 2
3 import (3 import (
4 "context"4 "context"
5+ "strings"
5 "testing"6 "testing"
6 "unsafe"7 "unsafe"
7 8
@@ -175,8 +176,6 @@ func TestDumberBPH_OnAlreadyRunningAppRelaunch_DoesNotForwardNonBrowse(t *testin
175 }176 }
176 177
177 func TestConfigureCommandLine_AppendsExpectedSwitches(t *testing.T) {178 func TestConfigureCommandLine_AppendsExpectedSwitches(t *testing.T) {
178- t.Parallel()
179-
180 commandLine := newMutableCommandLineStub()179 commandLine := newMutableCommandLineStub()
181 configureCommandLine(commandLine)180 configureCommandLine(commandLine)
182 181
@@ -188,9 +187,90 @@ func TestConfigureCommandLine_AppendsExpectedSwitches(t *testing.T) {
188 }187 }
189 }188 }
190 189
191-func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) {190+func TestConfigureCommandLine_DisablesWebAuthnByDefault(t *testing.T) {
192- t.Parallel()191+ commandLine := newMutableCommandLineStub()
192+ configureCommandLine(commandLine)
193+
194+ want := "WebAuth"
195+ if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != want {
196+ t.Fatalf("disable-features = %q, want %q", got, want)
197+ }
198+ if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != want {
199+ t.Fatalf("disable-blink-features = %q, want %q", got, want)
200+ }
201+}
202+
203+func TestConfigureCommandLine_WebAuthnUnsafeOptInDoesNotDisableWebAuth(t *testing.T) {
204+ t.Setenv(cefEnableWebAuthnUnsafeEnvVar, "1")
193 205
206+ commandLine := newMutableCommandLineStub()
207+ configureCommandLine(commandLine)
208+
209+ if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); strings.Contains(got, "WebAuth") {
210+ t.Fatalf("disable-features = %q, should not contain WebAuth", got)
211+ }
212+ if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); strings.Contains(got, "WebAuth") {
213+ t.Fatalf("disable-blink-features = %q, should not contain WebAuth", got)
214+ }
215+}
216+
217+func TestConfigureCommandLine_WebAuthnDisablePreservesExistingDisableFeatures(t *testing.T) {
218+ commandLine := newMutableCommandLineStub()
219+ commandLine.AppendSwitchWithValue(chromiumDisableFeaturesSwitch, "ExistingFeature,WebAuth")
220+ commandLine.AppendSwitchWithValue(chromiumDisableBlinkFeaturesSwitch, "ExistingBlinkFeature,WebAuth")
221+
222+ configureCommandLine(commandLine)
223+
224+ if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != "ExistingFeature,WebAuth" {
225+ t.Fatalf("disable-features = %q, want %q", got, "ExistingFeature,WebAuth")
226+ }
227+ if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != "ExistingBlinkFeature,WebAuth" {
228+ t.Fatalf("disable-blink-features = %q, want %q", got, "ExistingBlinkFeature,WebAuth")
229+ }
230+}
231+
232+func TestAppendUniqueCommaSeparatedSwitchValues(t *testing.T) {
233+ t.Run("trims whitespace and skips empty existing values", func(t *testing.T) {
234+ commandLine := newMutableCommandLineStub()
235+ commandLine.AppendSwitchWithValue("disable-features", " , ExistingFeature , ")
236+
237+ appendUniqueCommaSeparatedSwitchValues(commandLine, "disable-features", "FeatureA")
238+
239+ want := "ExistingFeature,FeatureA"
240+ if got := commandLine.GetSwitchValue("disable-features"); got != want {
241+ t.Fatalf("disable-features = %q, want %q", got, want)
242+ }
243+ })
244+
245+ t.Run("deduplicates existing and appended values", func(t *testing.T) {
246+ commandLine := newMutableCommandLineStub()
247+ commandLine.AppendSwitchWithValue("disable-features", "FeatureA,FeatureA")
248+
249+ appendUniqueCommaSeparatedSwitchValues(commandLine, "disable-features", "FeatureA", "FeatureB")
250+
251+ want := "FeatureA,FeatureB"
252+ if got := commandLine.GetSwitchValue("disable-features"); got != want {
253+ t.Fatalf("disable-features = %q, want %q", got, want)
254+ }
255+ })
256+
257+ t.Run("empty appended values are no-op", func(t *testing.T) {
258+ commandLine := newMutableCommandLineStub()
259+ commandLine.AppendSwitchWithValue("disable-features", "ExistingFeature")
260+
261+ appendUniqueCommaSeparatedSwitchValues(commandLine, "disable-features")
262+
263+ if got := commandLine.GetSwitchValue("disable-features"); got != "ExistingFeature" {
264+ t.Fatalf("disable-features = %q, want ExistingFeature", got)
265+ }
266+ })
267+
268+ t.Run("nil command line is no-op", func(_ *testing.T) {
269+ appendUniqueCommaSeparatedSwitchValues(nil, "disable-features", "FeatureA")
270+ })
271+}
272+
273+func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) {
194 commandLine := newMutableCommandLineStub()274 commandLine := newMutableCommandLineStub()
195 commandLine.AppendSwitchWithValue("type", "renderer")275 commandLine.AppendSwitchWithValue("type", "renderer")
196 276
@@ -200,3 +280,21 @@ func TestDumberBPH_OnBeforeChildProcessLaunch_AppendsNoZygote(t *testing.T) {
200 t.Fatal("expected no-zygote switch to be appended for child processes")280 t.Fatal("expected no-zygote switch to be appended for child processes")
201 }281 }
202 }282 }
283+
284+func TestDumberBPH_OnBeforeChildProcessLaunch_DisablesWebAuthnByDefault(t *testing.T) {
285+ commandLine := newMutableCommandLineStub()
286+ commandLine.AppendSwitchWithValue("type", "renderer")
287+
288+ (&dumberBPH{engine: &Engine{ctx: context.Background()}}).OnBeforeChildProcessLaunch(commandLine)
289+
290+ want := "WebAuth"
291+ if got := commandLine.GetSwitchValue(chromiumDisableFeaturesSwitch); got != want {
292+ t.Fatalf("disable-features = %q, want %q", got, want)
293+ }
294+ if got := commandLine.GetSwitchValue(chromiumDisableBlinkFeaturesSwitch); got != want {
295+ t.Fatalf("disable-blink-features = %q, want %q", got, want)
296+ }
297+ if !commandLine.HasSwitch("no-zygote") {
298+ t.Fatal("expected no-zygote switch to be appended")
299+ }
300+}
M internal/infrastructure/cef/env.go +8 -1
@@ -5,7 +5,10 @@ import (
55 "strings"
66 )
77
8-const cefExternalBeginFrameEnvVar = "DUMBER_CEF_EXTERNAL_BEGIN_FRAME"
8+const (
9+ cefExternalBeginFrameEnvVar = "DUMBER_CEF_EXTERNAL_BEGIN_FRAME"
10+ cefEnableWebAuthnUnsafeEnvVar = "DUMBER_CEF_ENABLE_WEBAUTHN_UNSAFE"
11+)
912
1013 // envBoolEnabled returns true when the given environment variable is set
1114 // to a truthy value ("1", "true", "yes", "on").
@@ -21,3 +24,7 @@ func envBoolEnabled(envVar string) bool {
2124 func externalBeginFrameEnabled() bool {
2225 return envBoolEnabled(cefExternalBeginFrameEnvVar)
2326 }
27+
28+func cefWebAuthnUnsafeEnabled() bool {
29+ return envBoolEnabled(cefEnableWebAuthnUnsafeEnvVar)
30+}
@@ -5,7 +5,10 @@ import (
5 "strings"5 "strings"
6 )6 )
7 7
8-const cefExternalBeginFrameEnvVar = "DUMBER_CEF_EXTERNAL_BEGIN_FRAME"8+const (
9+ cefExternalBeginFrameEnvVar = "DUMBER_CEF_EXTERNAL_BEGIN_FRAME"
10+ cefEnableWebAuthnUnsafeEnvVar = "DUMBER_CEF_ENABLE_WEBAUTHN_UNSAFE"
11+)
9 12
10 // envBoolEnabled returns true when the given environment variable is set13 // envBoolEnabled returns true when the given environment variable is set
11 // to a truthy value ("1", "true", "yes", "on").14 // to a truthy value ("1", "true", "yes", "on").
@@ -21,3 +24,7 @@ func envBoolEnabled(envVar string) bool {
21 func externalBeginFrameEnabled() bool {24 func externalBeginFrameEnabled() bool {
22 return envBoolEnabled(cefExternalBeginFrameEnvVar)25 return envBoolEnabled(cefExternalBeginFrameEnvVar)
23 }26 }
27+
28+func cefWebAuthnUnsafeEnabled() bool {
29+ return envBoolEnabled(cefEnableWebAuthnUnsafeEnvVar)
30+}
M internal/infrastructure/cef/env_test.go +31 -0
@@ -32,3 +32,34 @@ func TestExternalBeginFrameEnabledIsOptIn(t *testing.T) {
3232 })
3333 }
3434 }
35+
36+func TestCEFWebAuthnUnsafeEnabledIsOptIn(t *testing.T) {
37+ tests := []struct {
38+ name string
39+ value string
40+ want bool
41+ }{
42+ {name: "1", value: "1", want: true},
43+ {name: "true", value: "true", want: true},
44+ {name: "yes", value: "yes", want: true},
45+ {name: "on", value: "on", want: true},
46+ {name: "TRUE", value: "TRUE", want: true},
47+ {name: " true ", value: " true ", want: true},
48+ {name: "", value: "", want: false},
49+ {name: "false", value: "false", want: false},
50+ {name: "0", value: "0", want: false},
51+ {name: "no", value: "no", want: false},
52+ {name: "off", value: "off", want: false},
53+ {name: "random", value: "random", want: false},
54+ }
55+
56+ for _, tt := range tests {
57+ t.Run(tt.name, func(t *testing.T) {
58+ t.Setenv(cefEnableWebAuthnUnsafeEnvVar, tt.value)
59+
60+ if got := cefWebAuthnUnsafeEnabled(); got != tt.want {
61+ t.Fatalf("cefWebAuthnUnsafeEnabled(%q) = %v, want %v", tt.value, got, tt.want)
62+ }
63+ })
64+ }
65+}
@@ -32,3 +32,34 @@ func TestExternalBeginFrameEnabledIsOptIn(t *testing.T) {
32 })32 })
33 }33 }
34 }34 }
35+
36+func TestCEFWebAuthnUnsafeEnabledIsOptIn(t *testing.T) {
37+ tests := []struct {
38+ name string
39+ value string
40+ want bool
41+ }{
42+ {name: "1", value: "1", want: true},
43+ {name: "true", value: "true", want: true},
44+ {name: "yes", value: "yes", want: true},
45+ {name: "on", value: "on", want: true},
46+ {name: "TRUE", value: "TRUE", want: true},
47+ {name: " true ", value: " true ", want: true},
48+ {name: "", value: "", want: false},
49+ {name: "false", value: "false", want: false},
50+ {name: "0", value: "0", want: false},
51+ {name: "no", value: "no", want: false},
52+ {name: "off", value: "off", want: false},
53+ {name: "random", value: "random", want: false},
54+ }
55+
56+ for _, tt := range tests {
57+ t.Run(tt.name, func(t *testing.T) {
58+ t.Setenv(cefEnableWebAuthnUnsafeEnvVar, tt.value)
59+
60+ if got := cefWebAuthnUnsafeEnabled(); got != tt.want {
61+ t.Fatalf("cefWebAuthnUnsafeEnabled(%q) = %v, want %v", tt.value, got, tt.want)
62+ }
63+ })
64+ }
65+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close