From aeba4fc15848cffcacaeafec36b96cd2cc8f0e7e Mon Sep 17 00:00:00 2001 From: Sayan- <1415138+Sayan-@users.noreply.github.com> Date: Mon, 9 Nov 2026 22:53:11 +0000 Subject: [PATCH 1/4] Strip Kernel browser settings from Local State when loading a profile Co-Authored-By: Claude Opus 5.5 --- server/cmd/api/api/chromium_configure.go | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/server/cmd/api/api/chromium_configure.go b/server/cmd/api/api/chromium_configure.go index 1e618597..341a6100 100644 --- a/server/cmd/api/api/chromium_configure.go +++ b/server/cmd/api/api/chromium_configure.go @@ -1,6 +1,7 @@ package api import ( + "bytes" "context" "encoding/json" "errors" @@ -320,6 +321,9 @@ func (s *ApiService) chromiumConfigureRestart(ctx context.Context, st *chromiumC return cfg500ConfigureStep(chromiumConfigureStepProfile, err.Error()) } } + if err := stripProfileKernelLocalState(preparedProfile); err != nil { + logger.FromContext(ctx).Warn("failed to strip Kernel settings from profile Local State", "error", err) + } if err := chromiumInstallPreparedProfile(preparedProfile); err != nil { return cfg500ConfigureStep(chromiumConfigureStepProfile, err.Error()) } @@ -718,6 +722,44 @@ func stripProfileSessionRestore(preparedDir string) error { return nil } +// stripProfileKernelLocalState removes the top-level "kernel" object from the +// prepared profile's Local State. Kernel's Chromium build stores browser-wide +// settings set over CDP there (fingerprint config, request header rules), and +// they belong to the browser that set them, not to the profile. Only the live +// copy is touched; the stored archive is unchanged. +func stripProfileKernelLocalState(preparedDir string) error { + path := filepath.Join(preparedDir, "Local State") + data, err := os.ReadFile(path) + if os.IsNotExist(err) { + return nil + } + if err != nil { + return fmt.Errorf("read Local State: %w", err) + } + + dec := json.NewDecoder(bytes.NewReader(data)) + dec.UseNumber() + var state map[string]any + if err := dec.Decode(&state); err != nil { + return fmt.Errorf("parse Local State: %w", err) + } + if _, ok := state["kernel"]; !ok { + return nil + } + delete(state, "kernel") + + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + if err := enc.Encode(state); err != nil { + return fmt.Errorf("marshal Local State: %w", err) + } + if err := os.WriteFile(path, bytes.TrimRight(buf.Bytes(), "\n"), 0o644); err != nil { + return fmt.Errorf("write Local State: %w", err) + } + return nil +} + func chromiumInstallPreparedProfile(preparedDir string) error { if preparedDir == "" { return nil From fd35e98574a69692f71d2db74f0e85b50b1787e0 Mon Sep 17 00:00:00 2001 From: Sayan- <1415138+Sayan-@users.noreply.github.com> Date: Mon, 9 Nov 2026 22:53:11 +0000 Subject: [PATCH 2/4] Test stripping Kernel settings from profile Local State Co-Authored-By: Claude Opus 5.5 --- server/cmd/api/api/chromium_configure_test.go | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/server/cmd/api/api/chromium_configure_test.go b/server/cmd/api/api/chromium_configure_test.go index 607acafa..5d3ad051 100644 --- a/server/cmd/api/api/chromium_configure_test.go +++ b/server/cmd/api/api/chromium_configure_test.go @@ -162,6 +162,38 @@ func TestStripProfileSessionRestore(t *testing.T) { require.NoError(t, stripProfileSessionRestore(prepared)) } +func TestStripProfileKernelLocalState(t *testing.T) { + prepared := t.TempDir() + path := filepath.Join(prepared, "Local State") + input := `{"kernel":{"fingerprint_config":{"version":1},"request_header_rules":{"generation":2}},"browser":{"last_redirect_origin":"https://a.test/?x=1&y=2"},"variations_seed_date":13350000000000000001}` + require.NoError(t, os.WriteFile(path, []byte(input), 0o600)) + + require.NoError(t, stripProfileKernelLocalState(prepared)) + + got, err := os.ReadFile(path) + require.NoError(t, err) + require.JSONEq(t, `{"browser":{"last_redirect_origin":"https://a.test/?x=1&y=2"},"variations_seed_date":13350000000000000001}`, string(got)) + require.Contains(t, string(got), "13350000000000000001") + info, err := os.Stat(path) + require.NoError(t, err) + require.Equal(t, os.FileMode(0o600), info.Mode().Perm(), "rewrite keeps the existing file mode") + + // Without Kernel settings the file is left byte-for-byte unchanged. + untouched := "{\"browser\": {}}\n" + require.NoError(t, os.WriteFile(path, []byte(untouched), 0o600)) + require.NoError(t, stripProfileKernelLocalState(prepared)) + got, err = os.ReadFile(path) + require.NoError(t, err) + require.Equal(t, untouched, string(got)) + + // A profile without Local State is a no-op, not an error. + require.NoError(t, os.Remove(path)) + require.NoError(t, stripProfileKernelLocalState(prepared)) + + require.NoError(t, os.WriteFile(path, []byte("{not json"), 0o600)) + require.ErrorContains(t, stripProfileKernelLocalState(prepared), "parse Local State") +} + func TestChromiumValidateFlags(t *testing.T) { valid := `{"flags":["--kiosk"]}` plan, err := chromiumValidateFlags(&valid) From f5b5589a000cc7394bdc366468918fcb1dc541b5 Mon Sep 17 00:00:00 2001 From: Sayan- <1415138+Sayan-@users.noreply.github.com> Date: Mon, 9 Nov 2026 23:20:37 +0000 Subject: [PATCH 3/4] Run profile cleanup for configure through one helper Co-Authored-By: Claude Opus 5.5 --- server/cmd/api/api/chromium_configure.go | 25 +++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/server/cmd/api/api/chromium_configure.go b/server/cmd/api/api/chromium_configure.go index 341a6100..75b59495 100644 --- a/server/cmd/api/api/chromium_configure.go +++ b/server/cmd/api/api/chromium_configure.go @@ -316,13 +316,8 @@ func (s *ApiService) chromiumConfigureRestart(ctx context.Context, st *chromiumC if err != nil { return cfg500ConfigureStep(chromiumConfigureStepProfile, err.Error()) } - if spec.needsNav { - if err := stripProfileSessionRestore(preparedProfile); err != nil { - return cfg500ConfigureStep(chromiumConfigureStepProfile, err.Error()) - } - } - if err := stripProfileKernelLocalState(preparedProfile); err != nil { - logger.FromContext(ctx).Warn("failed to strip Kernel settings from profile Local State", "error", err) + if err := sanitizePreparedProfile(ctx, preparedProfile, spec.needsNav); err != nil { + return cfg500ConfigureStep(chromiumConfigureStepProfile, err.Error()) } if err := chromiumInstallPreparedProfile(preparedProfile); err != nil { return cfg500ConfigureStep(chromiumConfigureStepProfile, err.Error()) @@ -712,6 +707,22 @@ func chromiumPrepareProfileArchive(profilePath string, strip int) (preparedDir s return preparedDir, cleanup, nil } +// sanitizePreparedProfile edits the unpacked profile before it replaces +// user-data. Session restore is stripped only when a start_url will be +// navigated; Kernel settings are always stripped from Local State, and a +// failure there is logged rather than failing the load. +func sanitizePreparedProfile(ctx context.Context, preparedDir string, stripSessionRestore bool) error { + if stripSessionRestore { + if err := stripProfileSessionRestore(preparedDir); err != nil { + return err + } + } + if err := stripProfileKernelLocalState(preparedDir); err != nil { + logger.FromContext(ctx).Warn("failed to strip Kernel settings from profile Local State", "error", err) + } + return nil +} + // stripProfileSessionRestore deletes the prepared profile's Default/Sessions so // Chrome cannot restore its saved tabs after the restart and race the start_url // navigation. Only the live copy is touched; the stored archive is unchanged. From 64e3b3f2050914b79110f672df160ecd165e5c73 Mon Sep 17 00:00:00 2001 From: Sayan- <1415138+Sayan-@users.noreply.github.com> Date: Mon, 9 Nov 2026 23:20:37 +0000 Subject: [PATCH 4/4] Test the profile cleanup configure runs before install Co-Authored-By: Claude Opus 5.5 --- server/cmd/api/api/chromium_configure_test.go | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/server/cmd/api/api/chromium_configure_test.go b/server/cmd/api/api/chromium_configure_test.go index 5d3ad051..89084b6c 100644 --- a/server/cmd/api/api/chromium_configure_test.go +++ b/server/cmd/api/api/chromium_configure_test.go @@ -2,6 +2,7 @@ package api import ( "bytes" + "context" "errors" "io" "mime/multipart" @@ -194,6 +195,44 @@ func TestStripProfileKernelLocalState(t *testing.T) { require.ErrorContains(t, stripProfileKernelLocalState(prepared), "parse Local State") } +func TestSanitizePreparedProfile(t *testing.T) { + newPrepared := func(t *testing.T, localState string) string { + prepared := t.TempDir() + sessions := filepath.Join(prepared, "Default", "Sessions") + require.NoError(t, os.MkdirAll(sessions, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(sessions, "Session_123"), []byte("tabs"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(prepared, "Local State"), []byte(localState), 0o644)) + return prepared + } + const withKernel = `{"kernel":{"fingerprint_config":{"version":1}},"browser":{}}` + + t.Run("strips Kernel settings and session restore before navigation", func(t *testing.T) { + prepared := newPrepared(t, withKernel) + require.NoError(t, sanitizePreparedProfile(context.Background(), prepared, true)) + + require.NoDirExists(t, filepath.Join(prepared, "Default", "Sessions")) + got, err := os.ReadFile(filepath.Join(prepared, "Local State")) + require.NoError(t, err) + require.JSONEq(t, `{"browser":{}}`, string(got)) + }) + + t.Run("strips Kernel settings and keeps session restore without navigation", func(t *testing.T) { + prepared := newPrepared(t, withKernel) + require.NoError(t, sanitizePreparedProfile(context.Background(), prepared, false)) + + require.DirExists(t, filepath.Join(prepared, "Default", "Sessions")) + got, err := os.ReadFile(filepath.Join(prepared, "Local State")) + require.NoError(t, err) + require.JSONEq(t, `{"browser":{}}`, string(got)) + }) + + t.Run("unreadable Local State does not fail the load", func(t *testing.T) { + prepared := newPrepared(t, "{not json") + require.NoError(t, sanitizePreparedProfile(context.Background(), prepared, true)) + require.NoDirExists(t, filepath.Join(prepared, "Default", "Sessions")) + }) +} + func TestChromiumValidateFlags(t *testing.T) { valid := `{"flags":["--kiosk"]}` plan, err := chromiumValidateFlags(&valid)