How to secure temporary objects, metadata visibility, and audit assurance in SQL Server (complete guide)

Comments 0

Share to social media

SQL Server environments that are patched, firewalled, and configured to best practice can still carry hidden risk. Global temporary objects, Dynamic Management Views (DMVs), and audit specifications are all legitimate, widely-used SQL Server capabilities. Yet, each can quietly become part of an attack chain when granted too broadly, trusted inside privileged workflows, or only validated against the obvious case.

This article, part of Fabiano Amorim’s complete guide to SQL Server security, explains and demonstrates where these three features commonly go wrong. Learn the the exact signs and queries to check for exposure – and the practical fixes that turn a theoretical risk into a closed gap.

Over the course of this series, I’ve challenged the common assumption that a SQL Server environment is safe simply because it’s patched, firewalled, and configured according to familiar defaults.

I’ve proven that this is not quite true, because real security is not a checkbox. It requires a verification mindset, especially in areas where SQL Server intentionally provides powerful operational features for DBAs, developers, monitoring tools, and application frameworks. 

In this guide, I’ll focus on three features that are easy to underestimate because they look like normal SQL Server behavior. They are global temporary objects, Dynamic Management Views (DMVs), and audit coverage – each a useful feature in their own right.

The other thing they all have in common? They can each become part of an attack chain if granted too broadly, used inside privileged workflows, or trusted without validation. 

My goal here is not to provide a deep exploit walkthrough – that’s for my other series. Here, I want to help you, a DBA, answer the practical questions. Where am I exposed? What should I look for (and what should I monitor?) Then, what should I change when I find a risky pattern? 

Why these SQL Server risks matter

The risks in this article don’t necessarily start with an obviously dangerous permission. They may, in fact, look quite reasonable in isolation.

You might not bat an eyelid at, for example, a developer using a global temporary table, a vendor requesting VIEW SERVER STATE, or an auditor enabling a sensitive-data audit group. The danger comes when these choices are combined with elevated SQL Agent jobs, predictable object names, dynamic SQL, broad monitoring visibility, or narrow audit assumptions. 

Often times, an attacker with a low-privileged foothold only needs to influence something that a trusted process will later consume, observe enough metadata to understand where the trust boundaries are, or choose an access path that was never included in the audit test plan.

All of this is exactly why SQL Server DBAs should take the security of these features seriously, and treat them as operational trust boundaries. In this guide, I’ll help you to achieve this.

SQL Server global temporary tables and procedures 

SQL Server temporary objects are stored in tempdb. Local temporary tables, prefixed with a single #, are scoped to the creating session. Global temporary tables and global temporary procedures prefixed with ##, meanwhile, are different – they’re visible outside the session that created them.

This distinction is where many design mistakes begin, as temporary does not mean ‘private’ – and global means ‘shared’. 

The risk becomes serious when a privileged process relies on a predictable global temporary object. Examples include SQL Server Agent jobs running as sa, maintenance procedures executing as dbo, vendor routines that use global scratch tables, or administrative scripts that pass state between job steps through ## objects.

If lower-privileged users can predict the name, observe the lifecycle, pre-create a competing object, or influence the rows consumed by the privileged process, the temporary object becomes a trust-boundary problem. It’s that simple.

Global temporary procedures deserve special attention. A global temporary table may allow data poisoning, and a global temporary procedure is executable code. If privileged code executes a ## procedure by name, the important security question is: who controlled the code body that name resolved to at runtime?

An example of this in action

Consider a custom SQL Agent job that performs nightly user provisioning. It first creates a global table called ##UserProvisioningQueue and loads rows from an external feed. Then, it reads that table and builds dynamic SQL to create logins or assign roles. The job owner is sa because the workflow performs server-level administration. 

A compromised low-privileged account that can discover this pattern may try to create the same global object before the job starts – or may wait for the object to appear and attempt to insert crafted rows.

If the privileged job doesn’t validate the object origin, schema, and content, it may process attacker-controlled input under the sa context.

What are the signs a DBA should look for?

  • SQL Agent job steps, stored procedures, ETL routines, or vendor scripts that create or consume objects with names beginning with ##.

  • Privileged jobs that pass security-sensitive state between job steps through global temporary tables instead of local temp tables or permanent secured staging tables.

  • Global temporary procedures in tempdb – especially if their names are predictable or if they’re executed by elevated jobs.

  • Dynamic SQL built from rows stored in temporary tables, particularly when the job owner is sa or another sysadmin-equivalent principal.

  • Repeated errors such as ‘There is already an object named…’ around the same time privileged jobs start, which may indicate name collisions or probing.

What to monitor – and how

Code and job-step review

Schedule a recurring check against msdb.dbo.sysjobsteps and sys.sql_modules for references to ##. Treat any result in a privileged workflow as something that needs design review, not just code cleanup.

Current tempdb object inventory

Periodically sample tempdb for global temporary objects and correlate creation windows with privileged jobs. This is especially useful during testing and incident response.

Extended Events

Capture object creation and DDL (data declaration language) activity in tempdb, together with session_id, username, client_app_name, client_hostname, and sql_text. Validate the exact event fields in your SQL Server version before promoting the session to production.

Future-proof database monitoring with Redgate Monitor

Multi-platform database observability for your entire estate. Optimize performance, ensure security, and mitigate potential risks with fast deep-dive analysis, intelligent alerting, and AI-powered insights.
Learn more & try for free

SQL Audit

Where appropriate, layer schema/object change auditing with job definition monitoring. Audit alone is not a substitute for removing the risky design, but it helps detect unexpected object creation and modification.

Job history

Review job failures and retries around steps that create global temporary objects. A sudden collision or object-exists error can be a signal that another session created the expected object first.

Practical DBA interpretation

Finding ## in code is not automatically a vulnerability. The risk depends on who creates it, who can see it (and write to it), the predictability of its name, whether privileged code trusts it, and whether it feeds dynamic SQL or administrative decisions. The review should focus on the trust boundary rather than just the prefix.

  • Remove global temporary objects from privileged workflows, using local temporary tables where possible. Local # tables are session-scoped and avoid the cross-session trust problem.

  • Use secured permanent staging tables when state must cross sessions. If multiple job steps or sessions must exchange data, use a dedicated schema with strict permissions, ownership, validation constraints, and clear cleanup rules.

  • Avoid global temporary procedures; do not let privileged code execute ## procedures by predictable name! Package helper logic in signed stored procedures or controlled permanent modules instead.

  • Use unpredictable names only as a fallback. A GUID (globally unique identifier) suffix can reduce object hijacking risk, but doesn’t fix the core design if lower-privileged sessions can still influence the object.

  • Validate before use. If a legacy workflow can’t be changed immediately, validate the object_id, schema, expected columns, expected data types, row source, and row values before privileged code consumes the object.

  • Review dynamic SQL. Never concatenate administrative commands directly from temporary-table rows. Use parameterized calls, QUOTENAME for identifiers, strict allowlists/whitelists, and explicit validation.

Information disclosure through DMVs and metadata visibility in SQL Server

DMVs, catalog views, traces, Query Store, Extended Events, and job history are essential for troubleshooting. They’re also excellent reconnaissance tools.

While a login with broad observability rights may not have SELECT permission on sensitive tables, it may still be able to learn database names, job names, linked server names, query text, parameter values, execution patterns, privileged account names, and application architecture. This matters because attackers rarely need full data access at the start of a campaign.

Instead, they just need a map telling them which accounts are trusted, which jobs run as sa, which databases look sensitive, which procedures use EXECUTE AS OWNER, which linked servers exist, and which monitoring rules are watching them. Broad metadata visibility can provide that map.

The risk increased in complexity with newer SQL Server versions, with some DMV access now split across more granular permissions (such as VIEW SERVER PERFORMANCE STATE in SQL Server 2022 onwards). It’s useful, yes, but doesn’t remove the need for review.

Simply put: a monitoring account should receive the smallest permission set necessary for the exact telemetry it needs.

An example of this in practice

A third-party monitoring account is granted VIEW SERVER STATE because the vendor dashboard needs to display active sessions and wait statistics. Later, the account is compromised through the application server that hosts the monitoring tool. The attacker can’t query the Finance database directly, but they can observe currently running requests and retrieve query text through DMVs.

From that visibility, the attacker learns the name of a privileged password-reset procedure, sees which job checks for unauthorized sysadmins, discovers which linked server is used for financial reporting, and identifies the Windows group that the monitoring script treats as approved.

So, even if no data rows are stolen at this stage, the attacker has learned how the environment defends itself – and where to aim the next step.

What are the signs a DBA should look for?

  • Application service accounts, reporting users, and vendor tools. Additionally, any AD (Active Directory) groups granted VIEW SERVER STATE, VIEW SERVER PERFORMANCE STATE, VIEW ANY DEFINITION, ALTER TRACE, or ALTER ANY EVENT SESSION.

  • Support accounts that can read job definitions, job history, query text, execution plans, Query Store data, or Extended Events output without a clear operational need.

  • Monitoring jobs or DBA scripts that include sensitive server names, approved-admin lists, credentials, tokens, or detection logic in plain T-SQL text.

  • Unusual spikes in conversion errors, divide-by-zero errors, or syntax errors against system views, which may indicate error-based metadata probing.

  • Repeated calls to sys.dm_exec_sql_text, sys.dm_exec_query_plan, sys.dm_exec_requests, fn_trace_gettable, or Extended Events file readers from accounts that don’t usually perform troubleshooting.

What to monitor – and how

  • Permission baseline. Store a baseline of server-level permissions and fixed server-role membership. Alert on new grants of broad observability permissions, especially to service accounts and domain groups.

  • Error probing. Use Extended Events to capture error_reported for conversion errors and divide-by-zero errors from low-privileged accounts. Then, correlate the sql_text with system object access.

  • Query text access. In high-security environments, monitor repeated access to query-text and plan-related DMVs. This can be noisy, so start with unusual principals rather than all DBA tooling.

  • Default trace and trace permissions. Review who has ALTER TRACE and who can read trace files. If default trace is not needed, move to controlled Extended Events sessions with explicit targets and retention.

  • Monitoring-script hygiene, reviewing what your own monitoring code reveals. If a user can see the text of your monitoring query, assume they can also see your allowlists and detection gaps!

An important distinction to remember

Not every monitoring account is dangerous, and not every DMV is sensitive in the same way. The problem is blanket visibility. Replace broad grants with task-specific access wherever possible, and assume that query text, execution plans, and job definitions may reveal security assumptions that attackers can reuse.

  • Apply least privilege to observability, granting only the permissions required for the specific monitoring function. Do not use VIEW SERVER STATE as the default answer to every monitoring request!

  • Use signed modules or controlled wrappers. When a user needs a small set of DMV-derived metrics, expose a sanitized result through a signed stored procedure instead of granting broad DMV access.

  • Separate monitoring tiers – a basic health dashboard should not use the same account as deep DBA diagnostics! Use separate accounts, separate permissions, and separate retention.

  • Remove secrets from T-SQL text. Never hardcode passwords, tokens, SAS (shared access signature) URLs, sensitive hostnames, or detection allowlists in queries that can be observed by non-administrators.

  • Reduce trace exposure. Prefer controlled Extended Events sessions over default trace dependencies. Restrict file-system access to trace/XE (Extended Events) targets.

  • Review error handling – and never echo sensitive internal values in custom error messages or CATCH blocks! Treat errors as a potential disclosure channel.

Move fast. Govern at scale.

Redgate Flyway Enterprise embeds guardrails in the database layer, so every change is policy-checked, deterministic, and traceable.
Try for free

SQL Server audit assurance: proving your audit catches indirect access

Auditing is the last line of defense when prevention fails – and is also one of the easiest controls to overestimate. Many environments can show that an audit exists, but far fewer can prove that it captures the actual ways attackers may access the data. The difference between audit configuration and audit assurance is testing.

A common blind spot appears when audit policies are written around a single access path. For example, a DBA may validate that SELECT * FROM dbo.SecureVault generates an audit record, then assume the table is covered.

However, real access paths may involve views, synonyms, stored procedures, dynamic SQL, cross-database three-part names, sparse column sets, computed columns, indexed views, or application modules. If the audit was only tested against the obvious query, it may provide a false sense of security.

SENSITIVE_BATCH_COMPLETED_GROUP can be useful for classified data in SQL Server 2022 (and onwards), but should not be treated as a complete audit strategy by itself. Instead, layer it with schema object access auditing, permission-change auditing, module-execution monitoring, Extended Events, and regular adversarial validation.

An example of this in practice

An organization classifies columns in dbo.SecureVault as highly confidential and enables an audit specification. During validation, the DBA runs a direct SELECT from the same database context, sees the expected audit event, and the compliance task is marked as complete.

Later, a compromised reporting account accesses the same data through a view, synonym, stored procedure, dynamic SQL, cross-database three-part name, or XML column set over sparse columns.

Depending on how the audit was scoped and tested, the indirect path may produce different audit details, may be captured only at the wrapper level, or may not generate the alert the security team expects. The control may technically exist while still failing the detection goal.

What are the signs a DBA should look for?

  • Audit specifications that rely on one action group only; no complementary coverage for schema access, permission changes, role membership changes, or module execution.

  • Sensitive tables exposed through views, synonyms, stored procedures, functions, reporting schemas, computed columns, sparse column sets, or cross-database dependencies, while auditing is tested only against the base table.

  • Audits that are enabled but rarely reviewed, never restored from the audit target, and are not integrated with alerting.

  • Compliance validation based on the existence of an audit configuration, rather than adversarial testing of access paths.

  • Audit targets stored in locations that SQL Server service accounts, local administrators, or privileged database operators can tamper with, free from independent detection.

What to monitor – and how

  • Audit the audit configuration. Monitor changes to audits and audit specifications. A disabled audit, changed predicate, changed target path, or dropped specification should alert immediately.

  • Test direct and indirect access paths. For each sensitive table, test direct SELECT, views, stored procedures, dynamic SQL, synonyms, cross-database access, reporting schemas, and sparse XML column sets where applicable.

  • Layer SQL Audit with XE. Use SQL Audit for durable security evidence, coupled with Extended Events for additional operational context such as session_id, client_app_name, hostname, and SQL text patterns.

  • Validate alert semantics, and don’t just check that an audit row exists. Verify that the row contains enough information for the SOC (security operations center) or DBA team to understand what was accessed, by whom, from where, and through which path.

  • Review audit retention and integrity. Ensure audit files are written to protected storage, collected centrally, retained long enough, and reviewed for gaps.

The access paths to test – and how to do so

Access path to testWhy it mattersExpected assurance outcome
Direct SELECT on sensitive tableBaseline validation only. Not sufficient by itself.Audit row includes principal, database, object, statement, time, client context, and result.
View over sensitive tableMany applications expose data through reporting views.Audit either captures the base object access or clearly captures the wrapper object and statement.
Stored procedure wrapperApplications may never query the base table directly.Execution and underlying sensitive access are visible enough for investigation.
Dynamic SQLAttackers often use dynamic execution to change shape and context.Audit captures the submitted batch/procedure text or complementary XE captures context.
Cross-database three-part nameDatabase-level audit scoping may be misunderstood.Access from another database context still produces the expected security signal.
SynonymSynonyms hide the target object and can obscure review.Audit records are usable and identify the path or the resolved target clearly enough.
Sparse XML column setColumn-level expectations may not match aggregated XML access.Sensitive data access still generates a detectable event or compensating alert.

Audit design principle

A good audit strategy is defined by whether the security team can reliably answer who accessed the sensitive data, through what path, and from which client. Also: which principal did they use, did the action succeed, and would the event have still been captured if the attacker had used an indirect path?

  • Use layered audit coverage. Combine sensitive-data audit groups with schema object access, schema object changes, permission changes, database role membership changes, server role membership changes, and audit-change monitoring where appropriate.

  • Design for indirect access. Audit wrappers, views, synonyms, procedures, and reporting schemas that expose sensitive data.

  • Create an audit validation suite with a repeatable set of test queries for each critical data set. Run it after schema changes, permission changes, SQL Server upgrades, and audit policy changes.

  • Protect audit targets by writing audit files to protected storage and restricting operating system (OS)-level access. Also forward audit events to a central location where DBAs and SQL service accounts cannot silently modify history.

  • Alert on audit tampering. Audit disablement, audit target changes, specification changes, and audit write failures should trigger high-priority alerts.

  • Document expected gaps. If a control is compensating rather than complete, document what it does not catch – and what secondary control covers that gap.

In summary: a DBA’s checklist for securing temporary objects, metadata visibility, and audit assurance in SQL Server

  • Review all ## usage in SQL Agent jobs, stored procedures, vendor scripts, and application code.

  • Remove global temporary objects from privileged workflows or replace them with local temp objects or secured staging tables.

  • Review all grants of VIEW SERVER STATE, VIEW SERVER PERFORMANCE STATE, VIEW ANY DEFINITION, ALTER TRACE, and ALTER ANY EVENT SESSION.

  • Review who can read query text, execution plans, job steps, Extended Events files, trace files, and Query Store data.

  • Monitor conversion/divide-by-zero error bursts against system objects as possible metadata probing.

  • Remove secrets, tokens, approved-admin allowlists, and sensitive server names from observable T-SQL text.

  • Inventory audit specifications and confirm that sensitive-data access is tested through direct and indirect paths.

  • Alert on audit configuration changes and ensure audit targets are protected outside the control of the SQL Server service account.

  • Treat monitoring and auditing as code: version it, test it, review it, and re-test it after every meaningful change.

Conclusion (the key takeaways)

A global temporary object is not private just because it is temporary, a monitoring permission is not harmless just because it is read-only, and an audit specification is not sufficient just because it exists. Each one must be reviewed in the context of execution scope, privilege, observability, and validation.

As proven in every part of this series, attackers succeed by chaining ordinary features together. Default permissions, privileged roles, msdb jobs, untrusted restores, cross-database trust, triggers, linked servers, temporary objects, DMVs, and audit blind spots are all connected trust boundaries. It only takes one small weakness, in one area, to create danger, as another trusted component may amplify it.

For DBAs, the message is simple: reduce privilege, shared scope, and unnecessary metadata visibility. You should also prevent privileged code consuming untrusted input, and treat an audit as a system that must be tested.

SQL Server can be secured effectively – but only when DBAs move from trusting defaults to continuously verifying what the environment actually allows.

Protect your data. Demonstrate compliance.

With Redgate, stay ahead of threats with real-time monitoring and alerts, protect sensitive data with automated discovery & masking, and demonstrate compliance with traceability across every environment.
Learn more

FAQs: How to secure temporary objects, metadata visibility, and audit assurance in SQL Server

1. What's the risk with global temporary tables and procedures in SQL Server?

Objects prefixed with ## are visible outside the session that created them. If a privileged process (like an sa-owned SQL Agent job) trusts a predictable global object, a lower-privileged attacker can pre-create it, poison its data, or — in the case of global temp procedures — control the executable code itself.

2. Why are DMVs considered a security risk if they're read-only?

DMVs don’t expose table data, but broad access (e.g., VIEW SERVER STATE) can reveal job names, query text, linked servers, and privileged account names. This metadata gives attackers a reconnaissance map of trust boundaries even without direct data access.

3. How do I know if my SQL Server audit is actually working?

Test more than the obvious path. An audit validated only against a direct SELECT on a sensitive table may miss access through views, synonyms, stored procedures, dynamic SQL, or cross-database three-part names — all of which can bypass the assumptions baked into the original audit design.

4. What's the single most important mindset shift for DBAs here?

Treat these features as operational trust boundaries, not neutral defaults. Ask who can see an object, who can write to it, whether privileged code validates it before use, and whether your audit was tested against indirect access paths — not just the expected one.

5. Where should I start if I only have time to check one thing?

Run a search for ## references across msdb.dbo.sysjobsteps and sys.sql_modules. Global temp objects inside privileged jobs are one of the most concrete, checkable risks covered here.

This document contains proprietary information and is protected by copyright law.

Copyright © 2026 Red Gate Software Limited. All rights reserved

Article tags

About the author

Fabiano Amorim

See Profile

Fabiano Amorim is a Data Platform MVP since 2011 who loves to conquer complex, challenging problems - especially ones that others aren’t able to solve. He first became interested in technology when his older brother would bring him to his work meetings at the age of 14. With over a decade of experience, Fabiano is well known in the database community for his performance tuning abilities. Elsewhere, he loves to read and spend time with his family.