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.
| Field | Meaning |
|---|---|
equivalent_domains | The effective custom equivalent-domain result, kept for compatible clients and old-data fallback. |
custom_equivalent_domains | Full user edit state, including rule id, domains, and exclusion state. The Web Vault mainly uses this to restore editing state. |
excluded_global_equivalent_domains | List of global rule type values excluded by the user. It stores only exclusions, not a copy of all global rules. |
updated_at | Domain 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:
| File | Maintainer | Purpose |
|---|---|---|
src/static/global_domains.bitwarden.json | Generated by GitHub Action | Official global equivalent-domain rules synced from bitwarden/server. |
src/static/global_domains.bitwarden.meta.json | Generated by GitHub Action | Sync source, ref, generation time, rule count, and source file URLs. |
src/static/global_domains.custom.json | NodeWarden maintainers or contributors | NodeWarden-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:
{"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:
- Runs
npm run domains:syncto fetch official rules frombitwarden/server. - Writes only
src/static/global_domains.bitwarden.jsonandsrc/static/global_domains.bitwarden.meta.json. - Checks that
src/static/global_domains.custom.jsondid not change, then opens a sync PR.
Maintenance boundaries:
| Scenario | Update |
|---|---|
| Follow official Bitwarden global rule changes | Run the sync Action and update global_domains.bitwarden.json plus global_domains.bitwarden.meta.json. |
| Add a NodeWarden-specific global rule | Manual PR to global_domains.custom.json. |
| Let one user change their own matching behavior | Save 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.cssrc/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:
npm run domains:syncWith a specific Bitwarden ref:
npm run domains:sync -- --ref mainRuntime merge
The runtime entry is src/services/domain-rules.ts.
At bundle time, the service imports:
global_domains.bitwarden.jsonglobal_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 field | Source |
|---|---|
globalEquivalentDomains | Static global rules plus the user's exclusion state. Excluded rules can still be returned with excluded: true. |
customEquivalentDomains | Full user custom rule state from D1. |
equivalentDomains | Effective 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:
- Read the current user's existing
domain_settings. - Normalize submitted custom rules and excluded global rules.
- Derive
equivalent_domainsfrom non-excluded custom rules. - Save
equivalent_domains,custom_equivalent_domains, andexcluded_global_equivalent_domainstogether. - Update the user revision date so sync clients know the account state changed.
- 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.jsonPRs. - User exclusions are stored in D1 and remain effective after backup restore.