π¦ New version release - #542
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@smooai/smooth-operator@1.58.0
Minor Changes
88598c3: fix(security): enforce tenant isolation on the by-id session paths and the knowledge store (feature gap G7)
Closes G7 with a shared conformance suite β
rust/adapters/multitenancy_suite.rs,one body run by the in-memory, Postgres and DynamoDB adapters β plus a
server-level suite driving the real
handle_framefrom an attacker in anotherorg. Writing it found two live cross-tenant holes.
1. Cross-tenant session access on every by-id path (WS server + Lambda).
The connection's org was resolved only to stamp newly created sessions. Every
by-id action β
get_session,get_conversation_messages,send_message,confirm_tool_action,submit_interaction,verify_otp,rename_conversation,and conversation resume β went through
may_read_conversation, which checks theowner email and never the org. Its deliberate ownerless-is-open rule (a
conversation with no
userparticipant carrying an email stays readable, soanonymous principals keep their own sessions) is exactly the embeddable widget's
default state, so an attacker authenticated to org B who learned an org-A session
id could read that session, replay its whole history through a turn, retitle its
conversation, and resume it (minting a session bound to the victim's org, which
then flows into the turn's
ToolProviderContext). The Lambda transport had nocheck at all β
dispatch::get_session/send_messageacted on whateverstorage.get_sessionreturned.Fixed at the chokepoints:
scoped_sessionandmay_read_conversationnow takethe connection's
auth_organd refuse a row belonging to another tenant(indistinguishably from not-found), and the Lambda gained the same check off the
frame's verified principal. A connection with no verified org (anonymous /
tokenless β the widget's normal state) is unchanged.
2. Knowledge was not tenant-isolated on the in-memory adapter, and the admin
connector-index path ingested org-blind.
AclKnowledgeStorefiltered byuser/group only, on the assumption that the wrapped store was already
org-partitioned β true for Postgres/DynamoDB, false for the in-memory adapter and
for any third-party adapter using the
knowledge_for_accesstrait default. AndPOST /admin/connectors/{id}/indexingested through the org-blindknowledge()handle for every tenant: Postgres wrote
organization_id = NULL(which theorg-filtered read can never match, so connector-ingested knowledge silently
returned nothing) and DynamoDB wrote whichever partition the adapter was
constructed for.
AclKnowledgeStorenow records each document's owning org (from theorg_idmetadata the ingestion pipeline stamps, falling back to the org theingesting handle is bound to) and enforces the tenant boundary before the
ACL.
DynamoKnowledgeBasehonoursAccessContext::organization_idfor the querypartition and the document's own
org_idfor the ingest partition, mirroringwhat
PgKnowledgeBase::with_accessalready did.PgKnowledgeBase::ingestprefers the document'sorg_idover the handle's, sothe org-blind handle still lands rows in the right tenant.
knowledge_for_access.Behavior change worth reading before upgrading. A retrieval whose
AccessContextcarries an org now sees only documents recorded as that org'sβ matching the Postgres backend's existing SQL pre-filter, so all three backends
finally agree. A document ingested through the raw
knowledge()handle with noorg_idmetadata belongs to no tenant and is therefore invisible to a turn thathas one. If you seed knowledge directly, either stamp
org_idon the document oringest through
storage.knowledge_for_access(&AccessContext::default().with_organization_id(org))β which is what the reference server's seeding and the admin index path do.
@smooai/smooth-operator-web-chat-example@0.0.113
Patch Changes