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" {