The project's README from github.com/BareBrowser/bare-browser. As one markdown file: README.md.
Bare is a de-Googled Chromium build for Android, designed to give you more privacy, control and freedom on the web. It strips Google tracking, telemetry and AI integration while keeping powerful features like browser extensions and uBlock Origin. Bare also adds background video playback, the ability to save media from sites that normally block it, support for choosing your preferred download manager, and much more.
It is a patch series rather than a fork: 99 patches against one pinned revision of Chromium Desktop Android, so what this repository holds is exactly the difference between stock Chromium and Bare, and nothing else.
Built and used on a Pixel 10 Pro XL. Not affiliated with Google or the Chromium project.
153.0.7999.0 (commit 945b5115)is_desktop_android = true, target_cpu = "arm64"1.0.0-alpha.2, versionCode 801000035 (arm64) / 801000030 (32-bit arm)![]() |
![]() |
| Screenshot 1 | Screenshot 2 |
![]() |
![]() |
| Screenshot 3 | Screenshot 4 |
![]() |
![]() |
| Screenshot 5 | Screenshot 6 |
Bare is one person and one phone. There is no company behind it, nothing in the browser sells you anything, and there is nothing to upsell you to later, which is rather the point of it.
If it is useful to you, you can buy me a coffee. Entirely optional. It buys build hours and a second device to test on, and it changes nothing about the browser either way.
Chromium's Desktop Android build supports real browser extensions, which mobile Chrome does not. That makes it a genuinely better browser to live in, but it still ships Google's AI stack, phones home on a schedule, and hands your links to other apps. Bare fixes that as a patch series rather than a fork, so every change stays readable and reviewable.
Ninety-one patches against one pinned Chromium revision: crash fixes, the extensions toolbar on phone layouts, every Google callback and AI surface removed, and the features Bare adds on top.
The full list, patch by patch, is in docs/patches.md along with what is removed by build flag instead of by patch, and what is deliberately kept.
Measured on a Pixel 3 running Android 12 and an Android 17 emulator, by watching the sockets opened under Bare's own uid on a wiped profile. What follows is what was actually observed, not what was intended.
On a second and later start, with an established profile, one host is contacted without being
asked: update.googleapis.com. That is Chromium's component updater. It is kept deliberately,
because the components left registered are the ones that carry security or web compatibility:
| Component | Why it stays |
|---|---|
| CRLSet | certificate revocation |
| PKIMetadata | Certificate Transparency |
| SSLErrorAssistant | quality of the TLS interstitial |
| FileTypePolicies | download danger classification |
| OriginTrials | web compatibility |
| Widevine | DRM playback |
| SafetyTips, CrowdDeny | lookalike-domain and abusive-notification lists, applied locally |
Registration tells Google an install exists, along with a component id, a version, an install date and a cohort. Twelve other components were dropped in patch 0085 for buying nothing here: the Privacy Sandbox set, Google's omnibox suggestion model, the optimization-hints feed, shopping and checkout heuristics, on-device AI history strings, and two Safe Browsing lists that nothing can consult. Three more, ActorSafetyLists, Indigo and CaptchaProvider, are still registered because their purpose is not clear enough in the tree to remove them confidently.
On the first run of a fresh profile there is more, and almost all of it is uBlock Origin fetching the filter lists it ships enabled, from the list maintainers and their CDNs rather than from Google. That happens once and then on the extension's own schedule.
The update check contacts GitHub, and only after the welcome screen has been completed and an
update preference stored. It is at most one request a day, to a public release feed, sending
User-Agent: Bare/<version> and nothing else: no identifier, no cookies, no version in the
request. Choosing manual means nothing leaves the device until the Check for updates row is
tapped.
What no longer happens, each verified by re-running the same capture:
accounts.google.com, from the omnibox or at startup. Typing in the address
bar used to reach GAIA ListAccounts to decide whether to personalise suggestions. Separately, a
fresh profile used to POST to ListAccounts about a tenth of a second into every cold start,
before the welcome screen had been answered. Neither the omnibox nor the account reconcilor was
behind the startup one: two services asked for the cookie jar while the profile was still being
built, both only to report metrics, and reading the cookie jar fetched it from Google whenever
the cached answer was stale. Patch 0093 stops the read from reaching the network, so the answer
now comes from the cache. Visiting Google sites and signing in to them is unaffected.Traffic from Android itself and from Google Play Services is separate from all of this and is not affected by anything Bare does.
Safe Browsing does not work in this build, and no longer claims to. Chromium Android performs lookups through a handler that Google injects in its own build and that no public build has. The settings, the Safety Hub module and the promotional cards all described protection that never ran, so patch 0080 removed them. Bare shows no phishing or malware interstitial. Treat unknown links with the caution you would give a browser with no such feature, because that is what this is.
Builds run in an x86-64 Ubuntu 22.04 container. On Apple Silicon this means Rosetta emulation, and a full build takes hours.
Everything runs in the container. The Chromium checkout stays in its volume, because building
from a macOS bind mount is slow and unreliable; only small files cross to the host, through
exchange/, which is the one directory shared with the container.
1. Start the container.
./builder.sh build
./builder.sh start
2. Put what the build needs where the container can see it.
cp -R patches exchange/
cp third_party/ublock_origin/uBlockOrigin-1.73.0.crx exchange/
tools/version.py gn arm64 > exchange/version-args.gn
3. Fetch Chromium. Inside the container (./builder.sh shell). depot_tools is on the PATH
but not installed, so clone it first. This part is Chromium's own procedure rather than
anything specific to Bare. See the upstream Android build
instructions
if a step needs explaining. Expect hours and about 50 GB.
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git ~/depot_tools
mkdir -p /work/chromium && cd /work/chromium
fetch --nohooks android
cd src
git checkout 945b5115
gclient sync -D --force --reset
./build/install-build-deps.sh --android
4. Apply the series, and drop in the extension. uBlock Origin is kept out of the patches because a 4 MB signed binary as base64 would be unreviewable, so it is copied in separately. Without it the build fails at packaging.
git am /exchange/patches/*.patch
mkdir -p chrome/browser/resources/bare
cp /exchange/uBlockOrigin-1.73.0.crx chrome/browser/resources/bare/ublock_origin.crx
5. Configure. This is the complete configuration the published release was built with, nothing omitted:
target_os = "android"
target_cpu = "arm64"
is_desktop_android = true
is_official_build = true
is_debug = false
is_java_debug = false
is_component_build = false
dcheck_always_on = false
android_static_analysis = "off"
symbol_level = 0
blink_symbol_level = 0
v8_symbol_level = 0
use_remoteexec = false
use_reclient = false
proprietary_codecs = true
ffmpeg_branding = "Chrome"
use_mlkit_for_aicore = false
enable_glic_internal_resources = false
enable_reporting = false
enable_service_discovery = false
enable_mdns = false
enable_arcore = false
enable_cardboard = false
enable_openxr = false
chrome_public_manifest_package = "org.barebrowser"
android_override_version_code = "801000035"
android_override_version_name = "1.0.0-alpha.2"
The last two lines are what tools/version.py gn arm64 emits, and they matter: they are written into
the manifest, so a build without them will not match the release.
The 32-bit build differs in exactly three lines: target_cpu = "arm", the version code
801000020 that tools/version.py gn arm emits, and clang_use_default_sample_profile = false,
which stops GN demanding an AFDO profile this checkout never syncs.
mkdir -p out/Bare
$EDITOR out/Bare/args.gn # paste the block above
gn gen out/Bare
autoninja -C out/Bare chrome_public_apk
6. Compare. Your APK will not be byte-identical to the published one, because that one carries a signature only its key can produce. Everything else should match:
tools/apk-content-hash.py out/Bare/apks/ChromePublic.apk Bare-1.0.0-alpha.1.apk
See BUILDING.md for the rest of the workflow, including versioning and how releases are signed.
Three questions, and they have different answers.
Is this the file that was published? Compare its SHA-256 against the one on the release page. That catches a truncated or tampered download and nothing else: the hash is published by the same person as the file, so on its own it proves nothing about where the file came from.
Is it genuinely Bare? Check who signed it. Nothing signed by any other key is Bare.
apksigner verify --print-certs Bare-1.0.0-alpha.1.apk
SHA-256 ae:2a:0e:7f:b7:a1:32:ec:51:7d:26:a8:e7:c8:3d:27
5e:83:74:7b:0a:77:7d:4a:42:22:5b:1d:34:32:71:a0
subject CN=Bare Browser, O=Bare Browser, OU=Release
key RSA 4096
Releases before v1.0.0-alpha.1 were signed with Chromium's debug key, which is checked into
Chromium's tree and therefore public. Anyone can build an APK that installs as an update over one
of those. If you are running a Chromium Extend build, uninstall it rather than updating; a Bare
release cannot replace it in place anyway, because the signing identity deliberately changed.
Can you rebuild it yourself? Yes, and that is the only one of the three that proves where a binary came from. The signature is deliberately not reproducible, since nobody but the key holder can produce it, but everything else is. Follow Build it yourself exactly, including the version arguments, then compare:
tools/apk-content-hash.py out/Bare/apks/ChromePublic.apk Bare-1.0.0-alpha.1.apk
It hashes every entry except the signature, so two builds of the same source agree whoever signed them. What has been measured, and what has not.
kMaxSize in
chrome/browser/ui/android/extensions/extension_action_popup_contents.cc is hardcoded to
800dp. Two attempts to bound it failed; the second stopped popups rendering entirely, so
both were reverted. Needs instrumenting rather than guessing.ToolbarPhone has none of
the ToolbarWidthConsumer negotiation ToolbarTablet uses. Unpinning all but one extension
works around it.http(s) navigations stay in the browser. Some OAuth flows and
deep links legitimately expect an app handoff and will no longer get one. Explicit schemes
(intent://, tel:, mailto:, sms:) are untouched.kDefaultSearchProviderData persisted and
DefaultSearchManager prefers it, so upgrading over an older install keeps Google until you
change it in Settings → Search engine.ChromeDownloadManagerDelegate carries no cookie. Passing credentials needs a different
interception layer, not an extra argument. The setting's own summary says so.Fido.FIDO2_PRIVILEGED_API, is restricted to Google-signed browsers, so a self-built
Chromium is refused with ApiException: 17; passkeys held in Google Password Manager are
therefore out of reach, and no patch here can change that. Android's Credential Manager is a
separate path and it does reach third-party providers such as Bitwarden, 1Password and Proton
Pass. Those providers only complete a passkey for a browser they recognise, keyed on package
name plus signing certificate, and Bare is not on any published list yet. The first attempt
fails with "browser is not recognized"; choose Trust when prompted, or add
org.barebrowser by hand under Bitwarden → Settings → Autofill → Privileged apps. If you
had trusted an earlier self-built Bare you will see "browser signature does not match"
instead, because release builds are signed with a different key than local ones: delete the
stale entry, then trust it again..m3u8 or .mpd is a list of where the pieces
are, and a lone .ts or .m4s segment is not playable on its own, so both are refused rather
than handed over as a download that would disappoint. Assembling a segmented stream is the same
missing job as above.Each has an exact file and line reference in docs/stage-1-2.md: