Skip to content

fix(client): enforce and pin workspace on raw graph queries - #685

Draft
tteon wants to merge 4 commits into
mainfrom
fix-tenant-isolation-client-query-18026098678358793120
Draft

tteon wants to merge 4 commits into
mainfrom
fix-tenant-isolation-client-query-18026098678358793120

Conversation

@tteon

@tteon tteon commented Sep 16, 2026 •

Copy link
Copy Markdown
Owner

Severity: High
Vulnerability: The raw client.query() method, intended for executing raw Cypher queries against the graph store in local mode, did not enforce workspace isolation boundaries. It omitted the workspace_id parameter and the enforce_workspace_filter=True flag when delegating to graph_store.query().
Impact: A user or agent leveraging client.query() could potentially execute queries that traverse or leak nodes and relationships belonging to other tenants, bypassing the repository's data isolation guarantees.
Fix: Modified client.query() in src/seocho/client.py to explicitly inject workspace_id=self.workspace_id and enforce_workspace_filter=True when invoking self.graph_store.query(). This ensures that any raw Cypher query processed through this interface must explicitly respect the $workspace_id filter (as enforced by the underlying graph store implementation), preventing cross-tenant data access.
Validation: Ran the bash scripts/ci/run_basic_ci.sh CI script and relevant pytest suites (tests/seocho/test_operating_layer.py, tests/seocho/test_session_agent.py, etc.) locally. Tested that the addition of the strict arguments doesn't disrupt tests reliant on properly mocked components.
Risks: The fix makes client.query() strictly require the use of $workspace_id inside the raw Cypher query string (when workspace_id is set). Any existing automation or user scripts leveraging client.query() with globally-scoped (non-tenant-aware) Cypher queries will start raising WorkspaceFilterMissingError. However, this failure is by design for a multi-tenant platform to enforce safety.

Modified Files:

  • src/seocho/client.py

[Draft]


PR created automatically by Jules for task 18026098678358793120 started by @tteon

Maintainer integration review (2026-10-13)

Raw client queries copy parameters and overwrite the reserved workspace_id with the client workspace before delegation. Tests exercise the actual Neo4jGraphStore up to its driver boundary and reject unscoped reads before I/O. Updated the bring-your-data notebook and SDK contract. Targeted client tests: 25 passed.

Validation of the exact combined changes on current main: bash scripts/ci/run_basic_ci.sh — 1,252 passed, 3 skipped; all remaining lint/type/import/root/env/identity gates passed. bash scripts/ci/check-doc-contracts.sh passed. Local tracking: seocho-xlo1. Source changes remain in their owning SDK modules; local agent state stays untracked.

Raw query preflight additionally rejects WITH/UNION/UNWIND because the bounded checker cannot verify variable rebinding. Three attack-shape regression cases confirm rejection before driver I/O. The opt-in strict helper is used by the public raw-query facade; existing internally generated graph-query handling retains its current default. Updated the SDK contract. Targeted client/boundary tests: 32 passed. Final combined CI rerun pending.

Deferred after current review

This PR is draft and MUST NOT be merged as a complete workspace isolation fix. The review found that the bounded checker still does not prove scope for relationship/path results, and rebinding detection can match keywords inside Cypher literals. Reserved parameter pinning, notebook corrections, and rebinding rejection are partial changes retained for a future focused validator slice. The original main behavior remains unchanged by this deferred PR. Current unresolved review threads retain the concrete attacks and compatibility examples. Local tracking: seocho-xlo1.

Co-authored-by: tteon <52625664+tteon@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T15:49:07.228026Z 73a6444 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added area-sdk Public Python SDK, client facade, session API, or typed models. kind-maintenance Maintenance, chores, dependency hygiene, or repo cleanup. status-needs-triage Needs maintainer triage before work starts or merges. labels Sep 16, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e891a6bc9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/seocho/client.py Resolved
Comment thread src/seocho/client.py Resolved
@github-actions github-actions Bot added area-docs README, QUICKSTART, docs, tutorials, or contributor guidance. area-examples Examples, sample data, run specs, or public tutorials. kind-docs Documentation, examples, quickstarts, or onboarding copy. kind-test Test coverage, fixtures, or validation-only changes. labels Oct 13, 2026
@tteon tteon changed the title Draft: Enforce workspace filtering on client raw queries fix(client): enforce and pin workspace on raw graph queries Oct 13, 2026
@github-actions github-actions Bot added the kind-bug A reproducible defect or regression. label Oct 13, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a19637a5ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/seocho/client.py Resolved

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 73a64440a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/seocho/client.py
return self.graph_store.query(cypher, params=params, database=database)
from .store.graph import enforce_read_workspace_scope

enforce_read_workspace_scope(cypher, reject_rebinding=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject unscoped relationship results

A query such as MATCH ()-[r]->() WHERE r._workspace_id <> $workspace_id RETURN r.secret AS secret passes this gate without rebinding because verify_workspace_binding() only validates identifiers recognized as node variables. It therefore executes and returns relationship data belonging to every other workspace, despite the new governed-read guarantee; relationship variables and path aliases must also be proven to match the client workspace before delegation.

AGENTS.md reference: AGENTS.md:L126-L126

Useful? React with 👍 / 👎.

Comment thread src/seocho/store/graph.py
Comment on lines +252 to +253
if reject_rebinding and _REBIND_CONSTRUCTS_RE.search(stripped):
raise WorkspaceScopeViolationError(cypher, "unsupported_construct")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore keywords inside Cypher literals

For a legitimate scoped query such as MATCH (n) WHERE n._workspace_id = $workspace_id AND n.name = 'Union' RETURN n.name, this regex sees Union inside the string literal and raises unsupported_construct, even though the query contains no UNION clause or variable rebinding. Strip or tokenize Cypher string literals before applying the construct check so ordinary searches for values such as “Union”, “With”, or “Unwind” remain usable.

Useful? React with 👍 / 👎.

@tteon
tteon marked this pull request as draft October 15, 2026 22:23

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-docs README, QUICKSTART, docs, tutorials, or contributor guidance. area-examples Examples, sample data, run specs, or public tutorials. area-sdk Public Python SDK, client facade, session API, or typed models. kind-bug A reproducible defect or regression. kind-docs Documentation, examples, quickstarts, or onboarding copy. kind-maintenance Maintenance, chores, dependency hygiene, or repo cleanup. kind-test Test coverage, fixtures, or validation-only changes. status-needs-triage Needs maintainer triage before work starts or merges.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant