Skip to content

fix(analyzer): honor sqlguard:ignore only inside SQL comments - #92

Merged
KARTIKrocks merged 3 commits into
mainfrom
fix/ignore-directive-comment-only
Sep 26, 2026
Merged

KARTIKrocks merged 3 commits into
mainfrom
fix/ignore-directive-comment-only

Conversation

@KARTIKrocks

@KARTIKrocks KARTIKrocks commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

User description

Closes #66.

What was wrong

The in-SQL sqlguard:ignore directive only had to follow a comment marker somewhere earlier in the text, and that marker could itself sit inside a string. So SELECT * FROM users WHERE note = '-- sqlguard:ignore' reported nothing. Where a query embeds user input, a value could switch every rule off for the statement. The docs and the Greptile rationale both claimed the marker anchoring prevented exactly this.

What changed

  • parseIgnoreDirective now lexes the SQL and matches the directive only inside comment spans (--, /* */, #), skipping quoted runs, quoted identifiers and dollar-quoted bodies whole.
  • Literal ambiguity: where dialects disagree about where a literal ends (backslash escapes, $$), every reading is lexed and a directive counts only if all of them agree. This is the opposite side from Redact's union, on purpose: a spurious suppression hides findings, a missed one only reports a finding.
  • Comment-marker ambiguity: # is XOR in PostgreSQL, and MySQL's -- needs trailing whitespace. A directive found in a comment is dropped if any combination of those two readings puts it inside a literal (covers a # 4 AND note = '...', 1--'...', and MySQL's own mix).
  • // inside SQL text is no longer honored; it is not a SQL comment in any dialect and was never documented. The Go-source // sqlguard:ignore read by the scanner (ParseIgnoreComment) is unaffected.

Known cost: a # comment containing an apostrophe (# don't flag: sqlguard:ignore) is not honored, since under the readings where # is not a comment the apostrophe opens a literal. -- or /* */ work there.

Tests

  • TestParseIgnoreDirective: genuine directives, directives inside each kind of literal, both literal-ambiguity cases, and the # / -- / MySQL-mix cases. The literal cases fail on main.
  • TestIgnoreDirectiveInLiteralDoesNotSuppress: the sqlguard:ignore inside a string literal silences every rule #66 reproduction end to end.
  • make fmt-check vet lint test-race lint-docs green across all nine modules.

Docs

website/docs/suppressions.md (_Changed in 0.6._), CHANGELOG, AGENTS.md, and all reviewer configs: .greptile/rules.md rewritten (it stated the false claim), and a new suppression-comment-only rule in .greptile/config.json, .codeant/review.json and .coderabbit.yaml.


CodeAnt-AI Description

Prevent SQL text from disabling analysis through false sqlguard:ignore directives

What Changed

  • SQL suppression directives are honored only when they appear in actual SQL comments, not in string literals, quoted identifiers, or dollar-quoted bodies
  • Ambiguous SQL comment and literal syntax now fails closed, so uncertain directives do not suppress findings
  • // sqlguard:ignore is no longer treated as a SQL comment, while Go-source suppression remains supported
  • Added coverage and documentation for literal, dialect, and comment-marker edge cases

Impact

✅ Prevented user-supplied SQL values from hiding analyzer findings
✅ Fewer false suppressions across SQL dialects
✅ Clearer and safer suppression behavior

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

The in-SQL directive only had to follow a comment marker somewhere
earlier in the text, and the marker could itself sit inside a string, so
SELECT * FROM users WHERE note = '-- sqlguard:ignore' reported nothing.
Where a query embeds user input, a value could switch every rule off.

parseIgnoreDirective now lexes the SQL and matches the directive only in
comment spans, skipping quoted runs and dollar-quoted bodies. Where
dialects disagree about literals (backslash escapes, $$) every reading
is lexed and a directive counts only if all agree. Comment markers vary
too: # is XOR in Postgres and MySQL's -- needs trailing whitespace, so a
directive is dropped if the strict markers put it inside a literal. //
inside SQL, never a SQL comment, is no longer honored.

Closes #66
…ctives

Comparing only "all markers on" against "all markers off" missed MySQL's
actual mix, where # is a comment but --x is not. In
1 # '\n--x' -- sqlguard:ignore ' the directive sits inside a MySQL string
yet still suppressed every rule. A directive found in a comment is now
dropped if any combination of the # and -- readings puts it inside a
literal.
@codeant-ai

codeant-ai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR bd08614 Sep 26, 2026 · 00:44 00:46

@codeant-ai

codeant-ai Bot commented Sep 26, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Repository: KARTIKrocks/sqlguard/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 37f6a6df-edbd-4bc0-b68a-a4561d68f70b


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codeant-ai

codeant-ai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🏁 CodeAnt Quality Gate Results

Commit: d0a4b9ef
Scan Time: 2026-09-26 00:48:40 UTC

✅ Overall Status: PASSED

Quality Gate Details

Quality Gate Status Details
Secrets ✅ PASSED 0 secrets found
SAST ✅ PASSED No security issues
SCA (Dependencies) ✅ PASSED Rating S: No vulnerabilities

View Full Results

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Sep 26, 2026
Comment thread analyzer/suppress.go Outdated
Comment thread analyzer/suppress_test.go
// Genuine directives.
{"line comment", "SELECT * FROM t -- sqlguard:ignore", true, nil},
{"block comment scoped", "SELECT * FROM t /* sqlguard:ignore:select-star */", false, []string{"select-star"}},
{"hash comment", "SELECT * FROM t # sqlguard:ignore", true, nil},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Change the expected result to no suppression because # is not a comment marker in every supported dialect, so the directive must be rejected under intersection semantics.

Severity Level: Major ⚠️ · 🏷️ Custom_rule

Rule source 📖

.codeant/review.json line 22 (rule "suppression-comment-only")

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** analyzer/suppress_test.go
**Line:** 21:21
**Comment:**
	*Custom Rule: Change the expected result to no suppression because `#` is not a comment marker in every supported dialect, so the directive must be rejected under intersection semantics.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Declining: # sqlguard:ignore is a documented, working form (a MySQL comment), so this case must keep suppressing. The intersection covers the backslash and $$ literal readings only. The # and -- markers are handled differently: comments are found with every marker, and a directive is dropped only if some marker combination puts it inside a literal (the hash before a literal, mysql double minus and mysql marker mix cases). Here there is no literal, so it counts under every reading. The rule text read as if the intersection also covered the markers; d0a4b9e makes that explicit in .codeant/review.json and the other reviewer configs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Customized review instruction saved!

Instruction:

Treat documented SQL comment markers such as MySQL '#' as valid suppression markers; apply intersection semantics only to literal interpretations, not to whether supported comment markers are recognized.

Applied to:

  • analyzer/suppress_test.go

💡 To manage or update this instruction, visit: CodeAnt AI Settings

Comment thread analyzer/suppress.go
// (# is XOR in Postgres; MySQL's -- needs trailing whitespace) puts it inside
// a literal.
func commentDirective(sql string, backslash, dollar bool) (all bool, rules map[string]bool) {
comments, _ := lexSQL(sql, backslash, dollar, true, 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.

Suggestion: The candidate scan always treats # and tight -- as comments, even when a dialect treats them as operators, so malformed or dialect-specific SQL can suppress findings outside a comment.

Assessment: 🟠 Major · 🔁 Occurrence: Rarely · 🏷️ Incorrect condition logic

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** analyzer/suppress.go
**Line:** 47:47
**Comment:**
	*Incorrect Condition Logic: The candidate scan always treats `#` and tight `--` as comments, even when a dialect treats them as operators, so malformed or dialect-specific SQL can suppress findings outside a comment.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Declining, this is deliberate. Finding comments with every marker is what keeps a genuine MySQL # sqlguard:ignore working. The threat #66 closes is text inside a value, and that is covered: a directive is dropped if any #/-- combination, under any backslash/$$ reading, puts it inside a literal. For # or a tight -- to produce a suppression otherwise, the directive would have to appear as bare SQL tokens outside every literal, e.g. SELECT a # b FROM t sqlguard:ignore. That is not valid SQL in a dialect where # is an operator, a value cannot produce it, and whoever can write raw SQL can already write a real -- sqlguard:ignore.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Customized review instruction saved!

Instruction:

In the SQL suppression candidate scan, continue recognizing all dialect comment markers and discard directives found inside literals under any marker interpretation; do not flag this deliberate behavior as incorrect condition logic.

Applied to:

  • analyzer/suppress.go

💡 To manage or update this instruction, visit: CodeAnt AI Settings

parseIgnoreDirective runs on every analyzed query, and its pre-check
lowercased the whole query, allocating whenever it held an uppercase
byte. containsFold does the same case-insensitive search in place.

Also make the reviewer rules say which readings must all agree: the
backslash and $$ literal readings. The # and -- markers are handled by
dropping a directive a marker combination puts in a literal, so a genuine
# sqlguard:ignore still counts.
@KARTIKrocks
KARTIKrocks merged commit f4a8734 into main Sep 26, 2026
32 checks passed
@KARTIKrocks
KARTIKrocks deleted the fix/ignore-directive-comment-only branch September 26, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sqlguard:ignore inside a string literal silences every rule

1 participant