From 6226ddc75ab964f69c317e9d26f825ee0e234f13 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 12:01:59 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20SQL=20injection=20vulnerability=20in=20PRAGMA=20query?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Severity:** CRITICAL **Vulnerability:** The function `sqliteColumnExists` in `internal/storage/sqlite.go` used `fmt.Sprintf` to concatenate the `table` argument directly into a `PRAGMA table_info(%s);` SQL query string. This is a classic SQL injection vulnerability if the table parameter ever receives untrusted input, because `PRAGMA` statements in SQLite do not support bind parameters. Test files had the same string interpolation practice. **Impact:** While currently the `table` arguments might come from trusted internal schemas, static analysis and automated security scanners correctly flag this as a critical SQL injection risk. Any future refactor allowing user input to this function would result in full SQL injection leading to database compromise. **Fix:** Replaced the unsafe string interpolation with the SQLite table-valued function `pragma_table_info(?)`, which acts like a normal table and safely supports prepared statement parameter binding. Updated tests to use the same parameterized format. **Verification:** Ran `go test -v ./internal/storage/...` to verify SQLite functions still correctly interrogate schemas and table info. Tests pass. **Learning:** Documented the security pattern regarding SQLite PRAGMA vs `pragma_table_info(?)` in `.jules/sentinel.md` to prevent future re-introduction of this vulnerability. Co-authored-by: mattjoyce <278869+mattjoyce@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ internal/storage/sqlite.go | 2 +- internal/storage/sqlite_test.go | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 .jules/sentinel.md diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..c79e1663 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2025-02-26 - [Fix SQL Injection in pragma_table_info] +**Vulnerability:** String formatting (fmt.Sprintf) was used to construct a SQL query calling the SQLite pragma_table_info function, allowing for potential SQL injection if the table name was user-controlled. The `sqliteColumnExists` function dynamically passed table names into `PRAGMA table_info(table);` but PRAGMAs cannot be parameterized. Tests also used unparameterized formats. +**Learning:** SQLite's `PRAGMA` statements do not support prepared statement parameters (bound values). Therefore, dynamically building `PRAGMA` queries using string formatting is unsafe if input isn't strictly controlled, and it triggers automated security scan alerts. The table-valued function `pragma_table_info(?)` acts as a safe alternative. +**Prevention:** Avoid string formatting for SQLite table metadata queries. Use the table-valued function `pragma_table_info(?)` which safely supports parameter binding. diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go index 8c0cb1ed..b6fb6719 100644 --- a/internal/storage/sqlite.go +++ b/internal/storage/sqlite.go @@ -200,7 +200,7 @@ LIMIT 1; } func sqliteColumnExists(ctx context.Context, db *sql.DB, table, column string) (bool, error) { - cols, err := db.QueryContext(ctx, fmt.Sprintf("PRAGMA table_info(%s);", table)) + cols, err := db.QueryContext(ctx, `SELECT cid, name, type, "notnull", dflt_value, pk FROM pragma_table_info(?);`, table) if err != nil { return false, err } diff --git a/internal/storage/sqlite_test.go b/internal/storage/sqlite_test.go index ce150f0e..c8be45d3 100644 --- a/internal/storage/sqlite_test.go +++ b/internal/storage/sqlite_test.go @@ -26,7 +26,7 @@ func TestOpenSQLiteBootstrapsTables(t *testing.T) { } var jobIDColumn string - if err := db.QueryRow("SELECT name FROM pragma_table_info('job_log') WHERE name = 'job_id';").Scan(&jobIDColumn); err != nil { + if err := db.QueryRow("SELECT name FROM pragma_table_info(?) WHERE name = ?;", "job_log", "job_id").Scan(&jobIDColumn); err != nil { t.Fatalf("job_log.job_id missing: %v", err) } if jobIDColumn != "job_id" { @@ -34,7 +34,7 @@ func TestOpenSQLiteBootstrapsTables(t *testing.T) { } var seqColumn string - if err := db.QueryRow("SELECT name FROM pragma_table_info('plugin_facts') WHERE name = 'seq';").Scan(&seqColumn); err != nil { + if err := db.QueryRow("SELECT name FROM pragma_table_info(?) WHERE name = ?;", "plugin_facts", "seq").Scan(&seqColumn); err != nil { t.Fatalf("plugin_facts.seq missing: %v", err) } if seqColumn != "seq" {