IMPORTANT
Every PR MUST have exactly one of these labels, the merge is blocked otherwise:
major/minor/patchpublishes a new release on merge with the corresponding semver bump.skip-releasemerges without publishing a release (use for docs, CI changes, refactors, etc.).
📝 Description
A read-only operation rule let Postgres writes through. EXPLAIN ANALYSE (British spelling) and a quoted "analyze" option ran their DELETE but were classified as a plan. SELECT ... INTO t created t but was classified as select, or as unknown in parentheses.
The sidecar lexer now reads ANALYSE and a quoted option name as ANALYZE, gives Postgres SELECT ... INTO a create effect, and reads a parenthesis where a statement may begin as a query.
📣 User-facing impact
A sidecar rule that refuses writes now also refuses EXPLAIN ANALYSE, EXPLAIN ("analyze"), ANALYSE and SELECT ... INTO on Postgres.
🔗 Related Issue
ENG-803
🚀 Type of Change
- 🐛 Bug fix (non-breaking change which fixes an issue)
- ✨ New feature (non-breaking change which adds functionality)
- 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
- 📚 Documentation update
- 🎨 Style/UI update
- ♻️ Code refactor
- ⚡ Performance improvement
- ✅ Test update
- 🔧 Build configuration change
- 🧹 Chore
📋 Changes Made
sidecar/lexer/keywords.go:analyseis an EXPLAIN option and a statement verb, the same asanalyze.sidecar/lexer/analyze.go: a quoted name in EXPLAIN's option list counts as ANALYZE unless it names another known option. PostgreSQL compares a quoted option name verbatim.sidecar/lexer/analyze.go: an ANALYZE option whose whole value isfalse,offor0does not count, soEXPLAIN (ANALYZE false) DELETEis a plan, as in PostgreSQL.0x1or0_1is 1 in PostgreSQL 16 and stays on. One ANALYZE that is on still wins.sidecar/lexer/analyze.go: on Postgres,SELECT ... INTO tadds acreateeffect. The relation was already reported as written.sidecar/lexer/analyze.go: a(where a statement may begin, beforeSELECT,WITH,VALUES,TABLEor another(, opens a query, so theSELECTinside it is a statement head:(SELECT ...)is a select, not unknown. Any other(stays an expression:CASE ... ELSE (update) ENDreads a column.- Tests:
TestReadOnlyRuleRefusesWritesBehindExplainAndSelectIntoinsidecar/inspect/bypass_test.go,TestParenthesizedExpressionIsNotAStatement, new cases inTestExplainPlansButAnalyzeExecutesandTestObjectCreatingForms, and the statements in thelexer/conformancecorpus.
🧪 Testing
Result: on main, a sidecar with the read-only rule let six Postgres statements delete rows and create tables, each audited as an allowed select or unknown. With the fix, the sidecar refuses all six as delete or create, and the database does not change. Full write-up: EVIDENCE.md.
1. Does a read-only lane refuse these writes? The real hoop-inspect built from each side, in front of Postgres 16, with the Marketplace preset's read-only rule and no license. The last table is read without the sidecar.
2. Do plans and reads still pass? Yes, CASE reads of columns named update and delete included. (SELECT ...) moves from unknown to select.
3. Does the value of ANALYZE decide, as in Postgres? false and 0 only plan: main refused them, the branch allows them. 0x1 and 0o1 are 1 in Postgres 16 and run the DELETE: main let "analyze" 0o1 delete a row, the branch refuses both.
What it solves: a read-only rule on a Postgres sidecar lane, as the Marketplace preset and the agent templates ship it, refuses every write ENG-803 found, and lets the plans Postgres only plans.
Definition of done (ENG-803)
-
EXPLAIN ANALYSE DELETE,EXPLAIN ("analyze") DELETEandEXPLAIN (FORMAT JSON, "analyze") DELETEclassify asdelete, and a read-only operation rule refuses them: item 1. -
SELECT ... INTO tandSELECT ... INTO TEMP tclassify ascreate, in parentheses too: item 1. - Each case is in
sidecar/inspect/bypass_test.goas an operator's verdict, and fails onmain:TestReadOnlyRuleRefusesWritesBehindExplainAndSelectInto(9 refusals fail onmain). -
lexer/conformancepasses with the cases in its corpus (101/103 exact, 2 conceded, 0 wrong);EXPLAIN ANALYSEand(SELECT * INTO ...)fail there onmain.
Guards: TestReadOnlyRuleRefusesWritesBehindExplainAndSelectInto, TestExplainPlansButAnalyzeExecutes, TestObjectCreatingForms, TestParenthesizedExpressionIsNotAStatement, and the lexer/conformance corpus.
Re-run
cd sidecar && go test ./lexer/ ./inspect/ -run 'ReadOnlyRule|ExplainPlans|ObjectCreating|ParenthesizedExpression' -vcd sidecar/lexer/conformance && CGO_ENABLED=0 go test ./...
Test Configuration:
- Browser(s): n/a
- OS: macOS;
hoop-inspectbuilt fromorigin/main9d4534086and the branch5b8842a7c, Postgres 16 in Docker (QA: Postgres 17).
Tests performed:
- Unit tests pass
- Integration tests pass (CI)
- Manual testing completed: independent QA passed in round 5 on
5b8842a7c, on real relays in front of Postgres 17, simple and extended protocol
📸 Screenshots (if applicable)
In Testing above.
✅ Checklist
- My code follows the style guidelines of this project
- I have performed a self-review of my own code
- I have commented my code, particularly in hard-to-understand areas
- My changes generate no new warnings
- New and existing unit tests pass locally with my changes
- I have checked my code and corrected any misspellings
📄 Additional Notes
- Scope is the Postgres dialect. T-SQL's
SELECT INTOalso creates a table and is still a select; ENG-905 covers dialects outside Postgres. - The parenthesis change is not Postgres only: in every dialect,
(SELECT ...)and(SELECT ...) UNION (SELECT ...)are nowselect, notunknown. A rule that namesunknownno longer refuses them. - Checked with a differential: main and the branch on the lexer's test literals plus 40,000 seeded inputs, in every dialect. PostgreSQL's parser judged each changed Postgres input: no new write on a statement Postgres accepts, and no write lost.
EXPLAIN (ANALYZE false) DELETEwas refused onmain, although Postgres only plans it. It passes now, quoted or not (Qodo's review).- After the release: the Marketplace module, the agent templates and their READMEs name ENG-803 as a known gap. Remove those notes when their sidecar pin moves past this release.
🤖 Generated with Claude Code
Activity
2191baeeaPR Summary by Qodo
Close PostgreSQL read-only rule bypasses in SQL classification
🐞 Bug fix 🧪 Tests 🕐 40+ Minutes
AI Description
-
• Classify EXPLAIN ANALYSE and quoted analyze options as executing their wrapped statements. • Report PostgreSQL SELECT INTO, including parenthesized forms, as table creation. • Test read-only refusals while preserving ordinary plans, selects, and parenthesized expressions.
Diagram
-
graph TD SQL["PostgreSQL SQL"] --> Tokens["Lexer tokens"] --> Context["Statement context"] --> Effects["Operation effects"] --> Inspect["Inspect mapping"] --> Rule["Read-only rule"]
High-Level Assessment
-
The following are alternative approaches to this PR:
1. Use a PostgreSQL syntax parser
-
- ➕ Could model EXPLAIN options and SELECT INTO from parsed syntax rather than lexer context.
- ➖ Adds a substantial dependency and does not replace the existing multi-dialect classifier or policy mapping.
Recommendation: Keep the targeted lexer changes: they fit the existing effect-based policy pipeline and cover the demonstrated bypasses. A full parser is disproportionate to this fix, though continued dialect edge cases may warrant revisiting that trade-off.
-
Files changed (5) +223 / -5
-
Bug fix (2)
+72 / -5-
analyze.go
Recognize executing EXPLAIN options and SELECT INTO effects+68/-3
Recognize executing EXPLAIN options and SELECT INTO effects
• Treats quoted EXPLAIN option names conservatively, recognizes ANALYSE, and adds a create effect for PostgreSQL SELECT INTO. Distinguishes parenthesized queries from expressions so nested SELECT statements retain their head position.keywords.go
Recognize ANALYSE as a verb and EXPLAIN option+4/-2
Recognize ANALYSE as a verb and EXPLAIN option
• Adds PostgreSQL’s British spelling to the statement-verb and EXPLAIN modifier tables.
Tests (3)
+151 / -0-
bypass_test.go
Verify read-only rules refuse formerly allowed writes+72/-0
Verify read-only rules refuse formerly allowed writes
• Adds policy-level cases for executing EXPLAIN forms, ANALYSE, and SELECT INTO, including nested parentheses. Confirms ordinary plans, selects, and parenthesized column expressions remain allowed.oracle_test.go
Expand SQL conformance corpus with bypass forms+9/-0
Expand SQL conformance corpus with bypass forms
• Adds executing EXPLAIN ANALYSE and quoted-option statements, plus temporary and parenthesized SELECT INTO examples.lexer_test.go
Cover write effects and parenthesis classification+70/-0
Cover write effects and parenthesis classification
• Tests executing versus non-executing EXPLAIN options and PostgreSQL SELECT INTO variants. Adds cross-dialect checks that parenthesized expressions containing statement-like column names are not classified as writes.
-
Code Review by Qodo
🐞 Bugs (0) 📘 Rule violations (2) 📎 Requirement gaps (1) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)
1. Zero-valued options wrongly execute deletes ✗ Dismissed
Description
explainOptionOff recognizes only a single Number token whose text is 0, so split zero-valued literals such as 0x0, 0o0, 0b0 and 0_0 are treated as enabled. PostgreSQL accepts these literals as zero option values, causing the following DELETE to be reported as executing and refused by a read-only rule.
Code
+ if !a.explainOptionName(i) || !a.puncts(i+2, ",") && !a.puncts(i+2, ")") {
Relevance
●●● StrongConcrete PostgreSQL misclassification; deterministic lexer edge cases receive regression coverage.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The changed boundary check requires the value to be one token, while the scanner splits literals such as 0x0 and 0_0 into Number("0") followed by a word. The analyzer then treats the option as enabled whenever this check returns false, and the focused tests cover only corresponding nonzero values.
EXPLAIN write variants are refused on read-only lanessidecar/lexer/analyze.go[610-623]sidecar/lexer/scan.go[795-806]sidecar/lexer/lexer_test.go[327-355]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description Zero-valued non-decimal and underscored PostgreSQL literals are tokenized into multiple tokens, so `EXPLAIN (ANALYZE 0x0)`, `0o0`, `0b0`, and `0_0` are incorrectly treated as executing the wrapped statement. ## Fix Focus Areas - sidecar/lexer/analyze.go[610-624] - sidecar/lexer/lexer_test.go[327-355] ## Recommended Fix Update the EXPLAIN option value handling to recognize PostgreSQL zero-valued non-decimal and underscored literals as `ANALYZE` off while preserving nonzero values as on. Add regression tests for each zero-valued literal form and verify that the wrapped `DELETE` has no write effect.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
2. Plan-only statements are refused as writes ✓ Resolved
Description
The quoted-option branch sets analyzeSeen for "analyze" without checking whether its value disables execution. When a user runs EXPLAIN ("analyze" false) DELETE FROM customers, the analyzer retains the delete effect even though the statement only produces a plan, so a read-only rule refuses it.
Code
sidecar/lexer/analyze.go[R438-439]+ if t.Kind == Quoted && a.explainOptionName(i) && (t.Text == "analyze" || !wrapperModifier[t.Text]) { + a.analyzeSeen = true
Relevance
●●● StrongThe PostgreSQL oracle explicitly covers false/off/0, exposing a deterministic execution-classification gap.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The new branch sets analyzeSeen based on the quoted name alone. result() retains mutating effects whenever analyzeSeen is true, while the repository's PostgreSQL oracle explicitly treats an ANALYZE option with false, off, or 0 as non-executing.
sidecar/lexer/analyze.go[438-440]sidecar/lexer/analyze.go[1385-1402]sidecar/lexer/conformance/oracle_test.go[318-341]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description A quoted `"analyze"` option is treated as enabled even when its explicit value is `false`, `off`, or `0`, causing plan-only statements to be classified as writes. ## Fix Focus Areas - sidecar/lexer/analyze.go[438-440] - sidecar/lexer/lexer_test.go[313-335] ## Recommended Fix Inspect the quoted option's value before setting `analyzeSeen`, preserving conservative handling of unknown quoted option names. Add regression cases for quoted `"analyze"` with disabling values.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
3. New query forms lack oracle coverage ✓ Resolved
Description
opensQuery now classifies nested parentheses and parenthesized WITH, VALUES, and TABLE forms as query starts, while quoted EXPLAIN handling also covers undecoded escaped names. The PostgreSQL conformance corpus includes only a single parenthesized SELECT and plain quoted analyze cases, so these new branches are not all checked against the PostgreSQL parser.
Code
sidecar/lexer/analyze.go[R757-765]+func (a *analyzer) opensQuery(i int) bool { + if a.puncts(i+1, "(") { + return true + } + if i+1 >= len(a.toks) { + return false + } + next := a.toks[i+1] + return next.isWord("select") || next.isWord("with") || next.isWord("values") || next.isWord("table")
Relevance
●● ModerateRecommendation generated with fallback mode.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The implementation adds classifications for nested parentheses, four query-start keywords, and escaped quoted EXPLAIN options, but the corpus only contains one parenthesized SELECT form and plain quoted analyze options.
Rule 3062508: Update lexer conformance corpus for new classification casessidecar/lexer/analyze.go[438-440]sidecar/lexer/analyze.go[741-765]sidecar/lexer/conformance/oracle_test.go[604-610]sidecar/lexer/conformance/oracle_test.go[653-657]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description Several lexer classification branches added in this PR are covered by unit tests but not by the PostgreSQL conformance corpus required for oracle validation. ## Fix Focus Areas - sidecar/lexer/analyze.go[438-440] - sidecar/lexer/analyze.go[741-742] - sidecar/lexer/analyze.go[757-765] - sidecar/lexer/conformance/oracle_test.go[604-610] - sidecar/lexer/conformance/oracle_test.go[653-657] ## Recommended Fix Add PostgreSQL corpus cases for nested parenthesized queries, parenthesized `WITH`, `VALUES`, and `TABLE` statements, and the escaped quoted option form such as `EXPLAIN (U&"\\0061nalyze") DELETE ...`. Run the conformance suite and retain the cases in `oracle_test.go`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
4. New option values lack PostgreSQL cases 📘 Rule violation ≡ Correctness ⭐ New
Description
explainOptionOff adds special handling for whole values such as false, off, 0, and PostgreSQL 16 numeric forms, but the conformance corpus contains no corresponding value cases. The behavior is covered only by unit tests, so the PostgreSQL comparison path is not exercised for these newly classified forms.
Code
sidecar/lexer/analyze.go[R610-617]+// explainOptionOff reports whether the value of the EXPLAIN option named at i +// turns it off: `EXPLAIN (ANALYZE false) DELETE` plans the DELETE and runs +// nothing. PostgreSQL reads false, off and 0 as off. The value must be the +// whole value, ended by a comma or the closing parenthesis: the scanner reads +// `0x1`, `0_1` or `0b1` as 0 and a word, and PostgreSQL 16 reads each as 1. +// Any other value leaves the option on, a string literal included, whose text +// the scanner does not keep: that reading refuses a write. One ANALYZE that is +// on is enough, so a second one that is off cannot hide it.
Relevance
● WeakNear-identical corpus-coverage request was explicitly dismissed as a false positive.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The changed analyzer introduces value-sensitive EXPLAIN classification, while the conformance corpus additions cover bare ANALYSE, quoted analyze, and query forms but no disabled or numeric option values. The lexer unit tests cover those values separately, which does not satisfy the corpus-specific coverage requirement.
Rule 3062508: Update lexer conformance corpus for new classification casessidecar/lexer/analyze.go[610-617]sidecar/lexer/conformance/oracle_test.go[604-613]sidecar/lexer/lexer_test.go[325-355]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description The new `EXPLAIN` option-value handling distinguishes disabled values from values that execute the wrapped statement, but the PostgreSQL conformance corpus does not exercise these new forms. ## Fix Focus Areas - sidecar/lexer/conformance/oracle_test.go[604-613] - sidecar/lexer/analyze.go[610-617] ## Recommended Fix Add conformance corpus entries covering representative `ANALYZE` and quoted `"analyze"` values, including `false`, `off`, `0`, and a PostgreSQL 16 numeric form such as `0x1` or `0_1`. Ensure the corpus runner compares each case with PostgreSQL behavior.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
5. Audit evidence omits affected verdicts 📎 Requirement gap ◔ Observability
Description
The PR's TestReadOnlyRuleRefusesWritesBehindExplainAndSelectInto adds in-process assertions but does not provide before-and-after audit rows for the affected statements. The checklist requires an audit row for each EXPLAIN and SELECT INTO case, so reviewers cannot verify the historical allowed verdict and corrected refused verdict from the submitted evidence.
Code
sidecar/inspect/bypass_test.go[R367-370]+ {"EXPLAIN ANALYSE, the British spelling, is refused as the delete it runs", + `EXPLAIN ANALYSE DELETE FROM customers WHERE id = 2`, true, inspect.OpDelete}, + {"EXPLAIN (ANALYSE) is refused as the delete it runs", + `EXPLAIN (ANALYSE) DELETE FROM customers WHERE id = 2`, true, inspect.OpDelete},
Relevance
● WeakA closely matching before-and-after verdict-evidence request was rejected as unnecessary documentation.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The added tests verify classifications and rule decisions in process, but the PR changes contain no before-and-after audit rows for the affected statements.
Provide before-and-after audit evidencesidecar/inspect/bypass_test.go[367-370]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description The PR does not include before-and-after audit rows demonstrating the old allowed verdict and the corrected refused verdict for every affected EXPLAIN and SELECT INTO statement. ## Fix Focus Areas - sidecar/inspect/bypass_test.go[367-384] ## Recommended Fix Add evidence documenting one before-and-after audit row for each listed EXPLAIN ANALYSE, quoted analyze option, and SELECT INTO case, including the classified operation and read-only rule verdict.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
6. Evasion cases lack verdict-string tests 📘 Rule violation ▣ Testability
Description
TestReadOnlyRuleRefusesWritesBehindExplainAndSelectInto supplies SQL statements such as `EXPLAIN ANALYSE and SELECT INTO to inspectOne` instead of testing the exact operator-facing verdict strings required for new evasion cases. These additions exercise parser inputs but do not cover the verdict-string path in inspect/bypass_test.go.
Code
sidecar/inspect/bypass_test.go[R367-372]+ {"EXPLAIN ANALYSE, the British spelling, is refused as the delete it runs", + `EXPLAIN ANALYSE DELETE FROM customers WHERE id = 2`, true, inspect.OpDelete}, + {"EXPLAIN (ANALYSE) is refused as the delete it runs", + `EXPLAIN (ANALYSE) DELETE FROM customers WHERE id = 2`, true, inspect.OpDelete}, + {"a quoted analyze option is refused as the delete it runs", + `EXPLAIN ("analyze") DELETE FROM customers WHERE id = 3`, true, inspect.OpDelete},
Relevance
● WeakSame verdict-string test request was rejected in a closely matching inspect test precedent.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The changed test adds SQL statements and operation assertions rather than operator-facing verdict-string inputs, which fails the required coverage shape for new classification evasion scenarios.
Rule 3062500: Document new classification evasion cases as verdict strings in inspect/bypass_test.gosidecar/inspect/bypass_test.go[356-384]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description The new read-only evasion scenarios are tested as SQL inputs, but the compliance requirement expects each classification evasion case to also be represented by the exact human-readable verdict string an operator receives. ## Fix Focus Areas - sidecar/inspect/bypass_test.go[356-417] ## Recommended Fix Add test cases in `sidecar/inspect/bypass_test.go` using the exact operator-facing verdict strings for the new EXPLAIN, ANALYSE, quoted analyze-option, and SELECT INTO classifications, and assert the expected refusal behavior.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Context sources
🎫 Sidecar classifier: EXPLAIN ANALYSE and a quoted "analyze" option run writes on a read-only lane106 rulesbuild-ticket+8 more✅ Cross-repo context — repo relationships
repo: hoophq/documentation (sha: e6ab4f1a) — View relationshiprepo: hoophq/internal-runtime-control (sha: f9e2bc6f) — View relationshipAuto: ⚖️ Balanced: Localized lexer fix with read-only bypass implications warrants standard review.Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PRPrevious reviews
Review updated until commit 5b8842a
Results up to commit 16986a9 ⚖️ Balanced
🐞 Bugs (0) 📘 Rule violations (1) 📎 Requirement gaps (1) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)
1. Plan-only statements are refused as writes ✓ Resolved
Description
The quoted-option branch sets analyzeSeen for "analyze" without checking whether its value disables execution. When a user runs EXPLAIN ("analyze" false) DELETE FROM customers, the analyzer retains the delete effect even though the statement only produces a plan, so a read-only rule refuses it.
Code
sidecar/lexer/analyze.go[R438-439]+ if t.Kind == Quoted && a.explainOptionName(i) && (t.Text == "analyze" || !wrapperModifier[t.Text]) { + a.analyzeSeen = true
Relevance
●●● StrongThe PostgreSQL oracle explicitly covers false/off/0, exposing a deterministic execution-classification gap.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The new branch sets analyzeSeen based on the quoted name alone. result() retains mutating effects whenever analyzeSeen is true, while the repository's PostgreSQL oracle explicitly treats an ANALYZE option with false, off, or 0 as non-executing.
sidecar/lexer/analyze.go[438-440]sidecar/lexer/analyze.go[1385-1402]sidecar/lexer/conformance/oracle_test.go[318-341]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description A quoted `"analyze"` option is treated as enabled even when its explicit value is `false`, `off`, or `0`, causing plan-only statements to be classified as writes. ## Fix Focus Areas - sidecar/lexer/analyze.go[438-440] - sidecar/lexer/lexer_test.go[313-335] ## Recommended Fix Inspect the quoted option's value before setting `analyzeSeen`, preserving conservative handling of unknown quoted option names. Add regression cases for quoted `"analyze"` with disabling values.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
2. New query forms lack oracle coverage ✓ Resolved
Description
opensQuery now classifies nested parentheses and parenthesized WITH, VALUES, and TABLE forms as query starts, while quoted EXPLAIN handling also covers undecoded escaped names. The PostgreSQL conformance corpus includes only a single parenthesized SELECT and plain quoted analyze cases, so these new branches are not all checked against the PostgreSQL parser.
Code
sidecar/lexer/analyze.go[R757-765]+func (a *analyzer) opensQuery(i int) bool { + if a.puncts(i+1, "(") { + return true + } + if i+1 >= len(a.toks) { + return false + } + next := a.toks[i+1] + return next.isWord("select") || next.isWord("with") || next.isWord("values") || next.isWord("table")
Relevance
●● ModerateRecommendation generated with fallback mode.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The implementation adds classifications for nested parentheses, four query-start keywords, and escaped quoted EXPLAIN options, but the corpus only contains one parenthesized SELECT form and plain quoted analyze options.
Rule 3062508: Update lexer conformance corpus for new classification casessidecar/lexer/analyze.go[438-440]sidecar/lexer/analyze.go[741-765]sidecar/lexer/conformance/oracle_test.go[604-610]sidecar/lexer/conformance/oracle_test.go[653-657]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description Several lexer classification branches added in this PR are covered by unit tests but not by the PostgreSQL conformance corpus required for oracle validation. ## Fix Focus Areas - sidecar/lexer/analyze.go[438-440] - sidecar/lexer/analyze.go[741-742] - sidecar/lexer/analyze.go[757-765] - sidecar/lexer/conformance/oracle_test.go[604-610] - sidecar/lexer/conformance/oracle_test.go[653-657] ## Recommended Fix Add PostgreSQL corpus cases for nested parenthesized queries, parenthesized `WITH`, `VALUES`, and `TABLE` statements, and the escaped quoted option form such as `EXPLAIN (U&"\\0061nalyze") DELETE ...`. Run the conformance suite and retain the cases in `oracle_test.go`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
3. Evasion cases lack verdict-string tests 📘 Rule violation ▣ Testability
Description
TestReadOnlyRuleRefusesWritesBehindExplainAndSelectInto supplies SQL statements such as `EXPLAIN ANALYSE and SELECT INTO to inspectOne` instead of testing the exact operator-facing verdict strings required for new evasion cases. These additions exercise parser inputs but do not cover the verdict-string path in inspect/bypass_test.go.
Code
sidecar/inspect/bypass_test.go[R367-372]+ {"EXPLAIN ANALYSE, the British spelling, is refused as the delete it runs", + `EXPLAIN ANALYSE DELETE FROM customers WHERE id = 2`, true, inspect.OpDelete}, + {"EXPLAIN (ANALYSE) is refused as the delete it runs", + `EXPLAIN (ANALYSE) DELETE FROM customers WHERE id = 2`, true, inspect.OpDelete}, + {"a quoted analyze option is refused as the delete it runs", + `EXPLAIN ("analyze") DELETE FROM customers WHERE id = 3`, true, inspect.OpDelete},
Relevance
● WeakSame verdict-string test request was rejected in a closely matching inspect test precedent.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The changed test adds SQL statements and operation assertions rather than operator-facing verdict-string inputs, which fails the required coverage shape for new classification evasion scenarios.
Rule 3062500: Document new classification evasion cases as verdict strings in inspect/bypass_test.gosidecar/inspect/bypass_test.go[356-384]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description The new read-only evasion scenarios are tested as SQL inputs, but the compliance requirement expects each classification evasion case to also be represented by the exact human-readable verdict string an operator receives. ## Fix Focus Areas - sidecar/inspect/bypass_test.go[356-417] ## Recommended Fix Add test cases in `sidecar/inspect/bypass_test.go` using the exact operator-facing verdict strings for the new EXPLAIN, ANALYSE, quoted analyze-option, and SELECT INTO classifications, and assert the expected refusal behavior.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
4. Audit evidence omits affected verdicts 📎 Requirement gap ◔ Observability
Description
The PR's TestReadOnlyRuleRefusesWritesBehindExplainAndSelectInto adds in-process assertions but does not provide before-and-after audit rows for the affected statements. The checklist requires an audit row for each EXPLAIN and SELECT INTO case, so reviewers cannot verify the historical allowed verdict and corrected refused verdict from the submitted evidence.
Code
sidecar/inspect/bypass_test.go[R367-370]+ {"EXPLAIN ANALYSE, the British spelling, is refused as the delete it runs", + `EXPLAIN ANALYSE DELETE FROM customers WHERE id = 2`, true, inspect.OpDelete}, + {"EXPLAIN (ANALYSE) is refused as the delete it runs", + `EXPLAIN (ANALYSE) DELETE FROM customers WHERE id = 2`, true, inspect.OpDelete},
Relevance
● WeakA closely matching before-and-after verdict-evidence request was rejected as unnecessary documentation.
ⓘ Recommendations generated based on similar findings in past PRs
Evidence
The added tests verify classifications and rule decisions in process, but the PR changes contain no before-and-after audit rows for the affected statements.
Provide before-and-after audit evidencesidecar/inspect/bypass_test.go[367-370]
Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution ## Issue description The PR does not include before-and-after audit rows demonstrating the old allowed verdict and the corrected refused verdict for every affected EXPLAIN and SELECT INTO statement. ## Fix Focus Areas - sidecar/inspect/bypass_test.go[367-384] ## Recommended Fix Add evidence documenting one before-and-after audit row for each listed EXPLAIN ANALYSE, quoted analyze option, and SELECT INTO case, including the classified operation and read-only rule verdict.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Qodo findings 3 and 4 need no code change:
- 3, verdict strings: each new case in
TestReadOnlyRuleRefusesWritesBehindExplainAndSelectIntois named as the verdict an operator gets ("... is refused as the delete it runs", "... passes"), and assertsDeniedandOperationthrough the postgres wire path, assidecar/CLAUDE.mdasks. - 4, audit rows: the before and after audit rows of every EXPLAIN and SELECT INTO statement are in Testing, item 1: the real
hoop-inspectin front of Postgres, onmainand on this branch. EVIDENCE.md has them too.
Qodo findings 3 and 4 need no code change:
- 3, verdict strings: each new case in
TestReadOnlyRuleRefusesWritesBehindExplainAndSelectIntois named as the verdict an operator gets ("... is refused as the delete it runs", "... passes"), and assertsDeniedandOperationthrough the postgres wire path, assidecar/CLAUDE.mdasks.- 4, audit rows: the before and after audit rows of every EXPLAIN and SELECT INTO statement are in Testing, item 1: the real
hoop-inspectin front of Postgres, onmainand on this branch. EVIDENCE.md has them too.
The finding numbers are reversed in your summary:
- finding 3 is “Audit evidence omits affected verdicts.” If the PR’s
Testingitem 1 andEVIDENCE.mdcontain the claimed before/afterhoop-inspectaudit rows for all affected statements, then this finding’s premise is no longer valid and no code change is needed. I could not findEVIDENCE.mdin the checked-out PR files, so I cannot independently verify those rows here. - finding 4 is “Evasion cases lack verdict-string tests.” The test currently asserts SQL classification through
inspectOne,Denied, andOperation(sidecar/inspect/bypass_test.go:414-477); those are not exact operator-facing verdict-string inputs. Therefore, the statedsidecar/CLAUDE.mdtesting guidance does not by itself address this finding, and the finding remains applicable unless the required verdict-string coverage exists elsewhere.
For the rule behind finding 4, its scope can be updated or disabled here: https://app.qodo.ai/rules/3062500?state=active
No dismissal was performed because you explained why the findings need no code change but did not explicitly ask to dismiss them.
Code review by qodo was updated up to the latest commit https://github.com/hoophq/hoop/commit/46212b42b144b9684d9f971eccf1ca131cca2b72
Code review by qodo was updated up to the latest commit https://github.com/hoophq/hoop/commit/5b8842a7cbfdb3c8fd9de143efc2b2bff5cbab27
✅ Build Completed with Success, Version=2087.0.0-g5b8842a
Right, the numbers moved when the review was updated. By title, neither needs a code change:
- Audit evidence omits affected verdicts: the before and after audit rows are in Testing, items 1 and 3, and in EVIDENCE.md.
- Evasion cases lack verdict-string tests: each case is named as the verdict an operator gets and asserts it (
DeniedandOperation) through the postgres wire path, assidecar/CLAUDE.mdasks.
✅ Build Completed with Success, Version=2087.0.0-g16986a9