diff --git a/src/workerd/api/sql.c++ b/src/workerd/api/sql.c++ index 9c498c2ae85..29d08406628 100644 --- a/src/workerd/api/sql.c++ +++ b/src/workerd/api/sql.c++ @@ -7,6 +7,9 @@ #include "actor-state.h" #include +#include + +#include namespace workerd::api { @@ -135,7 +138,11 @@ double SqlStorage::getDatabaseSize(jsg::Lock& js) { } bool SqlStorage::isAllowedName(kj::StringPtr name) const { - return !name.startsWith("_cf_"); + if (name.startsWith("_cf_")) return false; + if (name.size() >= 4 && strncasecmp(name.begin(), "_cf_", 4) == 0) { + LOG_WARNING_PERIODICALLY("SQL identifier matches reserved _cf_ prefix case-insensitively"); + } + return true; } bool SqlStorage::isAllowedTrigger(kj::StringPtr name) const { diff --git a/src/workerd/api/tests/sql-test-tail.js b/src/workerd/api/tests/sql-test-tail.js index 3d1a3edf021..effe89c0c7d 100644 --- a/src/workerd/api/tests/sql-test-tail.js +++ b/src/workerd/api/tests/sql-test-tail.js @@ -22,7 +22,7 @@ export const test = { return acc; }, {}); assert.deepStrictEqual(reduced, { - durable_object_storage_exec: 268, + durable_object_storage_exec: 272, durable_object_storage_ingest: 1030, durable_object_storage_getDatabaseSize: 3, durable_object_storage_put: 18, diff --git a/src/workerd/api/tests/sql-test.js b/src/workerd/api/tests/sql-test.js index 398894d840b..202ca0eb536 100644 --- a/src/workerd/api/tests/sql-test.js +++ b/src/workerd/api/tests/sql-test.js @@ -350,6 +350,15 @@ async function test(state) { /access to _cf_KV.key is prohibited/ ); + // Exercise logging for future _cf_ prefix restrictions (VULN-129365). + // Mixed-case _cf_ prefix triggers a warning log but is currently allowed. + sql.exec('CREATE TABLE _CF_log_test (name TEXT)'); + sql.exec('DROP TABLE _CF_log_test'); + // FTS5 virtual table with mixed-case _CF_ prefix triggers a warning log but is currently + // allowed. (Lowercase _cf_ already fails indirectly because FTS5 shadow tables are blocked.) + sql.exec('CREATE VIRTUAL TABLE _CF_fts_log_test USING fts5(content)'); + sql.exec('DROP TABLE _CF_fts_log_test'); + // Some pragmas are completely not allowed assert.throws( () => sql.exec('PRAGMA hard_heap_limit = 1024'), diff --git a/src/workerd/util/sqlite.c++ b/src/workerd/util/sqlite.c++ index d76e2332e6b..814e2ef5610 100644 --- a/src/workerd/util/sqlite.c++ +++ b/src/workerd/util/sqlite.c++ @@ -1269,6 +1269,10 @@ bool SqliteDatabase::isAuthorized(int actionCode, KJ_IF_SOME(moduleName, param2) { if (strcasecmp(moduleName.begin(), "fts5") == 0 || strcasecmp(moduleName.begin(), "fts5vocab") == 0) { + auto& tableName = KJ_ASSERT_NONNULL(param1); + if (tableName.size() >= 4 && strncasecmp(tableName.begin(), "_cf_", 4) == 0) { + LOG_WARNING_PERIODICALLY("FTS5 virtual table uses reserved _cf_ prefix"); + } return true; } }