When building BigQuery applications using the Go SDK you may allow users to
select tables or datasets dynamically. This means you need to include
user-specified identifiers in your SQL queries. I was surprised that the
BigQuery manual and code examples do not warn about SQL injection
vulnerabilities when doing this. Even more surprising: BigQuery does not provide
a built-in mechanism to safely handle user input in table or dataset names. The
official SDK supports parameterized queries for data values using @ and ?
syntax, but these cannot be used for identifiers that need backtick escaping.
You may be tempted to use string concatenation, but that opens the door to SQL
injection, and should be avoided. This post explains the problem and introduces
saferbq, a Go package I wrote to tackle this shortcoming.
The SQL injection problem
When you need to reference a table name that comes from user input, the BigQuery SDK does not provide you with a safe option. Consider this common scenario:
client := bigquery.NewClient(ctx, projId)
tableName := getUserInput()
q := client.Query(fmt.Sprintf("SELECT * FROM `%s` WHERE id = 1", tableName))
q.Run(ctx)
If a user provides the input logs` WHERE 1=1; DROP TABLE customers; -- the
resulting query becomes:
SELECT * FROM `logs` WHERE 1=1; DROP TABLE customers; --` WHERE id = 1
This might execute successfully, returns all logs, and drop your customers table. You might want to use BigQuery’s named parameters as a mitigation:
q := client.Query("SELECT * FROM @table WHERE id = 1")
q.Parameters = []bigquery.QueryParameter{{Name: "table", Value: tableName}}
But this fails with an error because named parameters cannot be used for identifiers, only for data values. The official documentation and examples consistently show string concatenation for table names without any security warnings.
The saferbq solution
I implemented a package called saferbq that adds safe identifier handling to
the BigQuery SDK. It introduces $identifier syntax specifically for table and
dataset names:
client := saferbq.NewClient(ctx, projId)
tableName := getUserInput()
q := client.Query("SELECT * FROM $table WHERE id = 1")
q.Parameters = []bigquery.QueryParameter{{Name: "$table", Value: tableName}}
q.Run(ctx)
If a user tries to inject malicious SQL, the query fails immediately:
Error: identifier contains invalid characters: $table contains `=;
The package works by intercepting queries before they reach BigQuery. It scans
the SQL to identify all $identifier parameters, validates each identifier
value character-by-character against BigQuery’s naming rules, and only then
wraps them in backticks and substitutes them into the query. Invalid characters
like backticks, semicolons, quotes, or slashes cause immediate failure with a
detailed error message. The package also validates that all parameters are
present and that identifiers don’t exceed BigQuery’s 1024-byte limit.
Using saferbq
Install the package:
go get github.com/mevdschee/saferbq
Replace the client creation:
import (
"context"
"cloud.google.com/go/bigquery"
"github.com/mevdschee/saferbq"
)
// client, _ := biqquery.NewClient(ctx, projId)
client, _ := saferbq.NewClient(ctx, projId)
defer client.Close()
You can now use $ parameters for identifiers. Note that you can mix identifier
parameters with BigQuery’s native parameters:
q := client.Query("SELECT * FROM $table WHERE corpus = @corpus AND id = ?")
q.Parameters = []bigquery.QueryParameter{
{Name: "$table", Value: "my-table"},
{Name: "@corpus", Value: "en-US"},
{Value: 1},
}
The $ parameters are handled by saferbq, while @ and ? parameters are
handled by BigQuery’s standard parameterized query mechanism. This approach
ensures identifiers are safely quoted while preserving the security benefits of
parameterized queries for data values.
Conclusion
SQL injection remains one of the most critical security vulnerabilities. When official SDKs don’t provide safe mechanisms for common use cases, developers will be tempted to use unsafe string concatenation. The BigQuery SDK supports parameterized queries for data values, but not for identifiers. The saferbq package is a drop-in replacement that maintains the same API while adding that extra safety.
See: https://github.com/mevdschee/saferbq
Enjoy!