This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-3-test by this push:
     new 762fd74c135 [v3-3-test] Mask the SQLite database path in airflow info 
--anonymize (#73179, #73599) (#73603)
762fd74c135 is described below

commit 762fd74c1351865590c5daba66e9115057b3c2a9
Author: Henry Chen <[email protected]>
AuthorDate: Wed Sep 23 18:47:52 2026 +0800

    [v3-3-test] Mask the SQLite database path in airflow info --anonymize 
(#73179, #73599) (#73603)
    
    * Mask the SQLite database path in airflow info --anonymize (#73179)
    
    The whole point of --anonymize is that the report is safe to paste into a
    public bug report. SQLite is the default backend, so the people most likely
    to rely on that promise are exactly the ones it was broken for.
    
    Co-authored-by: Eason09053360 
<[email protected]>
    (cherry picked from commit 41480acd26db7ef2f8ff1eab724be9b0f0840374)
    
    * Keep the URL scheme out of path masking in airflow info --anonymize 
(#73599)
    
    Since #73179 a netloc-less connection string is masked through the
    unanchored username substitution, so an OS username that happens to be
    a substring of the scheme (e.g. "lite" or "db") corrupts the scheme
    itself: "sqlite:" becomes "sq${USER}:". Splitting on the first colon
    puts the scheme out of the substitution's reach; non-URL fallback
    values such as "NOT AVAILABLE" stay intact for the same reason.
    
    Suggested as an optional follow-up in the review of #73179.
    
    (cherry picked from commit 8b4e4df805665ec69aa99f21a85f94c4a9f131cf)
    
    ---------
    
    Co-authored-by: Y-C <[email protected]>
    Co-authored-by: Eason09053360 
<[email protected]>
    Co-authored-by: wiasliaw <[email protected]>
---
 .../src/airflow/cli/commands/info_command.py       |  7 +++-
 .../tests/unit/cli/commands/test_info_command.py   | 44 ++++++++++++++++++++++
 2 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/airflow-core/src/airflow/cli/commands/info_command.py 
b/airflow-core/src/airflow/cli/commands/info_command.py
index 79c4ae8b7f3..b5da90a7335 100644
--- a/airflow-core/src/airflow/cli/commands/info_command.py
+++ b/airflow-core/src/airflow/cli/commands/info_command.py
@@ -92,7 +92,6 @@ class PiiAnonymizer(Anonymizer):
             return value
 
         url_parts = urlsplit(value)
-        netloc = None
         if url_parts.netloc:
             # unpack
             userinfo = None
@@ -124,6 +123,12 @@ class PiiAnonymizer(Anonymizer):
                 netloc = host
             else:
                 netloc = ""
+        else:
+            # A netloc-less URL is a local-file backend (SQLite is the 
default), where
+            # the path itself is the identifying information. Split off the 
scheme so the
+            # unanchored username substitution cannot touch it ("sqlite:" -> 
"sq${USER}:").
+            scheme, sep, rest = value.partition(":")
+            return f"{scheme}{sep}{self.process_path(rest)}"
 
         return urlunsplit((url_parts.scheme, netloc, url_parts.path, 
url_parts.query, url_parts.fragment))
 
diff --git a/airflow-core/tests/unit/cli/commands/test_info_command.py 
b/airflow-core/tests/unit/cli/commands/test_info_command.py
index 2dc3d60e4e6..98113912668 100644
--- a/airflow-core/tests/unit/cli/commands/test_info_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_info_command.py
@@ -60,11 +60,55 @@ class TestPiiAnonymizer:
                 "postgresql+psycopg2://postgres/airflow",
                 "postgresql+psycopg2://postgres/airflow",
             ),
+            (
+                f"sqlite:///{os.path.expanduser('~/airflow/airflow.db')}",
+                "sqlite:///${HOME}/airflow/airflow.db",
+            ),
+            (
+                "sqlite:///relative.db",
+                "sqlite:///relative.db",
+            ),
         ],
     )
     def test_should_remove_pii_from_url(self, before, after):
         assert after == self.instance.process_url(before)
 
+    @pytest.mark.parametrize(
+        ("username", "home", "before", "after"),
+        [
+            (
+                "lite",
+                "/home/lite",
+                "sqlite:////home/lite/airflow.db",
+                "sqlite:///${HOME}/airflow.db",
+            ),
+            (
+                "db",
+                "/home/db",
+                "duckdb:////home/db/wh.duckdb",
+                "duckdb:///${HOME}/wh.duck${USER}",
+            ),
+            (
+                "AVA",
+                "/home/AVA",
+                "NOT AVAILABLE",
+                "NOT AVAILABLE",
+            ),
+        ],
+    )
+    @mock.patch("airflow.cli.commands.info_command.os.path.expanduser", 
autospec=True)
+    @mock.patch("airflow.cli.commands.info_command.getuser", autospec=True)
+    def test_should_leave_scheme_out_of_path_masking(
+        self, mock_getuser, mock_expanduser, username, home, before, after
+    ):
+        # The scheme of a netloc-less URL must stay out of reach of the 
unanchored
+        # username substitution: a user named "lite" must not turn "sqlite:" 
into
+        # "sq${USER}:". Anything before the first colon is exempt from masking,
+        # which also keeps non-URL fallback values like "NOT AVAILABLE" intact.
+        mock_getuser.return_value = username
+        mock_expanduser.return_value = home
+        assert after == info_command.PiiAnonymizer().process_url(before)
+
 
 class TestAirflowInfo:
     @classmethod

Reply via email to