CVE-2026-106303
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forcomponents/autofill/core/browser/filling/field_filling_util.cc |
modified | |
ifcomponents/autofill/core/browser/filling/field_filling_util.cc |
modified | |
FieldFillingUtilTestcomponents/autofill/core/browser/filling/field_filling_util_unittest.cc |
modified | |
TESTcomponents/autofill/core/browser/filling/field_filling_util_unittest.cc |
modified |
Files Changed
components/autofill/core/browser/filling/field_filling_util_unittest.cccomponents/autofill/core/browser/filling/field_filling_util.hcomponents/autofill/core/browser/filling/field_filling_util.cc
Patch
From 32567146bedbe92dee6530a6a691291ae8356691 Mon Sep 17 00:00:00 2001 From: Dominik Klemba <[email protected]> Date: Wed, 16 Sep 2026 05:03:16 -0700 Subject: [PATCH] Reland "[Autofill] Enforce constant-length obfuscation in GetObfuscatedValue" This is a reland of commit a1ba115228b6a2cd448314881b724c5c9e9be231 The original change caused FormInputAccessoryEGTest to fail on iOS CI because the test expectation hardcoded 5 dots instead of 4 for a 9-character passport. The test expectations were updated on main in commit 02198458d844b531c1c664956cdddb2b6e4079d6 (crrev.com/c/8407165) to call GetObfuscatedValue() directly, allowing this CL to reland as-is. Original change's description: > [Autofill] Enforce constant-length obfuscation in GetObfuscatedValue > > Previously, GetObfuscatedValue() could emit one bullet per character on > the legacy code path. This allowed a compromised renderer to infer the > exact character count of sensitive credentials (such as passports or > driver's licenses) during hover preview. > > This CL removes the legacy length-preserving branch, eliminating the > length leak. > > TAG=agy > CONV=5b9ab689-a020-4421-bba9-59caa6073b1b > > Bug: 517475258 > Change-Id: I8693e99a8e5ab43991bc0741890f37e2cce1b79d > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8389096 > Commit-Queue: Dominik Klemba <[email protected]> > Reviewed-by: Josef Raska <[email protected]> > Auto-Submit: Dominik Klemba <[email protected]> > Reviewed-by: Jihad Hanna <[email protected]> > Cr-Commit-Position: refs/heads/main@{#1696111} TAG=agy CONV=5b9ab689-a020-4421-bba9-59caa6073b1b Cq-Include-Trybots: luci.chromium.try:ios-simulator-full-configs Bug: 517475258 Change-Id: I718b3e67e6b7033b5cdf99f904f834e0ed3c6638 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8400367 Auto-Submit: Dominik Klemba <[email protected]> Reviewed-by: Josef Raska <[email protected]> Commit-Queue: Dominik Klemba <[email protected]> Commit-Queue: Jihad Hanna <[email protected]> Reviewed-by: Jihad Hanna <[email protected]> Cr-Commit-Position: refs/heads/main@{#1698844} --- diff --git a/components/autofill/core/browser/filling/field_filling_util.cc b/components/autofill/core/browser/filling/field_filling_util.cc index 6795b970..4a1a877 100644 --- a/components/autofill/core/browser/filling/field_filling_util.cc +++ b/components/autofill/core/browser/filling/field_filling_util.cc @@ -11,11 +11,11 @@ #include <memory> #include <optional> #include <string> +#include <string_view> #include <utility> #include <vector> #include "base/containers/span.h" -#include "base/feature_list.h" #include "base/i18n/case_conversion.h" #include "base/i18n/rtl.h" #include "base/no_destructor.h" @@ -31,7 +31,6 @@ #include "components/autofill/core/browser/geo/country_names.h" #include "components/autofill/core/browser/geo/state_names.h" #include "components/autofill/core/browser/proto/states.pb.h" -#include "components/autofill/core/common/autofill_features.h" #include "components/autofill/core/common/autofill_l10n_util.h" #include "components/autofill/core/common/autofill_util.h" #include "components/autofill/core/common/form_field_data.h" @@ -258,55 +257,28 @@ // - \u2022 - Bullet. // - \u2006 - SIX-PER-EM SPACE (small space between bullets). // - \u2060 - WORD-JOINER (makes obfuscated string indivisible). - static constexpr char16_t kDot[] = u"\u2022\u2060\u2006\u2060"; + static constexpr std::u16string_view kDot = u"\u2022\u2060\u2006\u2060"; + static constexpr size_t kTargetLength = 8; - if (!base::FeatureList::IsEnabled(features::kAutofillAiWalletPrivatePasses) && - !base::FeatureList::IsEnabled(features::kAutofillAmbientAutofill)) { - // This is only an approximation of the number of the actual unicode - // characters - if we want to match the length exactly, we would need to use - // `base::CountUnicodeCharacters`. - visible_suffix_length = std::min(visible_suffix_length, value.size()); - size_t obfuscation_length = value.size() - visible_suffix_length; - - std::u16string result; - result.reserve(sizeof(kDot) * obfuscation_length + visible_suffix_length); - - for (size_t i = 0; i < obfuscation_length; ++i) { - result.append(kDot); - } - if (visible_suffix_length > 0) { - result.append(value.substr(value.size() - visible_suffix_length)); - } - - // `WrapStringWithLTRFormatting` guarantees that the following passport - // number 123456789 won't be rendered as 789** in RTL languaguages. It - // forces the browser to use LTR convention. - if (base::i18n::IsRTL()) { - base::i18n::WrapStringWithLTRFormatting(&result); - } - return result; - } - - bool obfuscate_all = visible_suffix_length == 0; - - size_t target_length = 4 + std::min<size_t>(value.size(), 4); - size_t actual_visible_suffix = - obfuscate_all ? 0 : std::min<size_t>(value.size(), 4); - size_t num_dots = target_length - actual_visible_suffix; + // The total length is always fixed to `kTargetLength` (8) to prevent leaking + // the true length of sensitive values. + const size_t actual_visible_suffix = + std::min({visible_suffix_length, value.size(), size_t{4}}); + const size_t num_dots = kTargetLength - actual_visible_suffix; std::u16string result; - result.reserve(sizeof(kDot) * num_dots + actual_visible_suffix); + result.reserve(kDot.size() * num_dots + actual_visible_suffix); for (size_t i = 0; i < num_dots; ++i) { result.append(kDot); } - if (visible_suffix_length > 0) { - result.append(value.substr(value.size() - actual_visible_suffix)); + if (actual_visible_suffix > 0) { + result.append(value, value.size() - actual_visible_suffix); } // `WrapStringWithLTRFormatting` guarantees that the following passport - // number 123456789 won't be rendered as 789** in RTL languaguages. It + // number 123456789 won't be rendered as 789** in RTL languages. It // forces the browser to use LTR convention. if (base::i18n::IsRTL()) { base::i18n::WrapStringWithLTRFormatting(&result); diff --git a/components/autofill/core/browser/filling/field_filling_util.h b/components/autofill/core/browser/filling/field_filling_util.h index 98c51c0..aa3ae73 100644 --- a/components/autofill/core/browser/filling/field_filling_util.h +++ b/components/autofill/core/browser/filling/field_filling_util.h @@ -67,12 +67,9 @@ base::span<const SelectOption> field_options, std::string* failure_to_fill); -// Returns an obfuscated version of `value`. -// `visible_suffix_length` defines how many of the last n characters should -// not be obfuscated. -// TODO(crbug.com/394011769): Remove visible_suffix_length once -// kAutofillAiWalletPrivatePasses is rolled out and replace it with an -// 'obfuscate_all' bool. +// Returns an obfuscated version of `value` with a fixed total length of 8 +// slots. `visible_suffix_length` defines how many trailing characters (up to 4) +// remain unmasked. std::u16string GetObfuscatedValue(const std::u16string& value, size_t visible_suffix_length = 0); diff --git a/components/autofill/core/browser/filling/field_filling_util_unittest.cc b/components/autofill/core/browser/filling/field_filling_util_unittest.cc index 7f45747..a714800 100644 --- a/components/autofill/core/browser/filling/field_filling_util_unittest.cc +++ b/components/autofill/core/browser/filling/field_filling_util_unittest.cc @@ -6,20 +6,19 @@ #include <optional> #include <string> +#include <string_view> #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" -#include "base/test/scoped_feature_list.h" #include "components/autofill/core/browser/autofill_field.h" #include "components/autofill/core/browser/test_utils/autofill_test_util.h" -#include "components/autofill/core/common/autofill_features.h" #include "testing/gtest/include/gtest/gtest.h" namespace autofill { namespace { -constexpr std::u16string kDots = u"\u2022\u2060\u2006\u2060"; +constexpr std::u16string_view kDots = u"\u2022\u2060\u2006\u2060"; class FieldFillingUtilTest : public testing::Test { public: @@ -88,74 +87,53 @@ EXPECT_EQ(u"2", match_option->value); } -// TODO(crbug.com/394011769): Remove once `kAutofillAiWalletPrivatePasses` is -// launched. -TEST(GetObfuscatedValue, ObfuscateValueLegacy) { - base::test::ScopedFeatureList feature; - feature.InitAndDisableFeature(features::kAutofillAiWalletPrivatePasses); - std::u16string expected = base::StrCat({kDots, kDots}); - EXPECT_EQ(GetObfuscatedValue(u"12"), expected); -} - -// TODO(crbug.com/394011769): Remove once `kAutofillAiWalletPrivatePasses` is -// launched. -TEST(GetObfuscatedValue, ObfuscateValueWithPartialLegacy) { - base::test::ScopedFeatureList feature; - feature.InitAndDisableFeature(features::kAutofillAiWalletPrivatePasses); - // Test partial obfuscation (keep last 2 characters). - EXPECT_EQ(GetObfuscatedValue(u"12345", 2), - base::StrCat({kDots, kDots, kDots, u"45"})); - - // Test obfuscation of 0 characters (should return fully obfuscated). - EXPECT_EQ(GetObfuscatedValue(u"12345", 0), - base::StrCat({kDots, kDots, kDots, kDots, kDots})); - - // Test visible suffix are more characters than length (should not obfuscate - // all). - EXPECT_EQ(GetObfuscatedValue(u"12", 5), u"12"); - - // Test keeping all characters. - EXPECT_EQ(GetObfuscatedValue(u"12345", 5), u"12345"); -} - TEST(GetObfuscatedValue, ObfuscateValue) { - base::test::ScopedFeatureList feature_list; - feature_list.InitWithFeatures({features::kAutofillAiWalletPrivatePasses, - features::kAutofillAmbientAutofill}, - {}); - // 4 dots + up to 4 chars (visible_suffix_length = 4) EXPECT_EQ(GetObfuscatedValue(u"123456789", 4), base::StrCat({kDots, kDots, kDots, kDots, u"6789"})); - // Shorter than 4 chars. - EXPECT_EQ(GetObfuscatedValue(u"12", 4), - base::StrCat({kDots, kDots, kDots, kDots, u"12"})); + // Boundary case: exactly 4 characters. + EXPECT_EQ(GetObfuscatedValue(u"1234", 4), + base::StrCat({kDots, kDots, kDots, kDots, u"1234"})); - // Empty string. - EXPECT_EQ(GetObfuscatedValue(u"", 4), - base::StrCat({kDots, kDots, kDots, kDots})); + // Shorter than 4 chars pads with extra dots so total length is always 8. + EXPECT_EQ(GetObfuscatedValue(u"12", 4), + base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, u"12"})); + + // Empty string returns 8 dots. + EXPECT_EQ( + GetObfuscatedValue(u"", 4), + base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots})); + + // Partial visible suffix (e.g. 2 chars) pads with 6 dots so total is 8. + EXPECT_EQ(GetObfuscatedValue(u"12345", 2), + base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, u"45"})); + + // Requests for more than 4 visible characters clamp to 4. + EXPECT_EQ(GetObfuscatedValue(u"123456789", 6), + base::StrCat({kDots, kDots, kDots, kDots, u"6789"})); } TEST(GetObfuscatedValue, ObfuscateAll) { - base::test::ScopedFeatureList feature_list; - feature_list.InitWithFeatures({features::kAutofillAiWalletPrivatePasses, - features::kAutofillAmbientAutofill}, - {}); + // Default argument (visible_suffix_length = 0) returns 8 dots. + EXPECT_EQ( + GetObfuscatedValue(u"123456789"), + base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots})); - // Matches length of UI string (4 dots + 4 visible = 8 dots). - // visible_suffix_length = 0 means obfuscate all. + // Explicit visible_suffix_length = 0 returns 8 dots to avoid leaking length. EXPECT_EQ( GetObfuscatedValue(u"123456789", 0), base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots})); - // Short strings (4 dots + 2 visible = 6 dots). - EXPECT_EQ(GetObfuscatedValue(u"12", 0), - base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots})); + // Short strings also return 8 dots. + EXPECT_EQ( + GetObfuscatedValue(u"12", 0), + base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots})); - // Empty string. - EXPECT_EQ(GetObfuscatedValue(u"", 0), - base::StrCat({kDots, kDots, kDots, kDots})); + // Empty string also returns 8 dots. + EXPECT_EQ( + GetObfuscatedValue(u"", 0), + base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots})); } } // namespace
Regression Test / PoC
diff --git a/components/autofill/core/browser/filling/field_filling_util_unittest.cc b/components/autofill/core/browser/filling/field_filling_util_unittest.cc
index 7f45747..a714800 100644
--- a/components/autofill/core/browser/filling/field_filling_util_unittest.cc
+++ b/components/autofill/core/browser/filling/field_filling_util_unittest.cc
@@ -6,20 +6,19 @@
#include <optional>
#include <string>
+#include <string_view>
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
-#include "base/test/scoped_feature_list.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/test_utils/autofill_test_util.h"
-#include "components/autofill/core/common/autofill_features.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace autofill {
namespace {
-constexpr std::u16string kDots = u"\u2022\u2060\u2006\u2060";
+constexpr std::u16string_view kDots = u"\u2022\u2060\u2006\u2060";
class FieldFillingUtilTest : public testing::Test {
public:
@@ -88,74 +87,53 @@
EXPECT_EQ(u"2", match_option->value);
}
-// TODO(crbug.com/394011769): Remove once `kAutofillAiWalletPrivatePasses` is
-// launched.
-TEST(GetObfuscatedValue, ObfuscateValueLegacy) {
- base::test::ScopedFeatureList feature;
- feature.InitAndDisableFeature(features::kAutofillAiWalletPrivatePasses);
- std::u16string expected = base::StrCat({kDots, kDots});
- EXPECT_EQ(GetObfuscatedValue(u"12"), expected);
-}
-
-// TODO(crbug.com/394011769): Remove once `kAutofillAiWalletPrivatePasses` is
-// launched.
-TEST(GetObfuscatedValue, ObfuscateValueWithPartialLegacy) {
- base::test::ScopedFeatureList feature;
- feature.InitAndDisableFeature(features::kAutofillAiWalletPrivatePasses);
- // Test partial obfuscation (keep last 2 characters).
- EXPECT_EQ(GetObfuscatedValue(u"12345", 2),
- base::StrCat({kDots, kDots, kDots, u"45"}));
-
- // Test obfuscation of 0 characters (should return fully obfuscated).
- EXPECT_EQ(GetObfuscatedValue(u"12345", 0),
- base::StrCat({kDots, kDots, kDots, kDots, kDots}));
-
- // Test visible suffix are more characters than length (should not obfuscate
- // all).
- EXPECT_EQ(GetObfuscatedValue(u"12", 5), u"12");
-
- // Test keeping all characters.
- EXPECT_EQ(GetObfuscatedValue(u"12345", 5), u"12345");
-}
-
TEST(GetObfuscatedValue, ObfuscateValue) {
- base::test::ScopedFeatureList feature_list;
- feature_list.InitWithFeatures({features::kAutofillAiWalletPrivatePasses,
- features::kAutofillAmbientAutofill},
- {});
-
// 4 dots + up to 4 chars (visible_suffix_length = 4)
EXPECT_EQ(GetObfuscatedValue(u"123456789", 4),
base::StrCat({kDots, kDots, kDots, kDots, u"6789"}));
- // Shorter than 4 chars.
- EXPECT_EQ(GetObfuscatedValue(u"12", 4),
- base::StrCat({kDots, kDots, kDots, kDots, u"12"}));
+ // Boundary case: exactly 4 characters.
+ EXPECT_EQ(GetObfuscatedValue(u"1234", 4),
+ base::StrCat({kDots, kDots, kDots, kDots, u"1234"}));
- // Empty string.
- EXPECT_EQ(GetObfuscatedValue(u"", 4),
- base::StrCat({kDots, kDots, kDots, kDots}));
+ // Shorter than 4 chars pads with extra dots so total length is always 8.
+ EXPECT_EQ(GetObfuscatedValue(u"12", 4),
+ base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, u"12"}));
+
+ // Empty string returns 8 dots.
+ EXPECT_EQ(
+ GetObfuscatedValue(u"", 4),
+ base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots}));
+
+ // Partial visible suffix (e.g. 2 chars) pads with 6 dots so total is 8.
+ EXPECT_EQ(GetObfuscatedValue(u"12345", 2),
+ base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, u"45"}));
+
+ // Requests for more than 4 visible characters clamp to 4.
+ EXPECT_EQ(GetObfuscatedValue(u"123456789", 6),
+ base::StrCat({kDots, kDots, kDots, kDots, u"6789"}));
}
TEST(GetObfuscatedValue, ObfuscateAll) {
- base::test::ScopedFeatureList feature_list;
- feature_list.InitWithFeatures({features::kAutofillAiWalletPrivatePasses,
- features::kAutofillAmbientAutofill},
- {});
+ // Default argument (visible_suffix_length = 0) returns 8 dots.
+ EXPECT_EQ(
+ GetObfuscatedValue(u"123456789"),
+ base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots}));
- // Matches length of UI string (4 dots + 4 visible = 8 dots).
- // visible_suffix_length = 0 means obfuscate all.
+ // Explicit visible_suffix_length = 0 returns 8 dots to avoid leaking length.
EXPECT_EQ(
GetObfuscatedValue(u"123456789", 0),
base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots}));
- // Short strings (4 dots + 2 visible = 6 dots).
- EXPECT_EQ(GetObfuscatedValue(u"12", 0),
- base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots}));
+ // Short strings also return 8 dots.
+ EXPECT_EQ(
+ GetObfuscatedValue(u"12", 0),
+ base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots}));
- // Empty string.
- EXPECT_EQ(GetObfuscatedValue(u"", 0),
- base::StrCat({kDots, kDots, kDots, kDots}));
+ // Empty string also returns 8 dots.
+ EXPECT_EQ(
+ GetObfuscatedValue(u"", 0),
+ base::StrCat({kDots, kDots, kDots, kDots, kDots, kDots, kDots, kDots}));
}
} // namespace
Original Bug Report
Autofill AI Preview Length Leak to Compromised Renderer on Hover
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: When a user hovers over an Autofill AI suggestion, the browser process generates a form preview. Under the default configuration, the legacy obfuscation mechanism is length-preserving, appending exactly one separator per character of the sensitive data. A compromised renderer process can intercept this preview value over Mojo IPC to deduce the exact length of sensitive documents like passports and driver’s licenses without user consent.
Affected files:
components/autofill/core/browser/filling/field_filling_util.cccomponents/autofill/core/browser/filling/autofill_ai/field_filling_entity_util.cc
Estimated timestamp from git blame: 2025-02-24
Root Cause
During a preview path (triggered via mojom::ActionPersistence::kPreview when a user hovers over an Autofill AI suggestion), the browser obfuscates sensitive input attributes (such as passport numbers, driver’s licenses, and national IDs) before transferring the form values to the renderer. This obfuscation is performed by GetObfuscatedValue() in components/autofill/core/browser/filling/field_filling_util.cc:
std::u16string GetObfuscatedValue(const std::u16string& value,
size_t visible_suffix_length) {
static constexpr char16_t kDot[] = u"\u2022\u2060\u2006\u2060";
if (!base::FeatureList::IsEnabled(features::kAutofillAiWalletPrivatePasses)) {
visible_suffix_length = std::min(visible_suffix_length, value.size());
size_t obfuscation_length = value.size() - visible_suffix_length;
std::u16string result;
result.reserve(sizeof(kDot) * obfuscation_length + visible_suffix_length);
for (size_t i = 0; i < obfuscation_length; ++i) {
result.append(kDot);
}
...
return result;
}
...
}
When GetFillingValueAndTypeForEntity() (in components/autofill/core/browser/filling/autofill_ai/field_filling_entity_util.cc) handles input fields during a preview, it invokes GetObfuscatedValue(fill_value) with the default visible_suffix_length = 0.
Since the feature flag kAutofillAiWalletPrivatePasses is disabled by default, the legacy block is executed. It appends exactly one 4-character separator dot (kDot) per original character of the secret, producing an obfuscated string whose length is directly proportional to the original secret size (4 * value.size()).
Potential Exploit Scenario & Trigger Path
Note: These are potential steps; our current tooling has not executed or run live exploit code to produce a working proof of concept.
- The user has stored credentials or document numbers via Autofill AI (e.g., Passport, Driver’s License, or National ID).
- An attacker compromises the renderer process through an independent vulnerability (gaining arbitrary code execution within the sandbox).
- The compromised renderer serves or modifies a form with an input field classified under a sensitive entity type (such as
PASSPORT_NUMBER). - When the user focuses the field and hovers over the Autofill AI suggestion popup, the browser process generates a preview action via
FormFiller::FillOrPreviewForm->ContentAutofillDriver::ApplyFormAction(). - The browser computes the obfuscated preview string, which is exactly
4 * Ncharacters long (whereNis the sensitive document number’s length). - The browser transmits this value to the renderer via the Mojo IPC interface
mojom::AutofillAgent::ApplyFieldsAction. - The compromised renderer intercepts the Mojo message or inspects renderer memory to extract the exact length of the user’s sensitive document number, prior to any explicit user consent or confirmation to fill.
Security Impact
This constitutes a browser-to-compromised-renderer information disclosure. Knowing the exact length of government-issued IDs allows an attacker to deduce user metadata (such as issuing state or country, which have predictable length variations) and significantly narrows down the cryptographic or brute-force search space of the credential.
Suggested Fix
To remediate this leak, either enable the constant-length masking feature kAutofillAiWalletPrivatePasses by default, or modify GetObfuscatedValue() to always perform constant-length obfuscation for preview actions regardless of the flag’s status. For example, for fully obfuscated attributes, it should return a fixed number of dots (e.g., 8 dots) to avoid revealing the actual character count of the underlying secret.
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.