Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion internal/storage/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions internal/storage/sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ 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" {
t.Fatalf("job_log column = %q, want job_id", jobIDColumn)
}

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" {
Expand Down
Loading