Skip to content

Domain Rules

Domain rules provide Bitwarden-compatible equivalent-domain behavior. They do not decrypt passwords and do not change cipher data. They only tell clients and the Web Vault which login domains can be treated as one group for matching, autofill candidates, and settings display.

What users do

Regular users manage domain rules in Settings -> Domain Rules:

  • Add custom equivalent-domain groups, such as grouping two internal system domains.
  • Exclude a global equivalent-domain rule they do not want to participate in matching.
  • After saving, the server updates that user's domain settings and revision date. Other clients or Web Vault sessions see the new result after sync.

These settings are per-user. They do not modify static global rule files in the repository and do not affect other users.

How data is stored

User domain settings are stored in the D1 domain_settings table.

FieldMeaning
equivalent_domainsThe effective custom equivalent-domain result, kept for compatible clients and old-data fallback.
custom_equivalent_domainsFull user edit state, including rule id, domains, and exclusion state. The Web Vault mainly uses this to restore editing state.
excluded_global_equivalent_domainsList of global rule type values excluded by the user. It stores only exclusions, not a copy of all global rules.
updated_atDomain settings update time. Saving also advances the user revision date.

equivalent_domains and custom_equivalent_domains look similar, but they are not duplicates. The first is the effective compatible result; the second is the editable user state. Keep both in sync when changing this area, or the Web Vault display and client sync output can diverge.

Static global rule files

The project has three static global rule files:

FileMaintainerPurpose
src/static/global_domains.bitwarden.jsonGenerated by GitHub ActionOfficial global equivalent-domain rules synced from bitwarden/server.
src/static/global_domains.bitwarden.meta.jsonGenerated by GitHub ActionSync source, ref, generation time, rule count, and source file URLs.
src/static/global_domains.custom.jsonNodeWarden maintainers or contributorsNodeWarden-specific global rules that must not be overwritten by the sync script.

To add a NodeWarden-built-in global rule, submit a PR against src/static/global_domains.custom.json. Do not edit src/static/global_domains.bitwarden.json; it is generated and will be overwritten by the next upstream sync.

Custom global rule type values should avoid conflicts with official Bitwarden values. NodeWarden currently uses negative numbers, for example:

json
{"type":-10001,"domains":["nodewarden.example","nw.example"],"excluded":false,"source":"nodewarden"}

What automatic sync updates

.github/workflows/sync-global-domains.yml runs on a schedule and can also be triggered manually.

It does three things:

  1. Runs npm run domains:sync to fetch official rules from bitwarden/server.
  2. Writes only src/static/global_domains.bitwarden.json and src/static/global_domains.bitwarden.meta.json.
  3. Checks that src/static/global_domains.custom.json did not change, then opens a sync PR.

Maintenance boundaries:

ScenarioUpdate
Follow official Bitwarden global rule changesRun the sync Action and update global_domains.bitwarden.json plus global_domains.bitwarden.meta.json.
Add a NodeWarden-specific global ruleManual PR to global_domains.custom.json.
Let one user change their own matching behaviorSave in the settings page, writing D1 domain_settings; no repository file changes.

The sync script scripts/sync-global-domains.mjs reads these files from the Bitwarden repository:

  • src/Core/Enums/GlobalEquivalentDomainsType.cs
  • src/Core/Utilities/StaticStore.cs

It parses enum type numbers, parses domain groups from StaticStore.cs, and produces the JSON used by NodeWarden.

Manual local sync:

powershell
npm run domains:sync

With a specific Bitwarden ref:

powershell
npm run domains:sync -- --ref main

Runtime merge

The runtime entry is src/services/domain-rules.ts.

At bundle time, the service imports:

  • global_domains.bitwarden.json
  • global_domains.custom.json

It merges them into globalDomains. When a user requests /api/settings/domains or /settings/domains, the server reads that user's D1 settings and constructs:

Response fieldSource
globalEquivalentDomainsStatic global rules plus the user's exclusion state. Excluded rules can still be returned with excluded: true.
customEquivalentDomainsFull user custom rule state from D1.
equivalentDomainsEffective equivalent groups produced from non-excluded custom rules and non-excluded global rules related to custom domains.

The merge normalizes domains, deduplicates them, then merges overlapping groups with connected components. For example, if a user groups a.example with b.example and a global rule groups b.example with c.example, the final result becomes one connected equivalent-domain group.

What happens on save

The save endpoint lives in src/handlers/domains.ts. It accepts both camelCase and PascalCase fields, so the Web Vault and Bitwarden-style clients can submit data.

Save flow:

  1. Read the current user's existing domain_settings.
  2. Normalize submitted custom rules and excluded global rules.
  3. Derive equivalent_domains from non-excluded custom rules.
  4. Save equivalent_domains, custom_equivalent_domains, and excluded_global_equivalent_domains together.
  5. Update the user revision date so sync clients know the account state changed.
  6. Re-read and return the merged domain rule response.

If a submitted payload omits a field, the server tries to preserve the current value rather than clearing it. This is for compatibility with clients that submit partial fields.

Backup and restore

Instance backups export domain_settings. A user's custom rules, excluded global rules, and update time are restored with the instance.

Static global rule files are not user data. They follow the code version:

  • Official Bitwarden rules are updated by the sync Action.
  • NodeWarden custom global rules are maintained through global_domains.custom.json PRs.
  • User exclusions are stored in D1 and remain effective after backup restore.

Released under the LGPL-3.0 License.