Skip to content
Open
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
1 change: 1 addition & 0 deletions include/pgduckdb/pgduckdb_guc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ extern bool duckdb_log_pg_explain;
extern int duckdb_maximum_threads;
extern int duckdb_maximum_memory;
extern char *duckdb_disabled_filesystems;
extern char *duckdb_allowed_directories;
extern bool duckdb_enable_external_access;
extern bool duckdb_allow_community_extensions;
extern bool duckdb_allow_unsigned_extensions;
Expand Down
22 changes: 21 additions & 1 deletion src/pgduckdb_duckdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ DuckDBManager::Initialize() {
config.SetOptionByName("default_null_order", "postgres");

SET_DUCKDB_OPTION(allow_unsigned_extensions);
SET_DUCKDB_OPTION(enable_external_access);
SET_DUCKDB_OPTION(allow_community_extensions);
SET_DUCKDB_OPTION(autoinstall_known_extensions);
SET_DUCKDB_OPTION(autoload_known_extensions);
Expand Down Expand Up @@ -221,6 +220,27 @@ DuckDBManager::Initialize() {
InstallExtensions(context);
}
LoadExtensions(context);

/* Set allowed_directories and enable_external_access AFTER loading extensions
* (extensions need filesystem access to install/load). Set allowed_directories
* BEFORE disabling external access (DuckDB rejects changes to
* allowed_directories when external access is disabled). */
if (!IsEmptyString(duckdb_allowed_directories)) {
auto dirs = duckdb::StringUtil::Split(duckdb_allowed_directories, ',');
auto list_str = "[" +
duckdb::StringUtil::Join(dirs, dirs.size(), ", ",
[](const std::string &dir) {
auto trimmed = dir;
duckdb::StringUtil::Trim(trimmed);
return duckdb::KeywordHelper::WriteQuoted(trimmed);
}) +
"]";
pgduckdb::DuckDBQueryOrThrow(context, "SET allowed_directories=" + list_str);
}

if (!duckdb_enable_external_access) {
pgduckdb::DuckDBQueryOrThrow(context, "SET enable_external_access=false");
}
}

void
Expand Down
7 changes: 7 additions & 0 deletions src/pgduckdb_guc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ bool duckdb_force_motherduck_views = false;
int duckdb_maximum_threads = -1;
int duckdb_maximum_memory = 4096; /* 4GB in MB */
char *duckdb_disabled_filesystems = strdup("");
char *duckdb_allowed_directories = strdup("");
bool duckdb_enable_external_access = true;
bool duckdb_allow_community_extensions = false;
bool duckdb_allow_unsigned_extensions = false;
Expand Down Expand Up @@ -240,6 +241,12 @@ InitGUC() {
"Disable specific file systems preventing access (e.g., LocalFileSystem)",
&duckdb_disabled_filesystems, PGC_SUSET);

DefineCustomDuckDBVariable("duckdb.allowed_directories",
"Comma-separated list of directories and URL prefixes that are always "
"allowed for file access, even when enable_external_access is false "
"(e.g., '/data/,s3://mybucket/')",
&duckdb_allowed_directories, PGC_SUSET);

DefineCustomDuckDBVariable("duckdb.azure_transport_option_type",
"Set the azure_transport_option_type for DuckDB Azure extension. Can be used to "
"workaround issue #882: https://github.com/duckdb/pg_duckdb/issues/882",
Expand Down
38 changes: 38 additions & 0 deletions test/regression/expected/allowed_directories.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- Test duckdb.allowed_directories GUC with comma-separated values
-- Recycle DuckDB first so GucCheckDuckDBNotInitdHook allows SET
CALL duckdb.recycle_ddb();
SET duckdb.allowed_directories = 's3://test-bucket/data/, /tmp/duckdb-test/';
SET duckdb.enable_external_access = false;
-- Should be blocked (not in allowed dirs)
SELECT * FROM duckdb.raw_query($$ SELECT * FROM read_csv('/etc/passwd') $$);
WARNING: (PGDuckDB/CreatePlan) Prepared query returned an error: Catalog Error: Table Function with name raw_query does not exist!
Did you mean "main.pragma_user_agent"?

LINE 1: SELECT raw_query FROM duckdb.raw_query(' SELECT * FROM read_csv(''/etc/passwd...
^
ERROR: (PGDuckDB/pgduckdb_raw_query_cpp) Permission Error: Cannot access file "/etc/passwd" - file system operations are disabled by configuration

LINE 1: SELECT * FROM read_csv('/etc/passwd')
^
SELECT * FROM duckdb.raw_query($$ SELECT * FROM read_csv('https://example.com/test.csv') $$);
WARNING: (PGDuckDB/CreatePlan) Prepared query returned an error: Catalog Error: Table Function with name raw_query does not exist!
Did you mean "main.pragma_user_agent"?

LINE 1: SELECT raw_query FROM duckdb.raw_query(' SELECT * FROM read_csv(''https://example...
^
ERROR: (PGDuckDB/pgduckdb_raw_query_cpp) Permission Error: Cannot access file "https://example.com/test.csv" - file system operations are disabled by configuration

LINE 1: SELECT * FROM read_csv('https://example.com/test.csv')
^
SELECT * FROM duckdb.raw_query($$ SELECT * FROM read_csv('s3://other-bucket/secret.csv') $$);
WARNING: (PGDuckDB/CreatePlan) Prepared query returned an error: Catalog Error: Table Function with name raw_query does not exist!
Did you mean "main.pragma_user_agent"?

LINE 1: SELECT raw_query FROM duckdb.raw_query(' SELECT * FROM read_csv(''s3://other-bucket...
^
ERROR: (PGDuckDB/pgduckdb_raw_query_cpp) Permission Error: Cannot access file "s3://other-bucket/secret.csv" - file system operations are disabled by configuration

LINE 1: SELECT * FROM read_csv('s3://other-bucket/secret.csv')
^
-- Cleanup: recycle to clear restrictions for subsequent tests
CALL duckdb.recycle_ddb();
1 change: 1 addition & 0 deletions test/regression/schedule
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ test: views
test: parallel_postgres_scan
test: postgres_table_etl
test: order_by
test: allowed_directories
13 changes: 13 additions & 0 deletions test/regression/sql/allowed_directories.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- Test duckdb.allowed_directories GUC with comma-separated values
-- Recycle DuckDB first so GucCheckDuckDBNotInitdHook allows SET
CALL duckdb.recycle_ddb();
SET duckdb.allowed_directories = 's3://test-bucket/data/, /tmp/duckdb-test/';
SET duckdb.enable_external_access = false;

-- Should be blocked (not in allowed dirs)
SELECT * FROM duckdb.raw_query($$ SELECT * FROM read_csv('/etc/passwd') $$);
SELECT * FROM duckdb.raw_query($$ SELECT * FROM read_csv('https://example.com/test.csv') $$);
SELECT * FROM duckdb.raw_query($$ SELECT * FROM read_csv('s3://other-bucket/secret.csv') $$);

-- Cleanup: recycle to clear restrictions for subsequent tests
CALL duckdb.recycle_ddb();
Loading