Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@noah/bitwarden-secrets

Use Bitwarden Secrets Manager with Kody to securely store and use secrets in packages and ad hoc scripts, without ever exposing them to an AI agent.

README.md

205 lines · 10.0 KB · Markdown

@noah/bitwarden-secrets

Intent

Let Kody inject Bitwarden Secrets Manager values into outbound HTTPS fetch calls without the model reading them. This package is the account-bound bitwarden secret provider.

What it does

  • Declares kody.secretProvider.id = bitwarden
  • Exports sealed ./secretProvider which canonicalizes i/<uuid>/<field> and resolves value, key, or note
  • Uses the bound door-key user secret (machine-account access token)

Kody swaps the placeholder in for the real value at the fetch boundary. The model never sees the secret, and the value is only ever sent to hostnames you approved up front.


Setup from scratch

Never used Bitwarden before? Start at step 1 and do every step in order. It takes about fifteen minutes. Steps 1 through 7 happen in Bitwarden, steps 8 through 10 in Kody.

Throughout, the example names are Kody (project), Kody (machine account), and example-api-token (secret). Use whatever names you like; only the UUIDs matter to this package.

1. Create a Bitwarden account

  1. Go to https://bitwarden.com/go/start-free/.
  2. Enter your Email address, then pick your server region: bitwarden.com (United States) or bitwarden.eu (European Union).
  3. Select Sign Up (or Continue). Bitwarden emails you a link; select Verify email in it.
  4. Set a Master password and select Create account.

Your region choice is permanent and matters later. US and EU are separate clouds that cannot see each other, and the identity/API URLs you configure in step 10 differ per region. If you are not sure, pick United States, which is the default everywhere.

Already have a Bitwarden account? Skip to step 2.

2. Create a free organization

Secrets Manager is an organization product. It does not exist on a bare personal vault, so you need an organization even if you are the only person who will ever use it.

  1. Log in to the web app: https://vault.bitwarden.com (or https://vault.bitwarden.eu for EU).
  2. Select the New organization button.
  3. Enter an Organization name (for example Personal) and a Billing email.
  4. Under Choose your plan, pick Free. No payment information is required.
  5. Select Submit.

The Free plan gives you unlimited secrets, up to 3 projects, up to 3 machine accounts, and up to 2 users. That is plenty for one person wiring up Kody.

If you are on a Families plan, Secrets Manager is not available on that organization. Create a separate Free organization as above.

3. Turn on Secrets Manager

You must be the organization owner to do this.

  1. In the web app, open Admin Console for your organization.
  2. Go to BillingSubscription.
  3. In the More from Bitwarden section, check Subscribe to Secrets Manager.
  4. On the Free plan, select Submit.

Now switch products: use the product switcher in the navigation menu and select Secrets Manager. (The product switcher is the control that lets you move between Password Manager and Secrets Manager.) If you land on a promo screen instead, select Try it now.

4. Create a project

A project is a folder of secrets, and it is the unit of access control. Machine accounts are granted access to projects, not to individual secrets, so you need at least one.

  1. In Secrets Manager, open the New dropdown and select Project.
  2. Enter a Project name — use Kody.
  3. Select Save.

5. Create a secret

  1. Open the New dropdown and select Secret.
  2. Enter a Name (for example example-api-token) and the Value (the actual API token or password you want Kody to use). Notes is optional.
  3. Under Project, pick the Kody project you just made. A secret can belong to only one project at a time.
  4. Select Save.

6. Copy the secret's UUID

This package addresses secrets by UUID only. Names are rejected, because Bitwarden does not guarantee unique secret names across projects.

On the secrets list, the UUID is shown underneath the secret name. Use the Copy UUID button next to it. You will get something shaped like:

be8e0ad8-d545-4017-a55a-b02f014d4158

Keep it handy. This is the value you put in your placeholders.

7. Create a machine account and an access token

A machine account is a non-human identity. Its access token is the "door key" that Kody holds: one credential that lets Kody open your vault and read only the projects you allow.

Create the machine account:

  1. Open the New dropdown and select Machine account.
  2. Enter a Machine account name — use Kody.
  3. Select Save.

Grant it the project (this step is the one people forget):

  1. Open the machine account and go to the Projects tab.
  2. Select the Kody project from the dropdown and select Add.
  3. Set Permissions to Can read. Kody only needs to read.

Create the access token:

  1. Go to the Access tokens tab and select Create access token.
  2. Give it a Name (for example kody) and choose when it Expires. The default is Never; the other options are 7 days, 30 days, 60 days, and Custom.
  3. Select Create access token.
  4. Copy the token now and keep it somewhere safe. Bitwarden never stores it and cannot show it to you again. If you lose it, you have to revoke it and make a new one.

The token looks like this:

0.48c78342-1635-48a6-accd-afbe01336365.C0tMmQqHnAp1h0gL8bngprlPOYutt0:B3h5D+YgLvFiQhWkIq6Bow==

Reading it left to right: 0 is the format version, the UUID is the token id (used as the OAuth client id), the next chunk is the client secret, and everything after the : is a base64 encryption key. Copy it verbatim, including the trailing ==. Any truncation breaks it.

While you are here, open the Config tab of the machine account. It shows your Identity server URL and API server URL. Copy both, they are the region-correct values for step 10.

8. Store the token as a Kody secret

  1. Go to https://kody.codes/account/secrets/new.
  2. Name: bitwarden-token (this exact name is what the binding in step 10 refers to).
  3. Value: the access token from step 7.
  4. Approved hosts: identity.bitwarden.com and api.bitwarden.com (use the .eu equivalents if you chose the EU region). These are the only places this token is ever sent.
  5. Save.

9. Save this package

If you have not already, save @noah/bitwarden-secrets to your account so it has a package id. Note that id; you need it in the next step.

10. Bind the provider

Binding is what tells Kody "the bitwarden provider is served by this package, using this door key." Declaring kody.secretProvider in a package does not bind it.

Do it in the UI at /account/secret-providers, or from execute:

await kody.secretProviderBind({
	provider: 'bitwarden',
	package_id: '<this-package-id>',
	door_secret_name: 'bitwarden-token',
	config: {
		hosts: 'api.example.com',
		identityUrl: 'https://identity.bitwarden.com',
		apiUrl: 'https://api.bitwarden.com',
	},
})

The config keys:

  • hosts — comma-separated destination hostnames that resolved secret values may be sent to. Required, and empty fails closed. This is the important one: it is the allowlist of where your vault values are allowed to travel. Put the API you actually intend to call here, not bitwarden.com.
  • identityUrl — default https://identity.bitwarden.com. EU: https://identity.bitwarden.eu. Self-hosted: https://your.domain.com/identity.
  • apiUrl — default https://api.bitwarden.com. EU: https://api.bitwarden.eu. Self-hosted: https://your.domain.com/api.

11. Use it

Drop a placeholder into any outbound fetch. Substitute your own UUID from step 6 and your own host from step 10.

await fetch('https://api.example.com/v1/me', {
	headers: {
		Authorization:
			'Basic {{secret/bitwarden:i/00000000-0000-4000-8000-000000000000/value}}',
	},
})

Three fields are available:

{{secret/bitwarden:i/<secret-uuid>/value}}
{{secret/bitwarden:i/<secret-uuid>/key}}
{{secret/bitwarden:i/<secret-uuid>/note}}

value is the secret's Value, key is its Name, and note is its Notes.

Ad hoc execute does not need a provider grant. Saved packages do (secretProviderLock).

Done when

secretProviderList shows provider bitwarden bound to this package, and a fetch using a placeholder no longer errors with "No secret provider is bound".

To verify without ever printing a secret, call an approved host with a placeholder in a header and check that the request is rejected by the far end with a normal auth error rather than by Kody with a binding error. Never log a resolved value.


Troubleshooting

"No secret provider is bound" — Step 10 did not happen, or it bound a different package id. Check secretProviderList.

"Bitwarden identity rejected the machine-account token" — Usually the wrong region. A US token sent to identity.bitwarden.eu will never authenticate. Recheck identityUrl and apiUrl against the machine account's Config tab. Can also mean the token was truncated on copy.

"is not a Secrets Manager access token (expected 0.<uuid>.<secret>:<key>)" — The stored bitwarden-token is malformed. Most often the trailing == or the part after the : was lost. Access tokens cannot be re-displayed, so revoke it and create a new one.

"was not found or the machine account cannot read it" — The machine account was never granted the project (step 7, items 4 through 6), or the secret lives in a different project. Grant the project, then retry.

"Bitwarden MAC check failed" — The door key does not match the data it is decrypting. The stored token is wrong or belongs to a different organization.

The ref is rejected as not canonical — You used a secret name instead of a UUID. Only UUIDs work. Get it with Copy UUID (step 6).

It worked yesterday and not today — The access token expired, or someone revoked it. Note that a revoked token can keep working for up to an hour before the session expires, so "it still works" right after revoking is expected.

Hitting a wall on the Free plan — Free caps you at 3 projects, 3 machine accounts, and 2 users.