fix(ui): tell Bitwarden to skip config and secret fields

The Input and Textarea primitives already opted config and secret fields
out of 1Password and LastPass autofill (data-1p-ignore / data-lpignore)
whenever autocomplete was off, but Bitwarden had no opt-out hint anywhere,
so it kept offering to fill and save API keys, tokens and integration
URLs. Emit data-bwignore alongside the existing hints, which covers every
field flowing through the primitives. Pin explicit autocomplete on the
change-password dialog so genuine credentials still autofill, and add the
ignore hints to the export passphrase and the few new-password admin
config fields that opt in directly.
This commit is contained in:
Marc-André Bombeck committed 2026-06-30 18:32:35 +02:00
1 parent 8ce98a802f
commit fa2e2c6ba2
9 files changed
+31 -3

No files matched your search

@@ -102,6 +102,7 @@ export function GlitchtipSection() {
spellCheck={false}
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
disabled={updateSettings.isPending}
/>
</div>
@@ -121,6 +122,7 @@ export function GlitchtipSection() {
spellCheck={false}
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
disabled={updateSettings.isPending}
/>
</div>
+2
View File
@@ -105,6 +105,7 @@ export function UmamiSection() {
spellCheck={false}
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
disabled={updateSettings.isPending}
/>
</div>
@@ -122,6 +123,7 @@ export function UmamiSection() {
spellCheck={false}
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
disabled={updateSettings.isPending}
/>
</div>
@@ -144,6 +144,7 @@ export function WebPushVapidSection() {
spellCheck={false}
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
disabled={updateSettings.isPending}
/>
</div>
@@ -163,6 +164,7 @@ export function WebPushVapidSection() {
spellCheck={false}
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
disabled={updateSettings.isPending}
/>
</div>
@@ -182,6 +184,7 @@ export function WebPushVapidSection() {
spellCheck={false}
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
disabled={updateSettings.isPending}
/>
</div>
@@ -527,6 +527,7 @@ export function AccountSection() {
</Label>
<PasswordInput
id="current-password"
autoComplete="current-password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
/>
@@ -537,6 +538,7 @@ export function AccountSection() {
</Label>
<PasswordInput
id="new-password"
autoComplete="new-password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
@@ -547,6 +549,7 @@ export function AccountSection() {
</Label>
<PasswordInput
id="confirm-password"
autoComplete="new-password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
@@ -522,6 +522,9 @@ function FullBackupCard() {
"settings.sections.export.cards.fullBackup.passphrasePlaceholder",
)}
autoComplete="new-password"
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
className="h-9 text-sm"
/>
<p className="text-muted-foreground text-xs">
@@ -9,6 +9,7 @@ describe("<Input>", () => {
expect(html).toContain('autoComplete="off"');
expect(html).toContain('data-lpignore="true"');
expect(html).toContain('data-1p-ignore="true"');
expect(html).toContain('data-bwignore="true"');
});
it("respects an explicit autoComplete value and drops the ignore attrs", () => {
@@ -18,6 +19,7 @@ describe("<Input>", () => {
expect(html).toContain('autoComplete="email"');
expect(html).not.toContain('data-lpignore="true"');
expect(html).not.toContain('data-1p-ignore="true"');
expect(html).not.toContain('data-bwignore="true"');
});
it("explicit autoComplete=username keeps password manager active for login flows", () => {
@@ -27,6 +29,7 @@ describe("<Input>", () => {
expect(html).toContain('autoComplete="username"');
expect(html).not.toContain("lpignore");
expect(html).not.toContain("1p-ignore");
expect(html).not.toContain("bwignore");
});
it("derives inputMode=decimal from type=number", () => {
@@ -21,6 +21,7 @@ describe("<Textarea>", () => {
expect(html).toContain('autoComplete="off"');
expect(html).toContain('data-lpignore="true"');
expect(html).toContain('data-1p-ignore="true"');
expect(html).toContain('data-bwignore="true"');
});
it("defaults autoCapitalize to sentences for prose-like free-text input", () => {
@@ -51,6 +52,7 @@ describe("<Textarea>", () => {
expect(html).toContain('autoComplete="on"');
expect(html).not.toContain('data-lpignore="true"');
expect(html).not.toContain('data-1p-ignore="true"');
expect(html).not.toContain('data-bwignore="true"');
});
it("merges caller className via cn() without dropping defaults", () => {
+9 -2
View File
@@ -17,8 +17,14 @@ import { cn } from "@/lib/utils";
* because they already pass an explicit `autoComplete` value —
* `"username"`, `"email"`, `"current-password"`, `"new-password"`. When
* `autoComplete` is anything other than `"off"`, the LastPass /
* 1Password ignore attrs are dropped so password managers fill the
* field normally.
* 1Password / Bitwarden ignore attrs are dropped so password managers
* fill the field normally.
*
* v1.25.11 — extend the cross-manager opt-out to Bitwarden
* (`data-bwignore`) alongside the existing LastPass (`data-lpignore`)
* and 1Password (`data-1p-ignore`) hints, so config / secret fields
* (AI keys, bearer tokens, integration secrets, webhook URLs) are not
* offered for autofill or saved as a login by any of the three.
*
* v1.4.27 mobile pass: derive a sensible `inputMode` default from the
* `type` prop so every numeric / email / tel / url / search field
@@ -66,6 +72,7 @@ function Input({
inputMode={resolvedInputMode}
data-lpignore={skipAutofill ? "true" : undefined}
data-1p-ignore={skipAutofill ? "true" : undefined}
data-bwignore={skipAutofill ? "true" : undefined}
className={cn(
// v1.4.34 IW-G — floor at 44 px on mobile to clear WCAG 2.5.5;
// shrink to 40 px on sm+ where pointer precision is higher.
+4 -1
View File
@@ -34,7 +34,9 @@ import { cn } from "@/lib/utils";
* `<Input>`: HealthLog is a health-data app, and we don't want
* a password manager pasting the user's saved password into a
* free-text textarea that we then persist server-side. Caller
* can opt back in (e.g. `"on"`) per field.
* can opt back in (e.g. `"on"`) per field. When autofill is
* skipped, the LastPass / 1Password / Bitwarden ignore hints are
* emitted so none of the three offer to fill or save the field.
*
* Composition is via `cn(…)` so callers can override anything — the
* primitive defaults are the strict pass, not the only pass.
@@ -59,6 +61,7 @@ function Textarea({
spellCheck={resolvedSpellCheck}
data-lpignore={skipAutofill ? "true" : undefined}
data-1p-ignore={skipAutofill ? "true" : undefined}
data-bwignore={skipAutofill ? "true" : undefined}
className={cn(
// iOS zoom defence + WCAG 2.5.5 tap-target floor; see the
// primitive's docblock for the full reasoning.