From 51710b9ca193ab240a4dd5842c0de21c79932e6e Mon Sep 17 00:00:00 2001 From: Yuan Date: Thu, 23 Jul 2026 09:26:44 +0100 Subject: [PATCH 1/2] [CORE] Fix password leak in debug-mode config logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit printConfig() already had redaction logic keyed on spark.redaction.regex, but when that config key was absent (the common case) getRedactionRegex() returned std::nullopt and every config value — including passwords, tokens, and secrets — was logged in plain text. This patch adds a hard-coded default redaction pattern to guard on this case Signed-off-by: Yuan --- cpp/core/config/GlutenConfig.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cpp/core/config/GlutenConfig.cc b/cpp/core/config/GlutenConfig.cc index eb98f6bb903..6ad7c24f6d8 100644 --- a/cpp/core/config/GlutenConfig.cc +++ b/cpp/core/config/GlutenConfig.cc @@ -19,19 +19,22 @@ #include #include -#include #include "compute/ProtobufUtils.h" #include "config.pb.h" #include "jni/JniError.h" namespace { -std::optional getRedactionRegex(const std::unordered_map& conf) { +// Mirrors Spark's built-in default for spark.redaction.regex. +// See org.apache.spark.internal.config.SECRET_REDACTION_PATTERN. +constexpr std::string_view kDefaultRedactionRegex = "(?i)secret|password|token|access[.]?key"; + +boost::regex getRedactionRegex(const std::unordered_map& conf) { auto it = conf.find(gluten::kSparkRedactionRegex); if (it != conf.end()) { return boost::regex(it->second); } - return std::nullopt; + return boost::regex(kDefaultRedactionRegex.data()); } } // namespace @@ -67,7 +70,7 @@ std::string printConfig(const std::unordered_map& conf auto redactionRegex = getRedactionRegex(conf); for (const auto& [k, v] : conf) { - if (redactionRegex && boost::regex_match(k, *redactionRegex)) { + if (boost::regex_search(k, redactionRegex)) { oss << " [" << k << ", " << kSparkRedactionString << "]\n"; } else { oss << " [" << k << ", " << v << "]\n"; From 3c6c1c52d96602c3ad40291701ee4a0601fc8211 Mon Sep 17 00:00:00 2001 From: Yuan Date: Thu, 23 Jul 2026 09:31:29 +0100 Subject: [PATCH 2/2] add test Signed-off-by: Yuan --- cpp/core/tests/CMakeLists.txt | 1 + cpp/core/tests/PrintConfigTest.cc | 108 ++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 cpp/core/tests/PrintConfigTest.cc diff --git a/cpp/core/tests/CMakeLists.txt b/cpp/core/tests/CMakeLists.txt index 33026948ce5..c022f37aeaa 100644 --- a/cpp/core/tests/CMakeLists.txt +++ b/cpp/core/tests/CMakeLists.txt @@ -17,3 +17,4 @@ add_test_case(round_robin_partitioner_test SOURCES RoundRobinPartitionerTest.cc) add_test_case(object_store_test SOURCES ObjectStoreTest.cc) add_test_case(memory_allocator_test SOURCES MemoryAllocatorTest.cc) add_test_case(ffor_codec_test SOURCES FForCodecTest.cc) +add_test_case(print_config_test SOURCES PrintConfigTest.cc) diff --git a/cpp/core/tests/PrintConfigTest.cc b/cpp/core/tests/PrintConfigTest.cc new file mode 100644 index 00000000000..c1e42aa37bb --- /dev/null +++ b/cpp/core/tests/PrintConfigTest.cc @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "config/GlutenConfig.h" + +namespace gluten { + +// Helpers to check whether a key's value is redacted or plain in the output. +static bool isRedacted(const std::string& output, const std::string& key) { + // Look for the pattern " [, *********(redacted)]" + return output.find("[" + key + ", " + kSparkRedactionString + "]") != std::string::npos; +} + +static bool isPlain(const std::string& output, const std::string& key, const std::string& value) { + return output.find("[" + key + ", " + value + "]") != std::string::npos; +} + +// ── Default-redaction tests (no spark.redaction.regex in config) ───────────── + +TEST(PrintConfig, DefaultRedactsPassword) { + std::unordered_map conf = { + {"spark.datasource.jdbc.password", "secret123"}, + }; + auto out = printConfig(conf); + EXPECT_TRUE(isRedacted(out, "spark.datasource.jdbc.password")); +} + +TEST(PrintConfig, DefaultRedactsSecret) { + std::unordered_map conf = { + {"my.secret.value", "topsecret"}, + }; + auto out = printConfig(conf); + EXPECT_TRUE(isRedacted(out, "my.secret.value")); +} + +TEST(PrintConfig, DefaultRedactsToken) { + std::unordered_map conf = { + {"spark.hadoop.fs.s3a.access.token", "tok_abc123"}, + }; + auto out = printConfig(conf); + EXPECT_TRUE(isRedacted(out, "spark.hadoop.fs.s3a.access.token")); +} + +TEST(PrintConfig, DefaultRedactsAccessKey) { + std::unordered_map conf = { + {"spark.hadoop.fs.s3a.accesskey", "AKIAIOSFODNN7EXAMPLE"}, + }; + auto out = printConfig(conf); + EXPECT_TRUE(isRedacted(out, "spark.hadoop.fs.s3a.accesskey")); +} + +TEST(PrintConfig, DefaultDoesNotRedactSafeKey) { + std::unordered_map conf = { + {"spark.sql.session.timeZone", "UTC"}, + }; + auto out = printConfig(conf); + EXPECT_TRUE(isPlain(out, "spark.sql.session.timeZone", "UTC")); +} + +// ── Custom-regex tests (spark.redaction.regex present) ─────────────────────── + +TEST(PrintConfig, CustomRegexRedactsMatchingKey) { + std::unordered_map conf = { + {kSparkRedactionRegex, "supersensitive"}, + {"my.supersensitive.config", "very_private"}, + {"spark.sql.session.timeZone", "UTC"}, + }; + auto out = printConfig(conf); + EXPECT_TRUE(isRedacted(out, "my.supersensitive.config")); + EXPECT_TRUE(isPlain(out, "spark.sql.session.timeZone", "UTC")); +} + +TEST(PrintConfig, CustomRegexOverridesDefault) { + // When spark.redaction.regex is set, only keys matching it are redacted. + // A key that would match the default pattern (e.g. "password") but NOT the + // custom regex must be printed in plain text. + std::unordered_map conf = { + {kSparkRedactionRegex, "supersensitive"}, + {"spark.datasource.jdbc.password", "pass123"}, + }; + auto out = printConfig(conf); + EXPECT_TRUE(isPlain(out, "spark.datasource.jdbc.password", "pass123")); +} + +TEST(PrintConfig, CaseInsensitiveDefaultRedaction) { + std::unordered_map conf = { + {"spark.my.PASSWORD", "uppercase_pw"}, + }; + auto out = printConfig(conf); + EXPECT_TRUE(isRedacted(out, "spark.my.PASSWORD")); +} + +} // namespace gluten