JavaScript, webdev

Maybe You Don’t Need a Password Manager

Every young company has a #passwords problem. Someone needs the staging database password, so they paste it into Slack. A few minutes later, that password is sitting in Slack search, notification history, laptop caches, exports, backups, and probably a dozen places nobody thought about.

Then six months later someone asks, “Who had access to that password?”
Good luck answering that.

The obvious answer is: “Use a company password manager.”
And yes, you probably should.

But there is a surprisingly large gap between “Bob pasted the password into Slack” and “Let’s buy another SaaS product, create accounts for everyone, manage licenses, configure policies, train the team, and make this another system everyone has to use.”

That gap is what I wanted to explore.
So I built Secure Share.

It is a small, self-hosted app for handing a teammate a password or sensitive note without leaving the secret sitting in your chat history forever. It is under 4,000 lines of JavaScript, uses SQLite, authenticates with Google, and you can have it running locally in about ten minutes.

The design goal is simple:
If someone steals the database, the database alone should be boring.

If someone steals the database, the database alone should be boring.

Why not just use a password manager?

This is the obvious question, and it deserves an honest answer.

A proper company password manager is more powerful than Secure Share. Products such as 1Password, Bitwarden, Dashlane, and similar tools give you vaults, browser extensions, password generation, autofill, device management, auditing, SSO integrations, policies, and a whole ecosystem around credential management.

Read more: Maybe You Don’t Need a Password Manager

If you need all of that, use a password manager.

But sometimes you don’t need all of that.
Imagine a 10-person engineering company.
You don’t necessarily need a complete enterprise credential-management platform just because someone occasionally needs to give another engineer the production database password, an API key, a vendor credential, or a temporary secret.

You need three things:
– Who is this person?
– What secret are they allowed to see?
– Can I take that access away later?

That’s it.
Secure Share is deliberately built around that smaller problem.

1. No new identity system

The company already has Google Workspace.
Why create another username/password database just to share a password?
Secure Share uses Google authentication and restricts access to explicitly allowed company domains. The application doesn’t need to own another set of employee credentials.

That also makes the mental model simple: if you can sign into the company’s Google account, you can authenticate. If your domain isn’t allowed, you’re out.

2. No per-seat pricing

This is one of the less exciting reasons, but it matters.
Password managers generally make money by charging per user. That’s completely reasonable for a full-featured product, but it means a five-person company can end up paying for a platform when all it really wanted was a safe way to share a handful of secrets.

A self-hosted tool has a different cost model: one small server, one database, and whatever operational cost you already have.
For a small team, that can be attractive.

3. You own the infrastructure

This is the biggest philosophical difference.
With a SaaS password manager, you’re trusting another company with the availability and security of a system containing some of your most sensitive information. That’s not necessarily a bad trade. Good password managers invest heavily in security, infrastructure, audits, redundancy, and operational controls.

But it is still a trade.

With a self-hosted system, the infrastructure belongs to you. You decide where it runs, how it is backed up, who has server access, how long logs are retained, and when the system gets shut down.
Of course, this comes with a giant footnote:
Self-hosted does not automatically mean more secure.

If you put this thing on an unpatched EC2 instance with root access shared among six people and the master secret sitting in a Slack message, congratulations: you have successfully replaced a professionally operated security platform with yourself.
The advantage is control, not magic security.

4. The sharing model is deliberately narrow

A traditional password manager thinks in terms of vaults.
Secure Share thinks in terms of individual secrets. You create a secret and explicitly share it with one or more people. They can read it, but they cannot edit it, delete it, or share it with someone else. Change the password and everyone with access sees the new value. Remove someone’s access and their next request fails.
That is a much smaller permission model than managing an entire corporate vault hierarchy.
And smaller permission models are easier to understand.

5. It is easier to reason about

This is probably the biggest reason I built it. I can read the entire server.
The database is SQLite.
The cryptography uses Node’s built-in crypto.
There are only 11 runtime dependencies on the server. The important authorization rules are covered by tests. There isn’t a giant distributed system hiding underneath a button that says “Share.”
For security software, boring is good.

I want to know exactly what happens when Alice shares a secret with Bob, Bob leaves the company, Alice changes the password, and someone gets a copy of the database. That is a much easier system to reason about than a giant platform with 47 features that we aren’t using.

But there is a catch

This isn’t a replacement for a serious enterprise password manager.

If your company needs browser autofill, shared vaults, device management, passkeys, password generation, detailed audit trails, SSO provisioning, compliance reporting, granular administrative policies, and dozens of integrations, use a real password-management platform.

Secure Share is for a much narrower problem:
“I need to give this person this secret, and I want to be able to take it back.”

That’s it.
And that narrower scope is the feature.

What you get

The workflow is deliberately simple: sign in, store a password or note, share it with a teammate, and revoke access when that person no longer needs it.

  • Company sign-in only. Google OAuth is restricted to the email domains you allow. The domain is checked again on every request.
  • Two kinds of secrets. A password with username and password, or a free-form note up to 50,000 characters.
  • Share with up to ~10 people. Recipients see the secret under “Shared with Me”, along with the owner’s name.
  • Changes propagate. Rotate a password once and every authorized recipient sees the new value on their next load.
  • Revoke actually revokes. Remove a share or delete the secret and the recipient’s next request gets a 404.
  • Recipients are read-only. They cannot edit, delete, or re-share the secret.
  • Links survive sign-in. Send someone a direct link. If they aren’t signed in, they authenticate first and then land on the secret.
  • Basic security guard rails. Rate limits, Origin checks, SameSite cookies, CSP, and HSTS in production.

Under the hood, it is intentionally boring:

LayerWhat it uses
ServerNode.js, Express, Passport with Google OAuth 2.0
ClientReact, Material UI
StorageSQLite in WAL mode for data and sessions
CryptoNode’s built-in crypto, AES-256-GCM and PBKDF2-SHA256
Testsnode:test, Jest, GitHub Actions

The server has only 11 runtime dependencies, and none of them handle the cryptography.

One secret, many keys

The interesting part is the encryption model.
Each secret is encrypted exactly once, and every person who has access gets their own wrapped copy of the encryption key. This is envelope encryption, the same general pattern used by cloud KMS systems.

A random 32-byte data key encrypts the secret with AES-256-GCM, with the secret ID bound as associated data. The data key never touches disk in plaintext. For each user, the key is wrapped using a key derived with PBKDF2-SHA256 from the server’s MASTER_SECRET plus that user’s Google ID, using a fresh random salt.

Sharing means adding another wrapped copy of the data key. Revoking means deleting that user’s wrapped key.
That detail matters because it solves two nasty problems at the same time: password rotation and revocation.

The bug that got us here

The first version took the obvious shortcut. When you shared a secret, the application copied the entire secret into a new row, encrypted it for the recipient, and renamed it something like Staging database (shared).

It worked.
Until it didn’t.
The owner rotated the password, but the recipient still had the old copy. Revoking the share removed access to the shared record, but the copied secret was still sitting in the recipient’s account.

That is exactly the kind of security model that looks fine in a demo and becomes painful the moment you actually operate it.

Envelope encryption fixes the architecture instead of trying to patch the symptoms. There is one ciphertext, so an update is automatically visible to everyone with access. Revoking access deletes the recipient’s only wrapped key, so there is nothing left for that user to decrypt.

If you are upgrading an older installation, the server handles the migration on startup. It creates a backup with VACUUM INTO, converts each secret, and removes the duplicate only after the original has been converted successfully.

Now try to break it

Every test below was run against a live copy of the application before publishing this post.
A security feature you haven’t tried to break is, at best, a theory.

AttackHow to try itWhat happens
Read the stolen databaseOpen server/database/app.db with sqlite3Titles are readable; secret contents are encrypted ciphertext
Modify ciphertextChange a byte in encrypted_data and open the secretDecryption fails because the GCM authentication tag doesn’t match
Move ciphertext between secretsCopy secret B’s ciphertext into secret ADecryption fails because the payload is bound to the secret ID
Steal the database without the master secretRestart using a different MASTER_SECRETNothing decrypts
CSRF attemptPOST to /api/secrets with a foreign Origin403, Origin not allowed
Hammer sign-inRequest /auth/google six times in 15 minutesThe sixth request gets 429
Use an unauthorized accountSign in with a domain outside the allowlistRedirected back to login; no user row is created
Escalate as a recipientTry to edit, delete, or re-share a shared secret404, as if the secret doesn’t exist

Two important lessons fall out of these tests.
First, titles are readable. Don’t put the secret itself in the title.

Second, MASTER_SECRET is the crown jewel. Back it up somewhere separate from the database. A database backup without the master secret is basically a very elaborate collection of encrypted strings.

What it doesn’t do (yet)

Read this section before putting production credentials into it.

It is not end-to-end encrypted. The server encrypts and decrypts the secrets. Anyone who controls both the server and the master secret can ultimately read everything. That’s a deliberate trade-off because it makes sharing simple.

Titles are stored in plaintext. The dashboard needs them to list secrets.

Revoking doesn’t erase memory. It prevents future access. If someone already viewed the password, rotate it.

Recipients must sign in once. You can’t share with someone who has never created an account.

It is a single-process application. SQLite and in-memory rate limits are reasonable for a small team, but this isn’t designed for a cluster.

Sessions don’t disappear instantly. A session expires 24 hours after the last request. Suspending someone in Google Workspace prevents new sign-ins, but an already-active session can remain valid until it expires or you clear sessions.db.

There are no expiring shares, one-time links, or audit logs.

Those limitations are also a pretty good roadmap: expiring shares, audit logs, KMS integration, and an admin action to kill sessions during offboarding.

The real question

The interesting question isn’t whether this is better than a password manager.
It isn’t.

A mature password manager is a much bigger and more complete product. The interesting question is whether every secret-sharing problem needs a mature password manager.
Sometimes you need a vault.
Sometimes you need a browser extension that fills passwords into websites.
Sometimes you need enterprise SSO, device management, audit reports, and compliance controls.
And sometimes you just need to tell Sarah what the staging database password is without putting it into Slack.

For that last problem, a small tool with a tiny attack surface and an explicit sharing model can be a surprisingly good fit. It also has a nice property that most SaaS products don’t:

you can understand the entire thing.

You can inspect the database. Read the encryption code. Read the authorization rules. Run the attack tests. Back up the whole application. Move it to another server. Shut it down.
No vendor contract. No per-seat licensing. No dependency on somebody else’s roadmap.

Just a small box that does one thing.
Be safe & strong.


Discover more from Ido Green

Subscribe to get the latest posts sent to your email.

Standard

Leave a Reply