qcp is designed to make AI-assisted database querying safer, but it should still be treated as software that sits next to valuable data. This document explains the security model, privacy expectations, recommended deployment posture, and how to report vulnerabilities.
Security fixes are applied to the latest published qcp release and the main branch. If you are using an older version, upgrade before reporting behavior that may already have been fixed.
qcp treats all LLM output as untrusted. The model can propose SQL, but deterministic TypeScript code decides whether that SQL can run.
qcp enforces read-only behavior in multiple layers:
- SQL is parsed with
pgsql-ast-parserand validated structurally before execution. - Only
SELECT, safeWITH, andEXPLAINstatements are allowed. - Data-changing statements such as
INSERT,UPDATE,DELETE,DROP,ALTER,TRUNCATE,CREATE,GRANT,REVOKE, andCOPYare rejected. - Multiple statements are rejected.
- Data-changing CTEs are rejected, even when hidden inside a top-level
WITH. - Database reads run inside PostgreSQL read-only transactions.
This validation is deterministic and does not rely on the LLM following instructions.
For Prisma/Mastra database tools, qcp requires trusted server-side identity context:
requestContext.tenantIdrequestContext.userId
The agent is never trusted to invent tenant filters. qcp injects tenant and user predicates into supported SQL using AST rewriting, then executes only the rewritten SQL.
Default scope-column mapping:
tenantId:organization_id,tenant_id,org_id,workspace_id,account_iduserId:user_id,owner_id
Queries fail closed when qcp cannot determine a safe scope. This includes unknown tables, ambiguous unqualified tables, tables without supported scope columns, unsafe outer joins, table functions, lateral queries, and unsupported nested-query shapes.
qcp has human-in-the-loop approval for sensitive or potentially expensive reads:
- CLI
safeModeprompts before queries that match sensitive table patterns or estimated high-cost scans. - Mastra execution tools use tool-level approval hooks for sensitive or high-cost reads.
--yes skips CLI approval prompts only. It does not disable SQL validation, read-only transactions, tenant isolation, or scrubbing.
Database tool outputs are scrubbed before they are returned to model-facing contexts. qcp masks common sensitive values including:
- Email addresses
- Phone numbers
- SSNs
- Bearer tokens and JWT-like tokens
- API keys, secrets, passwords, and long secret-like strings
Scrubbing is a defense-in-depth layer. Do not use it as the only control for highly sensitive data; prefer least-privilege database roles, views, row-level security, and tenant-scoped credentials.
Raw database errors are not returned to the agent unchanged. qcp suppresses stack traces, raw SQL, and schema-revealing database messages in model-facing tool responses.
The semantic layer is local-first and advisory:
- Structural schema remains stored in
~/.qcp/schemas.json. - Human-authored semantic annotations are stored in
~/.qcp/semantic.db. qcp semantic scanis structure-only and does not read row values.qcp semantic profileis opt-in per selected table/column, uses bounded read-only queries, skips sensitive-pattern columns by default, truncates stored values, and stores local summaries only.- Semantic context can help agents map business language to schema objects, but SQL validation and execution still use the full structural schema and the read-only safety pipeline.
To generate SQL and summaries, qcp sends data to the configured model provider. Depending on the command and provider, this may include:
- Your natural-language question
- Locally scanned schema context
- Relevant local semantic annotations, when configured
- Generated SQL
- Query results for summarization
Use Ollama or another local model provider if you do not want database context or result summaries sent to a hosted LLM provider.
Anonymous telemetry is separate from LLM provider calls. qcp telemetry does not include:
- SQL queries
- Query results
- Schema metadata
- Semantic annotations or value profiles
- Database URLs
- Credentials
- API keys or tokens
- Row data
Telemetry may include qcp version, OS/architecture, command names, model/provider names, latency, and coarse error categories.
Disable telemetry:
qcp telemetry offUse defense in depth:
- Connect qcp with a database role that has read-only permissions.
- Prefer database views that expose only fields safe for analysis.
- Enable PostgreSQL row-level security for multi-tenant applications.
- Avoid granting access to secrets, credentials, raw tokens, payment data, or health data unless explicitly needed.
- Keep
safeModeenabled for interactive use. - Treat
--yesas an automation-only option for trusted environments. - Review
~/.qcp/schemas.json,~/.qcp/semantic.db, logs, and support bundles before sharing them. - Rotate credentials if they may have been exposed in terminal output, shell history, or logs.
qcp reduces risk; it does not make arbitrary database access risk-free.
- Schema context and result summaries can be sent to the configured LLM provider.
- Semantic annotations can be sent to the configured LLM provider when relevant to an ask/chat request.
- Opt-in semantic value profiles are local summaries, but they may still reveal business-sensitive value names.
- PII scrubbing is pattern-based and may not catch every sensitive value.
- Tenant isolation currently supports conservative SQL shapes and rejects unsupported forms.
- Database permissions remain the strongest boundary. Use least-privilege credentials.
- If you disable safe mode or use
--yes, you remove interactive approval, not deterministic validation.
Please report security issues privately. Do not open a public GitHub issue for vulnerabilities.
Email: security@moduna.ai
Include:
- qcp version
- Installation method
- Operating system
- Database type/provider, if relevant
- A minimal reproduction
- Whether credentials, SQL text, schema details, or row data were exposed
We will acknowledge reports as soon as possible and coordinate a fix before public disclosure.
The repository includes tests for:
- Destructive SQL rejection
- Multi-statement rejection
- Data-changing CTE rejection
- Tenant/user predicate injection
- Cross-tenant query rejection
- Unknown and unscoped table rejection
- PII/token scrubbing
- Sanitized database errors
- Mastra request-context enforcement
- Sensitive/high-cost approval hooks
Run locally:
bun test
bun type-check
bun lint