Identity

Secure Bitwarden with cnspec

Scan Bitwarden organization policies, members, collections, and groups against security and compliance best practices with cnspec.

A Bitwarden organization holds the credentials that open everything else, so the rules governing who joins it and what they can reach decide how far a single compromised member gets. cnspec reads a Bitwarden Teams or Enterprise organization through its Public API and evaluates the security policies the organization enforces, the members and their role, status, and two factor enrollment, the collections that structure vault access, and the groups and per member grants that decide who reaches which collection.

cnspec reads organization governance only. It never reads vault item contents, folders, or Send payloads: the Public API it talks to has no endpoint for them.

If you're new to cnspec, start with the Quickstart. For an overview of every identity provider cnspec can scan, see the identity scanning overview.

The Bitwarden resources are experimental. Field names and behavior can change in a future cnspec release.

Prerequisites

To scan Bitwarden with cnspec, you must have:

Authenticate

cnspec authenticates as the organization, not as a member, using an organization API key. To find it, open the Bitwarden web vault, go to the organization's Settings > Organization info page, and select View API key. The client ID has the form organization.<uuid>, where the UUID is the organization's own ID.

cnspec shell bitwarden --client-id organization.YOUR_ORG_UUID --client-secret YOUR_SECRET

Self-hosted deployments

A self-hosted Bitwarden instance serves the Public API and the identity endpoint from your own domain rather than from Bitwarden's cloud, so point cnspec at both:

cnspec shell bitwarden --client-id organization.YOUR_ORG_UUID --client-secret YOUR_SECRET \
  --api-url https://bitwarden.example.com/api --identity-url https://bitwarden.example.com/identity/connect/token

Environment variables

BITWARDEN_CLIENT_ID and BITWARDEN_CLIENT_SECRET supply the credentials, and BITWARDEN_API_URL and BITWARDEN_IDENTITY_URL point at a self-hosted deployment:

export BITWARDEN_CLIENT_ID=organization.YOUR_ORG_UUID
export BITWARDEN_CLIENT_SECRET=YOUR_SECRET

When these are set, you can omit the matching flags from the commands below.

Connection options

OptionDescription
--client-idOrganization client ID, of the form organization.<uuid>
--client-secretOrganization client secret
--api-urlPublic API base URL, for a self-hosted deployment
--identity-urlIdentity token URL, for a self-hosted deployment

Verify with a quick Bitwarden check

Confirm that cnspec can reach your organization by opening a cnspec shell:

cnspec shell bitwarden --client-id organization.YOUR_ORG_UUID --client-secret YOUR_SECRET
cnspec> bitwarden.organization { name planName seats occupiedSeats }
bitwarden.organization: {
  name: "Lunalectric"
  planName: "Enterprise (Annually)"
  seats: 250
  occupiedSeats: 187
}

If cnspec connects and reports your organization, you're ready to scan.

Scan Bitwarden

cnspec scan bitwarden --client-id organization.YOUR_ORG_UUID --client-secret YOUR_SECRET

The organization is a single asset. There are no child assets to discover.

When a scan completes, cnspec prints a summary of all the checks it ran, grouped by policy, along with a risk score from 0 (no risk) to 100 (highest risk). Failed checks include remediation guidance to help you fix issues. To learn more about reading scan results, read Understand cnspec Results.

Mondoo doesn't yet ship an out-of-the-box Bitwarden policy, so use the checks below as a starting point and create your own policies to meet your specific requirements.

Explore and test checks interactively

Open a cnspec shell to discover resources and try out checks:

cnspec shell bitwarden --client-id organization.YOUR_ORG_UUID --client-secret YOUR_SECRET

Review what the organization's plan makes available

A control the plan doesn't include can't be enforced, so these fields bound what the rest of an audit can expect:

cnspec> bitwarden.organization {
    name
    planName
    enabled
    useSso
    use2fa
    usePolicies
    useDirectory
    useEvents
    useResetPassword
  }

List the security policies and whether they're enforced

policyType selects the control and data carries its configuration, whose shape depends on the type:

cnspec> bitwarden.policies { policyType enabled data }

Review the master password rules

The masterPassword policy carries its thresholds in data:

cnspec> bitwarden.policies.where(policyType == "masterPassword") { enabled data }

List members, their role, and their security posture

cnspec> bitwarden.members { email role status twoFactorEnabled resetPasswordEnrolled accessAllCollections }

Find members without two factor authentication

cnspec> bitwarden.members.where(status == "confirmed" && twoFactorEnabled == false) { email role }

Review the collections and who reaches them

cnspec> bitwarden.collections { name groups { name } members { email role } }

Review the permissions each grant carries

A grant is more than membership. readOnly, hidePasswords, and manage decide what the member does once inside the collection:

cnspec> bitwarden.collections { name memberAccess { member { email } readOnly hidePasswords manage } }

Review the groups and the collections they reach

Every member of a group inherits the group's grants, so a group is the shortest path from one new member to many collections:

cnspec> bitwarden.groups { name accessAll members.length collections { name } }

Review who was invited but never finished enrollment

Only a confirmed member holds working access to shared collections. Everyone else is mid onboarding, or was left behind:

cnspec> bitwarden.members.where(status != "confirmed") { email role status }

Walk from a resource to its neighbors

Members, groups, and collections reference each other, so a query can start from any of them and walk in either direction:

cnspec> bitwarden.members { email groups { name } collections { name } }
cnspec> bitwarden.groups { name collectionAccess { collection { name } manage } }
cnspec> bitwarden.collections { name groupAccess { group { name } readOnly } }

Example security checks

Ensure the organization requires two factor authentication

cnspec> bitwarden.policies.where(policyType == "twoFactorAuthentication").all(enabled == true)
[ok] value: true

Ensure every confirmed member has two factor authentication enabled

The policy governs who can stay in the organization. This check reports who's covered right now:

cnspec> bitwarden.members.where(status == "confirmed").all(twoFactorEnabled == true)
[ok] value: true

Ensure members sign in through single sign-on

cnspec> bitwarden.policies.where(policyType == "requireSso").all(enabled == true)
[ok] value: true

Ensure a member can't belong to another organization

Without the single organization policy, a member can join a second organization and move shared items into it:

cnspec> bitwarden.policies.where(policyType == "singleOrg").all(enabled == true)
[ok] value: true

Ensure items created by a member belong to the organization

cnspec> bitwarden.policies.where(policyType == "organizationDataOwnership").all(enabled == true)
[ok] value: true

Ensure a member can't export their personal vault

cnspec> bitwarden.policies.where(policyType == "disablePersonalVaultExport").all(enabled == true)
[ok] value: true

Ensure the master password policy sets a length

The masterPassword policy's data carries minLength, minComplexity, and the character class requirements:

cnspec> bitwarden.policies.where(policyType == "masterPassword").all(data['minLength'] >= 12)
[ok] value: true

Ensure no member reaches every collection

accessAllCollections bypasses the collection grants entirely, so the member reaches collections nobody granted them:

cnspec> bitwarden.members.none(accessAllCollections == true)
[ok] value: true

Ensure no group reaches every collection

A group with accessAll gives each of its members every collection, including the ones created after they joined:

cnspec> bitwarden.groups.none(accessAll == true)
[ok] value: true

Ensure ownership stays rare

An owner can change every policy the organization enforces:

cnspec> bitwarden.members.where(role == "owner").length <= 2
[ok] value: true

Ensure a revoked member holds no collection access

A revoked member keeps their grants until someone removes them, so revoking access and removing it aren't the same thing:

cnspec> bitwarden.members.where(status == "revoked").all(collections.length == 0)
[ok] value: true

Ensure invitations don't linger

An invitation that's never accepted is a seat nobody watches:

cnspec> bitwarden.members.none(status == "invited")
[ok] value: true

Ensure event logging is available to the organization

Without it, no record survives of who reached which collection:

cnspec> bitwarden.organization.useEvents == true
[ok] value: true

Review who can manage a collection

Managing a collection means granting it to others, so it's the grant that creates more grants:

cnspec> bitwarden.collections { name memberAccess.where(manage == true) { member { email role } } }

Learn more

On this page