Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
af9f51e
refactor: extract between bind collaborator
kholdrex Jun 28, 2026
96727ac
fix: repair between bind CI regressions
kholdrex Jun 28, 2026
e0ea8be
fix: satisfy code_to_query CI lint gates
kholdrex Jun 28, 2026
c478542
fix: satisfy query spec rubocop cops
kholdrex Jun 28, 2026
5e0f126
fix: preserve policy-augmented query intent
kholdrex Jul 2, 2026
008b5cc
fix: keep subquery policy compliance fail-closed
kholdrex Jul 2, 2026
f3e1552
fix: keep EXISTS policy subqueries out of top-level allowlist
kholdrex Jul 3, 2026
d17c30c
fix: block comma-style top-level derived tables
kholdrex Jul 3, 2026
20fb244
style: satisfy rubocop safe navigation rule
kholdrex Jul 3, 2026
d0c5d7e
fix: align ask linting with query allowlist safety
kholdrex Jul 3, 2026
7d4951e
test: satisfy rubocop for ask allowlist specs
kholdrex Jul 3, 2026
324eec2
fix: fail closed for relation-only subquery policies
kholdrex Jul 3, 2026
129b842
fix: satisfy rubocop spacing in query spec
kholdrex Jul 3, 2026
0595c66
fix: keep related subqueries inside explicit allowlists
kholdrex Jul 3, 2026
e9be8bc
test: align allowlist security specs
kholdrex Jul 3, 2026
e5f825b
style: satisfy word array cop
kholdrex Jul 3, 2026
424963b
fix: block quoted top-level aliases outside allowlist
kholdrex Jul 4, 2026
ac59ded
fix: cover quoted aliases in allowlist guard
kholdrex Jul 4, 2026
b092697
fix: block lateral derived table bypass
kholdrex Jul 4, 2026
d96bca5
fix: block lateral derived tables in allowlist guard
kholdrex Jul 4, 2026
1f8efe7
fix: preserve subquery policy enforcement context
kholdrex Jul 5, 2026
4a97316
fix: enforce policy allowlists for related tables
kholdrex Jul 5, 2026
f95a693
fix: preserve nil allowlists when no policy applies
kholdrex Jul 5, 2026
439d41d
fix: normalize policy table allowlist intersections
kholdrex Jul 5, 2026
e9d9f10
fix: fail closed on disjoint policy allowlists
kholdrex Jul 5, 2026
6411c15
fix: carry related-table policy allowlists
kholdrex Jul 6, 2026
8642412
fix: scope related tables to declared exists filters
kholdrex Jul 6, 2026
712be6a
fix: catch outer and natural join allowlist bypasses
kholdrex Jul 6, 2026
9b1448c
fix: fail closed on incomplete policy enforcement
kholdrex Jul 12, 2026
1d7a90c
test: preserve fail-closed policy regressions
kholdrex Jul 12, 2026
66f8af1
Extract SQL scanning from Query
kholdrex Jul 12, 2026
d592455
Fix multiline SQL table scanning
kholdrex Jul 12, 2026
f339455
fix: reject undeclared exists references
kholdrex Jul 12, 2026
c882a1f
fix: validate policy scope per exists occurrence
kholdrex Jul 12, 2026
cec1b30
fix: bind policies to subquery occurrences
kholdrex Jul 12, 2026
e4cf3d4
fix: mask PostgreSQL dollar-quoted literals
kholdrex Jul 12, 2026
5c9e718
fix: satisfy RuboCop identity hash checks
kholdrex Jul 12, 2026
085bdc1
fix: enforce allowlisted related table policies
kholdrex Jul 12, 2026
9211a57
fix: harden quoted table extraction
kholdrex Jul 12, 2026
66e9f33
fix: prevent literal quotes masking table references
kholdrex Jul 12, 2026
b83e086
test: cover quoted identifier masker edge cases
kholdrex Jul 12, 2026
d0645e6
fix: harden policy scope enforcement
kholdrex Jul 12, 2026
706d184
fix: require enforcing policy predicates
kholdrex Jul 12, 2026
9401700
fix: enforce Unicode policy identifier boundaries
kholdrex Jul 12, 2026
49c8a19
test: preserve absent policy table allowlist
kholdrex Jul 12, 2026
0eab149
style: satisfy Ruby layout checks
kholdrex Jul 12, 2026
a8fe98f
fix: enforce related table policy allowlists
kholdrex Jul 12, 2026
3ce10aa
fix: verify question-mark policy bind identity
kholdrex Jul 12, 2026
b496e32
docs: clarify empty policy table allowlists
kholdrex Jul 12, 2026
60733ea
fix: close SQL table allowlist bypasses
kholdrex Jul 13, 2026
83ddae8
test: cover undeclared NOT EXISTS without declarations
kholdrex Jul 13, 2026
5cbc8e8
fix: honor adapter identifier case semantics
kholdrex Jul 13, 2026
7924d23
fix: preserve policy table identifier casing
kholdrex Jul 13, 2026
19af932
fix: enforce exact MySQL table allowlist casing
kholdrex Jul 13, 2026
2803483
fix: scope related policy table authorization
kholdrex Jul 13, 2026
09e65c6
fix: preserve unrestricted base policy linting
kholdrex Jul 13, 2026
877d1bc
fix: dispatch policy adapters safely
kholdrex Jul 13, 2026
a558cce
fix: close query policy execution gaps
kholdrex Jul 13, 2026
e87a48c
style: satisfy query class length gate
kholdrex Jul 13, 2026
31e51a3
fix: enforce adapter-aware column casing
kholdrex Jul 13, 2026
3d345c7
fix: close policy column casing gaps
kholdrex Jul 13, 2026
8b9edde
fix: fail closed on postgres policy table casing
kholdrex Jul 13, 2026
c6eb1f7
fix: reject policy table casing before column checks
kholdrex Jul 13, 2026
cbc52be
fix: verify policy predicate placement
kholdrex Jul 13, 2026
ef9db1b
fix: limit policy identifier folding to ASCII
kholdrex Jul 13, 2026
c521d1a
fix: qualify fallback base policy predicates
kholdrex Jul 13, 2026
520c936
test: isolate singleton configuration per example
kholdrex Jul 13, 2026
ce158ed
fix: close query guardrail bypasses
kholdrex Jul 13, 2026
decee59
fix: bind policy evidence to compiled queries
kholdrex Jul 13, 2026
5161100
style: satisfy SQL scanner lint
kholdrex Jul 13, 2026
ef54d65
fix: enforce related policy columns
kholdrex Jul 14, 2026
a78b609
fix: preserve policy enforcement boundaries
kholdrex Jul 14, 2026
01dc880
fix: block PostgreSQL dynamic query functions
kholdrex Jul 15, 2026
3f01eee
fix: reject Unicode-escaped query functions
kholdrex Jul 15, 2026
8cf5c1e
fix: honor quoted function identifier boundaries
kholdrex Jul 15, 2026
9d03560
fix: close PostgreSQL UESCAPE query bypass
kholdrex Jul 15, 2026
cbb802d
fix: mask quoted identifiers and PostgreSQL E strings
kholdrex Jul 15, 2026
95c46e6
fix: deny PostgreSQL XML export functions
kholdrex Jul 15, 2026
a9b48a0
fix: parse PostgreSQL ONLY relation modifiers
kholdrex Jul 15, 2026
3ad5e0c
fix: scope ONLY parsing to PostgreSQL
kholdrex Jul 15, 2026
7ca34f3
docs: clarify policy query compatibility
kholdrex Jul 20, 2026
04198f5
fix: enforce policy contract for relations
kholdrex Jul 23, 2026
231adf6
fix: reject MySQL TABLE query expressions
kholdrex Jul 30, 2026
c74df83
fix: reject backtick-quoted MySQL table queries
kholdrex Jul 30, 2026
a569459
style: satisfy SQL scanner lint
kholdrex Jul 30, 2026
bdda08e
fix: allow self-referential policy subqueries
kholdrex Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ This file tracks the major changes in each release.
- Added a security policy and threat model covering safe deployment defaults, cross-tenant data exposure, schema-context filtering, observability data exposure, and vulnerability reporting.

### Changed
- Security compatibility: when a `policy_adapter` is configured, executable `Query` objects must come from `CodeToQuery.ask` (or the compiler path) with the compiler's opaque policy contract. Directly constructed queries have no valid contract and now fail closed.
- `Query#sql`, `#params`, `#intent`, and `#metrics` now return detached deep copies. Mutating a returned value no longer changes query state; repeated reads may allocate new objects.
- Compiled policy queries are bound to the exact policy adapter and configuration identity used at compilation. Replacing the adapter or relevant configuration invalidates existing queries; compile them again before execution.
- Compatibility note: policy adapters that explicitly return `allowed_tables: []` deny access to all tables, as intended; omit `allowed_tables` only when no policy table allowlist is being supplied.
- Added explicit explain-gate profiles and deployment tradeoff guidance for `explain_fail_open` in the README, including strict, availability-first, and local/test configuration patterns.
- Breaking default change: EXPLAIN gate errors now fail closed by default; set `explain_fail_open = true` only for availability-first readonly deployments that can tolerate skipped EXPLAIN checks.
- Context pack generation now omits sensitive schema metadata by default for password, secret, token, credential, digest, salt, OTP, and API key style columns; customize `sensitive_column_patterns` when applications use additional naming conventions.
Expand Down
6 changes: 6 additions & 0 deletions docs/pundit-policy-adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ Keep these boundaries explicit:
- **Pundit remains authoritative.** Use Pundit to decide whether the user can access the reporting surface. Do not use natural-language prompts as authorization input.
- **Keep logs narrow.** Prefer logging table names, policy key names, and decisions. Avoid logging raw prompts, row data, full bind values, credentials, or full generated schema context in normal application logs.

### Query lifecycle and compatibility

- With a `policy_adapter` configured, obtain executable queries through `CodeToQuery.ask` or the compiler path. Policy compilation issues an opaque contract that callers cannot construct; a directly instantiated `CodeToQuery::Query` lacks that evidence and fails closed at safety checks and execution.
- A compiled query's policy contract is bound to the exact policy adapter object and configuration instance used to compile it, including the captured database adapter and policy fail-open setting. Replacing the policy adapter or changing that relevant configuration invalidates the query. Recompile instead of retaining policy-bearing queries across configuration changes or application reloads.
- The public `Query#sql`, `#params`, `#intent`, and `#metrics` readers return detached deep copies. Mutating those values does not alter the immutable state used for safety checks or execution, and repeated reads may allocate new objects.

## Lambda adapter for tenant, account, and user predicates

This example keeps table and column exposure small and derives row predicates from trusted application state. It raises on missing users, unknown tables, and denied policies so CodeToQuery fails closed. Define the custom Pundit predicates such as `code_to_query?`, `view_all_tickets?`, and `view_account_users?` on the relevant policies before using this shape.
Expand Down
15 changes: 11 additions & 4 deletions lib/code_to_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
require_relative 'code_to_query/configuration'
require_relative 'code_to_query/errors'
require_relative 'code_to_query/instrumentation'
require_relative 'code_to_query/identifier_semantics'
require_relative 'code_to_query/policy_adapter_invoker'
require_relative 'code_to_query/providers/base'
require_relative 'code_to_query/providers/openai'
require_relative 'code_to_query/providers/local'
Expand Down Expand Up @@ -110,7 +112,9 @@ def self.ask(prompt:, schema: nil, allow_tables: nil, current_user: nil)
policy_applied: false
}
compiled = Instrumentation.instrument(:compile, compile_payload) do
compile_result = Compiler.new(config).compile(validated_intent, current_user: current_user)
compile_result = Compiler.new(config).compile(
validated_intent, current_user: current_user, allow_tables: allow_tables
)
compile_payload[:policy_applied] = policy_applied_from_bind_spec?(compile_result[:bind_spec])
compile_result
end
Expand All @@ -124,9 +128,13 @@ def self.ask(prompt:, schema: nil, allow_tables: nil, current_user: nil)
row_limit: validated_intent['limit'],
policy_applied: policy_applied
}
query = Query.new(sql: compiled[:sql], params: compiled[:params], bind_spec: compiled[:bind_spec],
intent: compiled[:intent] || validated_intent, allow_tables: allow_tables, config: config,
policy_contract: compiled[:policy_contract])

begin
Instrumentation.instrument(:lint, **lint_payload) do
Guardrails::SqlLinter.new(config, allow_tables: allow_tables).check!(compiled[:sql])
query.send(:lint_sql!)
end
rescue SecurityError => e
Instrumentation.instrument(
Expand All @@ -141,8 +149,7 @@ def self.ask(prompt:, schema: nil, allow_tables: nil, current_user: nil)
raise
end

Query.new(sql: compiled[:sql], params: compiled[:params], bind_spec: compiled[:bind_spec],
intent: validated_intent, allow_tables: allow_tables, config: config)
query
end

def self.query_shape(intent)
Expand Down
Loading
Loading