Repository navigation
fix(run_sql): scope agent SQL to the finance tables with a Postgres role - #58
Merged
Merged
Conversation
run_sql ran agent-written SQL as the app's own DB user, so a SELECT could read
mcp_server (plaintext OAuth tokens, env vars, headers), the checkpoint tables,
config and thread. MCP tools auto-approve, so an injected tool result could get
the agent to read those and pass them on.
- Migration 0006 creates cameron_analytics (NOLOGIN) with SELECT on
transaction and category only; runReadOnlyQuery switches to it with
SET LOCAL ROLE inside the existing READ ONLY transaction.
- Revoke set_config from PUBLIC: set_config('role', ...) is SET ROLE as a
function and, with query_to_xml, escaped the role in one SELECT. The
migration raises if the revoke did not take.
- A permission denial (42501) returns an "isn't available to run_sql"
payload with availableTables; other errors keep their shape.
- Tests pin the error mapping and that the grants match the tables
describe_finance_schema documents.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
run_sqlran agent-written SQL as the app's own database user, inside a READ ONLY transaction. That blocked writes but not reads, soSELECT auth_tokens, env, headers FROM mcp_serverworked. That table holds plaintext MCP OAuth tokens, stdio env vars and HTTP headers. The checkpoint tables (every conversation),configandthreadwere readable too. MCP tools auto-approve, so text injected into an MCP tool result could get the agent to read those secrets and send them out through another tool, with no approval card along the way.Fix
Postgres now decides what
run_sqlcan read; there is no table filter in code.0006_analytics_readonly_role.sql(hand-written, idempotent): creates acameron_analyticsrole (NOLOGIN) with USAGE onpublicand SELECT ontransactionandcategoryonly, then grants the app user membership in it.runReadOnlyQueryrunsSET LOCAL ROLE cameron_analyticsinside the existing transaction. READ ONLY,statement_timeout, the rollback and the row cap are unchanged.set_configescape.SET ROLEchanges only the current role, andset_config('role', 'none', true)isSET ROLEas a function call, which the guard'sSETkeyword check misses. Paired withquery_to_xml('select … from mcp_server', …), a single SELECT readmcp_serveron the dev database. The migration therefore revokesEXECUTEonset_configfromPUBLICand grants it back to the app user. It ends with a check that raises if the role can still callset_configor readmcp_server, because aREVOKErun by a non-owner only warns. Migrations must run as a superuser; all three compose stacks already do.42501) comes back as{ error: "That table isn't available to run_sql…", availableTables, detail }. Other DB errors keep the existingQuery failed:shape. The tool description is unchanged.Tests
pnpm test(459) andtsc --noEmitpass.drizzle/*.sqland checks that the role's grants match the tablesdescribe_finance_schemadocuments, with nothing beyondSELECTand no whole-schema grants.mydb_dev, 5544), through the realrunSql:mcp_server,checkpoints,config,thread, thedrizzleschema and theset_configescape are all refused.migrateimage copiesdrizzle/and runs as the superuser, so it picks this up on the next--profile full up.Docs
The
run_sql, raw-SQL boundary andconfigbullets inAGENTS.md, plus a "Whatrun_sqlcan read" subsection indocs/ARCHITECTURE.md.🤖 Generated with Claude Code