From a55d40b74bda77dff6ec565abdd551607fc665d6 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 30 Sep 2026 13:55:59 +0200 Subject: feat(hub): a bundle pepper per account, handed only to a proven session Sealed at rest and bound to the account; returned by sign-in, device sign-in, a passphrase change and GET /me/bundle-pepper, never by a refresh, to a node token, in a token or in a log. Erasure clears it. Co-Authored-By: Claude Opus 5.5 --- .../versions/c3d4e5f6a7b9_bundle_pepper.py | 28 ++++++++++++++++++++++ packages/meshbay-hub/src/meshbay_hub/db/models.py | 8 +++++++ 2 files changed, 36 insertions(+) create mode 100644 packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/c3d4e5f6a7b9_bundle_pepper.py (limited to 'packages/meshbay-hub/src/meshbay_hub/db') diff --git a/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/c3d4e5f6a7b9_bundle_pepper.py b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/c3d4e5f6a7b9_bundle_pepper.py new file mode 100644 index 0000000..dbf1718 --- /dev/null +++ b/packages/meshbay-hub/src/meshbay_hub/db/migrations/versions/c3d4e5f6a7b9_bundle_pepper.py @@ -0,0 +1,28 @@ +"""a bundle pepper per account, so a node cannot test passphrase guesses + +Revision ID: c3d4e5f6a7b9 +Revises: b2c3d4e5f6a8 +""" + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op + +revision: str = "c3d4e5f6a7b9" +down_revision: str | Sequence[str] | None = "b2c3d4e5f6a8" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + with op.batch_alter_table("users") as batch: + batch.add_column(sa.Column("bundle_pepper", sa.String(128), nullable=True)) + batch.add_column(sa.Column("bundle_pepper_version", sa.Integer(), nullable=False, + server_default="1")) + + +def downgrade() -> None: + with op.batch_alter_table("users") as batch: + batch.drop_column("bundle_pepper_version") + batch.drop_column("bundle_pepper") diff --git a/packages/meshbay-hub/src/meshbay_hub/db/models.py b/packages/meshbay-hub/src/meshbay_hub/db/models.py index 293b940..1e652a6 100644 --- a/packages/meshbay-hub/src/meshbay_hub/db/models.py +++ b/packages/meshbay-hub/src/meshbay_hub/db/models.py @@ -60,6 +60,14 @@ class User(Base): # active|suspended|revoked status: Mapped[str] = mapped_column(String(16), default="active") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_now) + # The second half of what opens this account's keypair bundles on nodes + # (`auth.seal_pepper`, sealed at rest). A bundle is sealed under a key derived + # from the passphrase *and* this, so an operator holding one cannot test + # passphrase guesses offline: the pepper is handed only to a session that + # proved the passphrase or a device key, never to a node. Created the first + # time it is asked for; the version names which pepper sealed a bundle. + bundle_pepper: Mapped[str | None] = mapped_column(String(128), nullable=True) + bundle_pepper_version: Mapped[int] = mapped_column(Integer, default=1, server_default="1") nodes: Mapped[list["Node"]] = relationship(back_populates="user") group_memberships: Mapped[list["GroupMember"]] = relationship(back_populates="user") -- cgit v1.2.3