Fix presubmit and postsubmit path handling vulnerabilities (#2917)

Addresses b/569811973.

- `bcr_presubmit.py`: Uses `git diff -z` to parse quoted paths
correctly, preventing a bypass of symlink validations.
- `bcr_postsubmit.py`: Verifies that the resolved path of the
destination attestation file is contained within the expected output
directory using `pathlib.Path.resolve()`, preventing arbitrary file
writes via malicious symlinks.
diff --git a/buildkite/bazel-central-registry/bcr_postsubmit.py b/buildkite/bazel-central-registry/bcr_postsubmit.py
index 78425b0..489d7ac 100755
--- a/buildkite/bazel-central-registry/bcr_postsubmit.py
+++ b/buildkite/bazel-central-registry/bcr_postsubmit.py
@@ -27,6 +27,7 @@
 import hashlib
 import json
 import os
+import pathlib
 import requests
 import subprocess
 import sys
@@ -123,6 +124,14 @@
     print("\t\tIntegrity: OK")
 
     dest = os.path.join(dest_dir, get_canonical_basename(url))
+
+    dest_path = pathlib.Path(dest).resolve()
+    dest_dir_path = pathlib.Path(dest_dir).resolve()
+    if not dest_path.is_relative_to(dest_dir_path):
+        raise AttestationError(
+            f"Resolved path {dest_path} is outside of expected directory {dest_dir_path}"
+        )
+
     print(f"\t\tWriting attestation to {dest}...")
     with open(dest, "wb") as f:
         f.write(raw_content)
diff --git a/buildkite/bazel-central-registry/bcr_presubmit.py b/buildkite/bazel-central-registry/bcr_presubmit.py
index 4c4ed78..cacd22a 100755
--- a/buildkite/bazel-central-registry/bcr_presubmit.py
+++ b/buildkite/bazel-central-registry/bcr_presubmit.py
@@ -96,11 +96,15 @@
     """
     # Get the list of changed files compared to the main branch
     output = subprocess.check_output(
-        ["git", "diff", "main...HEAD", "--name-only", "--pretty=format:"]
+        ["git", "diff", "main...HEAD", "--name-only", "-z", "--pretty=format:"]
     )
     modules = set()
     # Matching modules/<name>/<version>/
-    for line in output.decode("utf-8").split():
+    # `git diff -z --name-only` delimits affected files with \0 and outputs paths verbatim
+    # (without quoting unusual characters), so split on \0 instead of whitespace.
+    for line in output.decode("utf-8").split("\0"):
+        if not line:
+            continue
         s = re.match(r"modules\/([^\/]+)\/([^\/]+)\/", line)
         if s:
             module_name, module_version = s.groups()
@@ -127,11 +131,13 @@
     """
     # Get the list of changed files compared to the main branch
     output = subprocess.check_output(
-        ["git", "diff", "main...HEAD", "--name-only", "--pretty=format:"]
+        ["git", "diff", "main...HEAD", "--name-only", "-z", "--pretty=format:"]
     )
     modules = set()
     # Matching modules/<name>/metadata.json
-    for line in output.decode("utf-8").split():
+    for line in output.decode("utf-8").split("\0"):
+        if not line:
+            continue
         s = re.match(r"modules\/([^\/]+)\/metadata\.json", line)
         if s:
             module_name = s.groups()[0]
@@ -365,7 +371,7 @@
     # Get all files that are Modified, Renamed, or Deleted.
     bazelci.print_expanded_group("Checking if existing modules are not modified")
     output = subprocess.check_output(
-        ["git", "diff", "main...HEAD", "--diff-filter=MRD", "--name-only", "--pretty=format:"]
+        ["git", "diff", "main...HEAD", "--diff-filter=MRD", "--name-only", "-z", "--pretty=format:"]
     )
 
     # Check if any of the source.json, MODULE.bazel, or patch files are changed for an existing module.
@@ -376,7 +382,9 @@
         re.compile(r"modules\/([^\/]+)\/([^\/]+)\/overlay"),
     ]
     changed_modules = []
-    for line in output.decode("utf-8").split():
+    for line in output.decode("utf-8").split("\0"):
+        if not line:
+            continue
         for p in NO_CHANGE_FILE_PATTERNS:
             s = p.match(line)
             if s:
@@ -394,10 +402,14 @@
         return
     bazelci.print_expanded_group("Checking if any file changes outside of modules/")
     output = subprocess.check_output(
-        ["git", "diff", "main...HEAD", "--name-only", "--pretty=format:", ":!modules"]
-    ).decode("utf-8").strip()
-    if output:
-        error("The following files should not be changed when adding a new module version:\n" + output)
+        ["git", "diff", "main...HEAD", "--name-only", "-z", "--pretty=format:", ":!modules"]
+    ).decode("utf-8")
+    changed_files = [f for f in output.split("\0") if f]
+    if changed_files:
+        error(
+            "The following files should not be changed when adding a new module version:\n"
+            + "\n".join(changed_files)
+        )
     else:
         bazelci.eprint("Nothing changed outside of modules/")