Skip to content

Commit 6e599c4

Browse files
authored
Merge branch 'main' into ac/pm-35351/post-public-members-no-longer-works-on-self-hosts
2 parents 398e210 + 53dc0c4 commit 6e599c4

26 files changed

Lines changed: 1644 additions & 70 deletions

.claude/CONTRIBUTING.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Contributing Claude Context to This Repo
2+
3+
Every time you catch Claude making the same mistake twice, explain the same convention in chat, or hand a teammate a mental map they didn't have — that's knowledge worth encoding. This guide covers what belongs in this repo's `.claude/`, where to put it, and how to land it alongside the code it describes.
4+
5+
## When to contribute here vs. elsewhere
6+
7+
Ask: **is this knowledge specific to this codebase, or generic enough to work across repos?**
8+
9+
- **Specific to this codebase** → contribute here, in `.claude/`.
10+
Example: "how we add a new cipher type," "how our feature-flag system works."
11+
- **Generic, reusable across repos**[`bitwarden/ai-plugins`](https://github.com/bitwarden/ai-plugins) — persona plugins (e.g., a code-review agent), tool integrations, or shared utilities.
12+
13+
When unsure, keep it here. Promoting up to `ai-plugins` later is easier than pulling it back — see its [CONTRIBUTING.md](https://github.com/bitwarden/ai-plugins/blob/main/CONTRIBUTING.md) when you're ready.
14+
15+
## Choose scope, then shape
16+
17+
### 1. Scope — where does it apply?
18+
19+
This is a monorepo. Claude loads every `CLAUDE.md` and `CLAUDE.local.md` by [walking up from the working directory](https://code.claude.com/docs/en/memory#how-claude-md-files-load) — looking in each ancestor directly, not in a nested `.claude/` subdirectory. Files below the working directory (including nested `.claude/skills/`) are loaded lazily when Claude reads into that subtree. Use that hierarchy:
20+
21+
- **Applies everywhere in this repo** → root `CLAUDE.md` or `.claude/skills/`
22+
- **Applies only within one app, library, utility, or subtree** → nested `CLAUDE.md` or `.claude/skills/` in that directory
23+
24+
Push rules as deep as they'll go — keeping app-specific rules local saves context for everyone else's sessions, not just yours.
25+
26+
For rules that should apply only to certain file types (e.g., all `*Controller.cs` files), use [`.claude/rules/<name>.md` with a `paths:` frontmatter glob](https://code.claude.com/docs/en/memory#organize-rules-with-claude/rules/) instead of a nested `CLAUDE.md`.
27+
28+
### 2. Shape — how should Claude use it?
29+
30+
| You want to… | Use |
31+
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
32+
| State a rule Claude must always follow in its scope | `CLAUDE.md` |
33+
| State a rule that applies only to certain file globs | `.claude/rules/<name>.md` with `paths:` frontmatter |
34+
| Teach a procedure Claude invokes on demand | `.claude/skills/<name>/SKILL.md` |
35+
| Give Claude a specialized subagent with its own context | `.claude/agents/<name>.md` (YAML frontmatter; `name` + `description` required) |
36+
| Add a user-invocable slash command | `.claude/commands/<name>.md` |
37+
| Trigger a shell script on a Claude Code event | _We have them, but no strict project enforcement yet — register yours in `settings.local.json`._ |
38+
39+
Rule of thumb: **if Claude only needs it sometimes, it's a skill.** Once a `CLAUDE.md` loads, it stays in context for the rest of the session — keep each one lean, especially the root.
40+
41+
## Security conventions
42+
43+
Skills and agents that touch vault data, authentication, or cryptography must use Bitwarden's [Core Vocabulary](https://contributing.bitwarden.com/architecture/security/definitions) (Vault Data, Protected Data, Secure Channel, etc.) and re-state the zero-knowledge invariant inline. **Subagents run in a fresh context** and do not inherit this repo's `CLAUDE.md` — include the relevant definitions directly in the agent's system prompt.
44+
45+
## What good contributions look like
46+
47+
- **Grounded in the code.** Real files, real patterns, real commands.
48+
If it could apply to any repo, it belongs in `ai-plugins`.
49+
- **Describes the "what" and "why," not the "who."**
50+
Avoid team-persona framing. Describe the domain and its constraints; the team is an implementation detail.
51+
- **Short and specific.**
52+
2,000 words of general advice isn't a skill.
53+
- **Active voice, direct language.**
54+
"Invoke this skill when..." — not "This skill may be invoked when..."
55+
- **Reviewed like code.**
56+
Teams of domain experts own `.claude/` in their areas — they're the ones shaping how Claude behaves for everyone who works there, so treat changes with the same seriousness as source.
57+
58+
## Anti-patterns
59+
60+
- **Team-persona agents** ("Team ABC engineer").
61+
If a team's process is unique enough to warrant a persona, that's an SDLC signal to address, not a persona to encode.
62+
- **Root-level rules that only matter in one subtree.**
63+
If it applies to `util/Seeder` only, put it in `util/Seeder/CLAUDE.md`.
64+
- **Duplicating `ai-plugins` content.**
65+
Check existing plugin skills before writing a new one.
66+
- **Generic advice disguised as repo-local knowledge.**
67+
"Write good tests" isn't repo-specific.
68+
"Our integration tests must hit a real database because…" is.
69+
70+
## Building a contribution
71+
72+
The Claude Code ecosystem moves fast — last session's habits may already be out of date. Here's the workflow we follow.
73+
74+
### 1. Start with the canonical docs
75+
76+
A quick refresh before you begin goes a long way — the rules shift more often than you'd think:
77+
78+
- [How Claude Code Works](https://code.claude.com/docs/en/how-claude-code-works) — the mental model.
79+
- [Best Practices for Claude Code](https://code.claude.com/docs/en/best-practices) — what Anthropic recommends.
80+
- [Extend Claude Code](https://code.claude.com/docs/en/features-overview) — what you can build (skills, agents, commands, hooks).
81+
- [The Complete Guide to Building Skills for Claude](https://resources.anthropic.com/hubfs/The-Complete-Guide-to-Building-Skill-for-Claude.pdf) - a must read for skill building
82+
83+
### 2. Survey the landscape
84+
85+
A quick skim of both goes a long way:
86+
87+
- This repo's [`.claude/`](.) tree.
88+
- [`bitwarden/ai-plugins`](https://github.com/bitwarden/ai-plugins).
89+
90+
Try to match the voice you see. "Invoke when the user asks to X" — not "This skill may be invoked when X." Direct, active, specific. Your contribution should read like the neighbors.
91+
92+
### 3. Build iteratively
93+
94+
When you're authoring a skill, start with `/skill-creator:skill-creator`. It runs an iterative loop — draft → test against evals → review outputs → refine — with benchmark stats and a side-by-side reviewer. You end up with a skill that's been exercised against concrete inputs before you open the PR.
95+
96+
For agents, commands, hooks, and `CLAUDE.md` entries, start from an existing one in the repo and adapt it. No need to invent a new structure when a neighbor already solves the shape problem.
97+
98+
### 4. Validate before you push
99+
100+
- Run a local Bitwarden Claude Code review with `/bitwarden-code-review:code-review-local` — it writes findings to files so you can fix them before pushing, without posting anything to GitHub.
101+
- When you raise the PR, apply the `ai-review` label. Our reusable GitHub workflow watches for it and runs a Claude Code review automatically; without the label, the review doesn't fire.

.github/workflows/_move_edd_db_scripts.yml

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ on:
77
permissions:
88
pull-requests: write
99
contents: write
10+
id-token: write
11+
actions: read
1012

1113
jobs:
1214
setup:
@@ -55,13 +57,6 @@ jobs:
5557
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
5658
client_id: ${{ secrets.AZURE_CLIENT_ID }}
5759

58-
- name: Retrieve Slack secrets
59-
id: retrieve-slack
60-
uses: bitwarden/gh-actions/get-keyvault-secrets@main
61-
with:
62-
keyvault: "bitwarden-ci"
63-
secrets: "devops-alerts-slack-webhook-url"
64-
6560
- name: Retrieve secrets
6661
id: retrieve-secret
6762
uses: bitwarden/gh-actions/get-keyvault-secrets@main
@@ -190,12 +185,3 @@ jobs:
190185
$(echo -e "$MOVED_FILES")
191186
")
192187
echo "pr_url=${PR_URL}" >> "$GITHUB_OUTPUT"
193-
194-
- name: Notify Slack about creation of PR
195-
if: ${{ steps.commit.outputs.pr_needed == 'true' }}
196-
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
197-
env:
198-
SLACK_WEBHOOK_URL: ${{ steps.retrieve-slack.outputs.devops-alerts-slack-webhook-url }}
199-
with:
200-
message: "Created PR for moving EDD database scripts: ${{ steps.create-pr.outputs.pr_url }}"
201-
status: ${{ job.status }}

.github/workflows/build.yml

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -594,50 +594,3 @@ jobs:
594594
permissions:
595595
contents: read
596596
id-token: write
597-
598-
check-failures:
599-
name: Check for failures
600-
if: always()
601-
runs-on: ubuntu-22.04
602-
needs:
603-
- lint
604-
- build-artifacts
605-
- upload
606-
- build-mssqlmigratorutility
607-
- bitwarden-lite-build
608-
- trigger-k8s-deploy
609-
permissions:
610-
id-token: write
611-
steps:
612-
- name: Check if any job failed
613-
if: |
614-
github.event_name != 'pull_request'
615-
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
616-
&& contains(needs.*.result, 'failure')
617-
run: exit 1
618-
619-
- name: Log in to Azure
620-
uses: bitwarden/gh-actions/azure-login@main
621-
with:
622-
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
623-
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
624-
client_id: ${{ secrets.AZURE_CLIENT_ID }}
625-
626-
- name: Retrieve secrets
627-
id: retrieve-secrets
628-
uses: bitwarden/gh-actions/get-keyvault-secrets@main
629-
if: failure()
630-
with:
631-
keyvault: "bitwarden-ci"
632-
secrets: "devops-alerts-slack-webhook-url"
633-
634-
- name: Log out from Azure
635-
uses: bitwarden/gh-actions/azure-logout@main
636-
637-
- name: Notify Slack on failure
638-
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
639-
if: failure()
640-
env:
641-
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
642-
with:
643-
status: ${{ job.status }}

.github/workflows/repository-management.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,5 +241,9 @@ jobs:
241241
move_edd_db_scripts:
242242
name: Move EDD database scripts
243243
needs: cut_branch
244-
permissions: {}
244+
permissions:
245+
pull-requests: write
246+
contents: write
247+
id-token: write
248+
actions: read
245249
uses: ./.github/workflows/_move_edd_db_scripts.yml

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
"msbuild-sdks": {
77
"Microsoft.Build.Traversal": "4.1.0",
88
"Microsoft.Build.Sql": "1.0.0",
9-
"Bitwarden.Server.Sdk": "1.5.1"
9+
"Bitwarden.Server.Sdk": "1.5.2"
1010
}
1111
}

src/Api/Vault/Controllers/SyncController.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class SyncController : Controller
4343
private readonly GlobalSettings _globalSettings;
4444
private readonly ICurrentContext _currentContext;
4545
private readonly Version _sshKeyCipherMinimumVersion = new(Constants.SSHKeyCipherMinimumVersion);
46-
private readonly Version _bankAccountCipherMinimumVersion = new(Constants.BankAccountCipherMinimumVersion);
46+
private readonly Version _pm32009NewItemTypeMinimumVersion = new(Constants.PM32009NewItemTypeMinimumVersion);
4747
private readonly IFeatureService _featureService;
4848
private readonly IApplicationCacheService _applicationCacheService;
4949
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
@@ -171,9 +171,11 @@ private ICollection<CipherDetails> FilterUnsupportedCipherTypes(ICollection<Ciph
171171

172172
if (!_featureService.IsEnabled(FeatureFlagKeys.PM32009_NewItemTypes)
173173
|| _currentContext.ClientVersion == null
174-
|| _currentContext.ClientVersion < _bankAccountCipherMinimumVersion)
174+
|| _currentContext.ClientVersion < _pm32009NewItemTypeMinimumVersion)
175175
{
176176
unsupportedTypes.Add(Core.Vault.Enums.CipherType.BankAccount);
177+
unsupportedTypes.Add(Core.Vault.Enums.CipherType.DriversLicense);
178+
unsupportedTypes.Add(Core.Vault.Enums.CipherType.Passport);
177179
}
178180

179181
return unsupportedTypes.Count == 0
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using Bit.Core.Utilities;
2+
using Bit.Core.Vault.Models.Data;
3+
4+
namespace Bit.Api.Vault.Models;
5+
6+
public class CipherDriversLicenseModel
7+
{
8+
public CipherDriversLicenseModel() { }
9+
10+
public CipherDriversLicenseModel(CipherDriversLicenseData data)
11+
{
12+
FirstName = data.FirstName;
13+
MiddleName = data.MiddleName;
14+
LastName = data.LastName;
15+
DateOfBirth = data.DateOfBirth;
16+
LicenseNumber = data.LicenseNumber;
17+
IssuingCountry = data.IssuingCountry;
18+
IssuingState = data.IssuingState;
19+
IssueDate = data.IssueDate;
20+
IssuingAuthority = data.IssuingAuthority;
21+
ExpirationDate = data.ExpirationDate;
22+
LicenseClass = data.LicenseClass;
23+
}
24+
25+
[EncryptedString]
26+
[EncryptedStringLength(1000)]
27+
public string? FirstName { get; set; }
28+
29+
[EncryptedString]
30+
[EncryptedStringLength(1000)]
31+
public string? MiddleName { get; set; }
32+
33+
[EncryptedString]
34+
[EncryptedStringLength(1000)]
35+
public string? LastName { get; set; }
36+
37+
[EncryptedString]
38+
[EncryptedStringLength(1000)]
39+
public string? DateOfBirth { get; set; }
40+
41+
[EncryptedString]
42+
[EncryptedStringLength(1000)]
43+
public string? LicenseNumber { get; set; }
44+
45+
[EncryptedString]
46+
[EncryptedStringLength(1000)]
47+
public string? IssuingCountry { get; set; }
48+
49+
[EncryptedString]
50+
[EncryptedStringLength(1000)]
51+
public string? IssuingState { get; set; }
52+
53+
[EncryptedString]
54+
[EncryptedStringLength(1000)]
55+
public string? IssueDate { get; set; }
56+
57+
[EncryptedString]
58+
[EncryptedStringLength(1000)]
59+
public string? IssuingAuthority { get; set; }
60+
61+
[EncryptedString]
62+
[EncryptedStringLength(1000)]
63+
public string? ExpirationDate { get; set; }
64+
65+
[EncryptedString]
66+
[EncryptedStringLength(1000)]
67+
public string? LicenseClass { get; set; }
68+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using Bit.Core.Utilities;
2+
using Bit.Core.Vault.Models.Data;
3+
4+
namespace Bit.Api.Vault.Models;
5+
6+
public class CipherPassportModel
7+
{
8+
public CipherPassportModel() { }
9+
10+
public CipherPassportModel(CipherPassportData data)
11+
{
12+
Surname = data.Surname;
13+
GivenName = data.GivenName;
14+
DateOfBirth = data.DateOfBirth;
15+
Sex = data.Sex;
16+
BirthPlace = data.BirthPlace;
17+
Nationality = data.Nationality;
18+
PassportNumber = data.PassportNumber;
19+
PassportType = data.PassportType;
20+
IssuingCountry = data.IssuingCountry;
21+
IssuingAuthority = data.IssuingAuthority;
22+
IssueDate = data.IssueDate;
23+
ExpirationDate = data.ExpirationDate;
24+
NationalIdentificationNumber = data.NationalIdentificationNumber;
25+
}
26+
27+
[EncryptedString]
28+
[EncryptedStringLength(1000)]
29+
public string? Surname { get; set; }
30+
31+
[EncryptedString]
32+
[EncryptedStringLength(1000)]
33+
public string? GivenName { get; set; }
34+
35+
[EncryptedString]
36+
[EncryptedStringLength(1000)]
37+
public string? DateOfBirth { get; set; }
38+
39+
[EncryptedString]
40+
[EncryptedStringLength(1000)]
41+
public string? Sex { get; set; }
42+
43+
[EncryptedString]
44+
[EncryptedStringLength(1000)]
45+
public string? BirthPlace { get; set; }
46+
47+
[EncryptedString]
48+
[EncryptedStringLength(1000)]
49+
public string? Nationality { get; set; }
50+
51+
[EncryptedString]
52+
[EncryptedStringLength(1000)]
53+
public string? PassportNumber { get; set; }
54+
55+
[EncryptedString]
56+
[EncryptedStringLength(1000)]
57+
public string? PassportType { get; set; }
58+
59+
[EncryptedString]
60+
[EncryptedStringLength(1000)]
61+
public string? IssuingCountry { get; set; }
62+
63+
[EncryptedString]
64+
[EncryptedStringLength(1000)]
65+
public string? IssuingAuthority { get; set; }
66+
67+
[EncryptedString]
68+
[EncryptedStringLength(1000)]
69+
public string? IssueDate { get; set; }
70+
71+
[EncryptedString]
72+
[EncryptedStringLength(1000)]
73+
public string? ExpirationDate { get; set; }
74+
75+
[EncryptedString]
76+
[EncryptedStringLength(1000)]
77+
public string? NationalIdentificationNumber { get; set; }
78+
}

0 commit comments

Comments
 (0)