This check flags Cloud SQL for SQL Server instances where cross db ownership chaining is set to on. That setting lets database access permissions bleed across databases on the same instance, which can be abused for privilege escalation. Set the cross db ownership chaining database flag to off and rely on explicit grants instead.
Cross-database ownership chaining is one of those SQL Server settings that sounds convenient on paper and turns into a security headache in practice. On a GCP Cloud SQL SQL Server instance, leaving it enabled means a user who owns objects in one database can reach into another database on the same server without the access checks you would normally expect. Lensix raises sql_crossdbownership when it finds an instance with this behavior turned on at the instance level.
This post walks through what the check looks for, why it is worth caring about, and exactly how to turn the setting off without breaking legitimate workloads.
What this check detects
The sql_crossdbownership check inspects the database flags on each Cloud SQL SQL Server instance in your GCP project. It fails when it finds the flag cross db ownership chaining set to on.
Ownership chaining is the mechanism SQL Server uses to skip permission checks when a chain of objects shares the same owner. When you query a view, for example, SQL Server normally checks your permission on both the view and the underlying tables. If the view and tables have the same owner, the engine assumes the owner intended the access and skips the check on the underlying objects. That is single-database ownership chaining, and it is generally fine.
Cross-database ownership chaining extends that trust across database boundaries on the same instance. With it enabled, an object in DatabaseA can reference an object in DatabaseB, and if the ownership chain lines up, SQL Server skips the permission check in DatabaseB too.
Note: The flag exists at the server (instance) level on Cloud SQL. SQL Server also exposes a per-database DB_CHAINING option, but the server-level flag is the one that opens chaining up broadly. This check focuses on the instance-level flag because that is the wide-open setting.
Why it matters
The risk is privilege escalation across databases that were supposed to be isolated from each other. A common pattern on a single Cloud SQL instance is hosting multiple databases for different applications, tenants, or environments. The whole point of putting them on separate databases is to keep their access boundaries separate.
Cross-database chaining quietly undermines that. Consider a realistic scenario:
- You run an instance with
AppDBandBillingDB. - A developer has
db_owneronAppDBbut no access at all toBillingDB. - Both databases happen to be owned by the same login, which is extremely common when one service account creates everything.
- With chaining on, that developer can create a stored procedure or view in
AppDBthat reads fromBillingDB, and SQL Server will not check their permissions on the billing tables.
The result is a user reading or modifying data in a database they were never granted access to. If the shared owner has elevated rights, the blast radius gets bigger. This is exactly the kind of lateral movement an attacker looks for after compromising a single application credential.
Warning: The danger scales with consolidation. The more databases you pack onto one instance to save money, the more attractive a target chaining becomes, because a single foothold can reach everything on the box.
Beyond the direct risk, this setting trips compliance benchmarks. The CIS Benchmark for Google Cloud and Microsoft's own SQL Server hardening guidance both call for cross-database ownership chaining to be disabled unless you have a specific, documented reason to use it. Auditors flag it, and so does Lensix.
How to fix it
The fix is to set the cross db ownership chaining database flag to off. You can do this through the gcloud CLI, the Cloud Console, or your infrastructure-as-code tooling.
Option 1: gcloud CLI
First, check the current flags on the instance so you do not clobber others when you update:
gcloud sql instances describe INSTANCE_NAME \
--project=PROJECT_ID \
--format="value(settings.databaseFlags)"
Warning: The --database-flags argument replaces the full set of flags. If your instance has other flags set, include all of them in the command below, or they will be reset to their defaults.
Then set the flag to off, including any other flags your instance already uses:
gcloud sql instances patch INSTANCE_NAME \
--project=PROJECT_ID \
--database-flags="cross db ownership chaining=off"
If the instance also had, say, remote access=off set, the command becomes a comma-separated list:
gcloud sql instances patch INSTANCE_NAME \
--project=PROJECT_ID \
--database-flags="cross db ownership chaining=off,remote access=off"
Note: Applying a database flag to a Cloud SQL for SQL Server instance does not require a restart for cross db ownership chaining, since it is a dynamic flag. Still, verify after the change because Cloud SQL occasionally needs a brief reconfiguration depending on the flag set.
Option 2: Cloud Console
- Open SQL in the Google Cloud Console and select your SQL Server instance.
- Click Edit.
- Expand the Flags section.
- Find
cross db ownership chaining. If it is present and set toon, change it tooff. If it is absent, it already defaults to off, and no failing instance should reach this state. - Click Save.
Option 3: Terraform
If you manage Cloud SQL with Terraform, set the flag explicitly so it stays off across applies:
resource "google_sql_database_instance" "sqlserver" {
name = "my-sqlserver-instance"
database_version = "SQLSERVER_2019_STANDARD"
region = "us-central1"
settings {
tier = "db-custom-2-7680"
database_flags {
name = "cross db ownership chaining"
value = "off"
}
}
}
Verify the fix
Confirm the flag is now off:
gcloud sql instances describe INSTANCE_NAME \
--project=PROJECT_ID \
--format="json(settings.databaseFlags)"
You should see cross db ownership chaining with value off, or no longer see it listed if you removed it (off is the default).
Danger: If any production code actually depends on cross-database chaining, turning it off will break those queries with permission errors. Before flipping the flag, search your codebase and database objects for views, procedures, or functions that reference objects in another database by three-part name (OtherDB.schema.object). Plan the proper grants first. See the next section for the right way to replace chaining.
Replacing chaining the right way
If something genuinely needs to read across databases, do not reach for chaining. Grant the access explicitly. The supported, auditable approach is to create a user mapped to the same login in both databases and grant only the permissions that are actually needed.
-- In the target database, grant explicit access
USE BillingDB;
GO
CREATE USER app_reader FOR LOGIN app_login;
GO
GRANT SELECT ON SCHEMA::dbo TO app_reader;
GO
For cases where you want a procedure to run with elevated rights without granting the caller those rights, use module signing or the EXECUTE AS clause instead of chaining. These give you explicit, traceable control over what runs with what permissions, which is far easier to reason about during an audit than implicit ownership chains.
Tip: If you truly need data shared across two databases regularly, ask whether they should be separate databases at all. Sometimes the cleaner answer is consolidating into one database with proper schema separation, or splitting onto separate instances for hard isolation.
How to prevent it from happening again
Fixing one instance is easy. Keeping every future instance compliant is the real work. Bake the rule into the places where instances get created.
Enforce with Terraform and policy-as-code
Add an OPA or Conftest policy that rejects any Cloud SQL SQL Server instance plan that does not set the flag to off. A simple Conftest rule against a Terraform plan:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_sql_database_instance"
flags := resource.change.after.settings[_].database_flags
flag := flags[_]
flag.name == "cross db ownership chaining"
flag.value == "on"
msg := sprintf("Instance %s has cross db ownership chaining enabled", [resource.address])
}
Run it in CI before terraform apply so a misconfigured instance never reaches your environment.
Gate it in CI/CD
Wire the policy check into your pipeline as a required step:
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
conftest test tfplan.json --policy ./policy
Use GCP Organization Policy where possible
While GCP does not offer a built-in org policy constraint for this specific database flag, you can detect drift continuously with Lensix and alert on any instance that fails sql_crossdbownership. Pair the scheduled scan with a notification so a console-created instance gets caught within minutes rather than at the next audit.
Tip: Run the Lensix sql_checks module on a schedule across all projects. Detection plus a pull-request gate gives you defense in depth: the gate stops most issues at the source, and the scan catches anything that slips through a manual change.
Best practices
- Keep cross-database ownership chaining off by default. It is off by default on new instances for a reason. Treat any instance that turns it on as needing explicit justification.
- Prefer explicit grants over implicit trust. Map users in each database and grant least-privilege permissions. Implicit chaining is hard to audit and easy to abuse.
- Use module signing or
EXECUTE ASwhen a procedure needs more rights than the caller, rather than relying on ownership chains. - Isolate sensitive databases on separate instances. Hard instance-level isolation removes cross-database chaining as a concern entirely.
- Review all server-level database flags together. While you are at it, confirm related hardening flags like
remote access,user connections, andcontained database authenticationmatch your security baseline. - Document any exception. If a workload genuinely requires chaining, record why, scope it as narrowly as possible, and revisit it on a schedule.
Cross-database ownership chaining is a small flag with an outsized impact on how trust flows across your databases. Turn it off, replace it with explicit grants where needed, and put a gate in place so it never quietly comes back.

