allow global overrides
diff --git a/internal/bzlmod/default_gazelle_overrides.bzl b/internal/bzlmod/default_gazelle_overrides.bzl
index 21e74e0..75b3c74 100644
--- a/internal/bzlmod/default_gazelle_overrides.bzl
+++ b/internal/bzlmod/default_gazelle_overrides.bzl
@@ -14,64 +14,29 @@
 
 visibility("private")
 
+# This file should ONLY specify default overrides for
+# repos explicitly required to build gazelle or rules_go.
+#
+# Otherwise, users should should specify these overrides themselves
+# in their MODULE.bazel file.
+#
+# Gazelle should not be hard coupled to any specific group of
+# Go dependencies.
+
 DEFAULT_BUILD_FILE_GENERATION_BY_PATH = {
-    "github.com/envoyproxy/protoc-gen-validate": "on",
-    "github.com/google/safetext": "on",
-    "github.com/grpc-ecosystem/grpc-gateway/v2": "on",
     "google.golang.org/grpc": "on",
 }
 
 DEFAULT_DIRECTIVES_BY_PATH = {
-    "github.com/census-instrumentation/opencensus-proto": [
-        "gazelle:proto disable",
-    ],
-    "github.com/envoyproxy/protoc-gen-validate": [
-        "gazelle:build_file_name BUILD.bazel",
-    ],
     "github.com/gogo/googleapis": [
         "gazelle:proto disable",
     ],
     "github.com/gogo/protobuf": [
         "gazelle:proto disable",
     ],
-    "github.com/google/gnostic": [
-        "gazelle:proto disable",
-    ],
-    "github.com/google/gnostic-models": [
-        "gazelle:proto disable",
-    ],
-    "github.com/google/safetext": [
-        "gazelle:build_file_name BUILD.bazel",
-        "gazelle:build_file_proto_mode disable_global",
-    ],
-    "github.com/googleapis/gax-go/v2": [
-        "gazelle:proto disable",
-    ],
-    "github.com/googleapis/gnostic": [
-        "gazelle:proto disable",
-    ],
-    "github.com/pseudomuto/protoc-gen-doc": [
-        # The build file in github.com/mwitkow/go-proto-validators has both go_proto and gogo_proto targets, but the checked
-        # in go files are generated by gogo proto. Resolving to the gogo proto target preserves the behavior of Go modules.
-        "gazelle:resolve go github.com/mwitkow/go-proto-validators @com_github_mwitkow_go_proto_validators//:validators_gogo",
-    ],
     "google.golang.org/grpc": [
         "gazelle:proto disable",
     ],
-    "k8s.io/api": [
-        "gazelle:proto disable",
-    ],
-    "k8s.io/apiextensions-apiserver": [
-        "gazelle:proto disable",
-    ],
-    "k8s.io/apimachinery": [
-        "gazelle:go_generate_proto false",
-        "gazelle:proto_import_prefix k8s.io/apimachinery",
-    ],
 }
 
-DEFAULT_BUILD_EXTRA_ARGS_BY_PATH = {
-    "github.com/census-instrumentation/opencensus-proto": [
-        "-exclude=src",
-    ],
-}
+DEFAULT_BUILD_EXTRA_ARGS_BY_PATH = {}
diff --git a/internal/bzlmod/go_deps.bzl b/internal/bzlmod/go_deps.bzl
index 9eb1c74..fa50849 100644
--- a/internal/bzlmod/go_deps.bzl
+++ b/internal/bzlmod/go_deps.bzl
@@ -44,6 +44,36 @@
 https://github.com/bazelbuild/bazel-gazelle/tree/master/internal/bzlmod/default_gazelle_overrides.bzl.
 """
 
+_GAZELLE_ATTRS = {
+    "build_file_generation": attr.string(
+        default = "auto",
+        doc = """One of `"auto"` (default), `"on"`, `"off"`.
+
+        Whether Gazelle should generate build files for the Go module. In
+        `"auto"` mode, Gazelle will run if there is no build file in the Go
+        module's root directory.""",
+        values = [
+            "auto",
+            "off",
+            "on",
+        ],
+    ),
+    "build_extra_args": attr.string_list(
+        default = [],
+        doc = """
+        A list of additional command line arguments to pass to Gazelle when generating build files.
+        """,
+    ),
+    "directives": attr.string_list(
+        doc = """Gazelle configuration directives to use for this Go module's external repository.
+
+        Each directive uses the same format as those that Gazelle
+        accepts as comments in Bazel source files, with the
+        directive name followed by optional arguments separated by
+        whitespace.""",
+    ),
+}
+
 def _fail_on_non_root_overrides(module_ctx, module, tag_class):
     if module.is_root:
         return
@@ -68,7 +98,8 @@
     unmatched_overrides = [path for path in override_keys if path not in resolutions]
     if unmatched_overrides:
         fail("Some {} did not target a Go module with a matching path: {}".format(
-            override_name, ", ".join(unmatched_overrides)
+            override_name,
+            ", ".join(unmatched_overrides),
         ))
 
 def _check_directive(directive):
@@ -76,37 +107,40 @@
         return
     fail("Invalid Gazelle directive: \"{}\". Gazelle directives must be of the form \"gazelle:key value\".".format(directive))
 
-def _get_build_file_generation(path, gazelle_overrides):
-    override = gazelle_overrides.get(path)
-    if override:
-        return override.build_file_generation
+def _get_override_or_default(specific_overrides, gazelle_default_attributes, default_path_overrides, path, default_value, attribute_name):
+    # 1st: Check for user-provided specific overrides.
+    specific_override = specific_overrides.get(path)
+    if specific_override and hasattr(specific_override, attribute_name):
+        return getattr(specific_override, attribute_name)
 
-    return DEFAULT_BUILD_FILE_GENERATION_BY_PATH.get(path, "auto")
+    # 2nd: Check for default overrides for specific path.
+    default_path_override = default_path_overrides.get(path)
+    if default_path_override:
+        return default_path_override
 
-def _get_build_extra_args(path, gazelle_overrides):
-    override = gazelle_overrides.get(path)
-    if override:
-        return override.build_extra_args
-    return DEFAULT_BUILD_EXTRA_ARGS_BY_PATH.get(path, [])
+    # 3rd. Check for default attributes provided by the user.
+    global_override_value = getattr(gazelle_default_attributes, attribute_name, None)
+    if global_override_value:
+        return global_override_value
 
-def _get_directives(path, gazelle_overrides):
-    override = gazelle_overrides.get(path)
-    if override:
-        return override.directives
+    # 4th. Return the default value if no override was found.
+    return default_value
 
-    return DEFAULT_DIRECTIVES_BY_PATH.get(path, [])
+def _get_directives(path, gazelle_overrides, gazelle_default_attributes):
+    return _get_override_or_default(gazelle_overrides, gazelle_default_attributes, DEFAULT_DIRECTIVES_BY_PATH, path, [], "directives")
+
+def _get_build_file_generation(path, gazelle_overrides, gazelle_default_attributes):
+    return _get_override_or_default(gazelle_overrides, gazelle_default_attributes, DEFAULT_BUILD_FILE_GENERATION_BY_PATH, path, "auto", "build_file_generation")
+
+def _get_build_extra_args(path, gazelle_overrides, gazelle_default_attributes):
+    return _get_override_or_default(gazelle_overrides, gazelle_default_attributes, DEFAULT_BUILD_EXTRA_ARGS_BY_PATH, path, [], "build_extra_args")
 
 def _get_patches(path, module_overrides):
-    override = module_overrides.get(path)
-    if override:
-        return override.patches
-    return []
+    return _get_override_or_default(module_overrides, struct(), {}, path, [], "patches")
 
 def _get_patch_args(path, module_overrides):
-    override = module_overrides.get(path)
-    if override:
-        return ["-p{}".format(override.patch_strip)]
-    return []
+    override = _get_override_or_default(module_overrides, struct(), {}, path, None, "patch_strip")
+    return ["-p{}".format(override)] if override else []
 
 def _repo_name(importpath):
     path_segments = importpath.split("/")
@@ -124,6 +158,32 @@
     # not available.
     return module_ctx.is_dev_dependency(tag) if hasattr(module_ctx, "is_dev_dependency") else False
 
+# This function processes the gazelle_default_attributes tag for a given module and returns a struct
+# containing the attributes from _GAZELLE_ATTRS that are defined in the tag.
+def _process_gazelle_default_attributes(module_ctx):
+    for module in module_ctx.modules:
+        _fail_on_non_root_overrides(module_ctx, module, "gazelle_default_attributes")
+
+    for module in module_ctx.modules:
+        tags = module.tags.gazelle_default_attributes
+        if not tags:
+            continue
+
+        if len(tags) > 1:
+            fail(
+                "go_deps.gazelle_default_attributes: only one tag can be specified per module, got:\n",
+                *[t for p in zip(module.tags.gazelle_default_attributes, len(module.tags.gazelle_default_attributes) * ["\n"]) for t in p]
+            )
+
+        tag = tags[0]
+        return struct(**{
+            attr: getattr(tag, attr)
+            for attr in _GAZELLE_ATTRS.keys()
+            if hasattr(tag, attr)
+        })
+
+    return None
+
 # This function processes a given override type for a given module, checks for duplicate overrides
 # and inserts the override returned from the process_override_func into the overrides dict.
 def _process_overrides(module_ctx, module, override_type, overrides, process_override_func, additional_overrides = None):
@@ -142,11 +202,11 @@
     for directive in gazelle_override_tag.directives:
         _check_directive(directive)
 
-    return struct(
-        directives = gazelle_override_tag.directives,
-        build_file_generation = gazelle_override_tag.build_file_generation,
-        build_extra_args = gazelle_override_tag.build_extra_args,
-    )
+    return struct(**{
+        attr: getattr(gazelle_override_tag, attr)
+        for attr in _GAZELLE_ATTRS.keys()
+        if hasattr(gazelle_override_tag, attr)
+    })
 
 def _process_module_override(module_override_tag):
     return struct(
@@ -215,6 +275,7 @@
     replace_map = {}
     bazel_deps = {}
 
+    gazelle_default_attributes = _process_gazelle_default_attributes(module_ctx)
     archive_overrides = {}
     gazelle_overrides = {}
     module_overrides = {}
@@ -341,7 +402,6 @@
     # in the module resolutions and swapping out the entry.
     for path, replace in replace_map.items():
         if path in module_resolutions:
-
             # If the replace directive specified a version then we only
             # apply it if the versions match.
             if replace.from_version:
@@ -409,9 +469,9 @@
         go_repository_args = {
             "name": module.repo_name,
             "importpath": path,
-            "build_directives": _get_directives(path, gazelle_overrides),
-            "build_file_generation": _get_build_file_generation(path, gazelle_overrides),
-            "build_extra_args": _get_build_extra_args(path, gazelle_overrides),
+            "build_directives": _get_directives(path, gazelle_overrides, gazelle_default_attributes),
+            "build_file_generation": _get_build_file_generation(path, gazelle_overrides, gazelle_default_attributes),
+            "build_extra_args": _get_build_extra_args(path, gazelle_overrides, gazelle_default_attributes),
             "patches": _get_patches(path, module_overrides),
             "patch_args": _get_patch_args(path, module_overrides),
         }
@@ -451,7 +511,7 @@
         },
         build_naming_conventions = drop_nones({
             module.repo_name: get_directive_value(
-                _get_directives(path, gazelle_overrides),
+                _get_directives(path, gazelle_overrides, gazelle_default_attributes),
                 "go_naming_convention",
             )
             for path, module in module_resolutions.items()
@@ -553,7 +613,7 @@
 )
 
 _gazelle_override_tag = tag_class(
-    attrs = {
+    attrs = dict({
         "path": attr.string(
             doc = """The Go module path for the repository to be overridden.
 
@@ -561,37 +621,15 @@
             extension within this Bazel module.""",
             mandatory = True,
         ),
-        "build_file_generation": attr.string(
-            default = "auto",
-            doc = """One of `"auto"` (default), `"on"`, `"off"`.
-
-            Whether Gazelle should generate build files for the Go module. In
-            `"auto"` mode, Gazelle will run if there is no build file in the Go
-            module's root directory.""",
-            values = [
-                "auto",
-                "off",
-                "on",
-            ],
-        ),
-        "build_extra_args": attr.string_list(
-            default = [],
-            doc = """
-            A list of additional command line arguments to pass to Gazelle when generating build files.
-            """,
-        ),
-        "directives": attr.string_list(
-            doc = """Gazelle configuration directives to use for this Go module's external repository.
-
-            Each directive uses the same format as those that Gazelle
-            accepts as comments in Bazel source files, with the
-            directive name followed by optional arguments separated by
-            whitespace.""",
-        ),
-    },
+    }, **_GAZELLE_ATTRS),
     doc = "Override Gazelle's behavior on a given Go module defined by other tags in this extension.",
 )
 
+_gazelle_default_attributes_tag = tag_class(
+    attrs = _GAZELLE_ATTRS,
+    doc = "Override Gazelle's default attribute values for all modules in this extension.",
+)
+
 _module_override_tag = tag_class(
     attrs = {
         "path": attr.string(
@@ -619,6 +657,7 @@
         "config": _config_tag,
         "from_file": _from_file_tag,
         "gazelle_override": _gazelle_override_tag,
+        "gazelle_default_attributes": _gazelle_default_attributes_tag,
         "module": _module_tag,
         "module_override": _module_override_tag,
     },
diff --git a/tests/bcr/MODULE.bazel b/tests/bcr/MODULE.bazel
index d20340d..87a9bed 100644
--- a/tests/bcr/MODULE.bazel
+++ b/tests/bcr/MODULE.bazel
@@ -24,6 +24,11 @@
 
 # Validate a go.mod replace directive works.
 go_deps.from_file(go_mod = "//:go.mod")
+go_deps.gazelle_default_attributes(
+    directives = [
+        "gazelle:proto disable",
+    ],
+)
 
 # Verify that the gazelle:go_naming_convention directive in an override is
 # respected.