{"id":88723,"date":"2020-10-19T15:18:22","date_gmt":"2020-10-19T15:18:22","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=88723"},"modified":"2026-04-21T13:24:36","modified_gmt":"2026-04-21T13:24:36","slug":"exploring-errors-to-reveal-unauthorized-information","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/exploring-errors-to-reveal-unauthorized-information\/","title":{"rendered":"SQL Server View-Based Security Flaw: How Error Messages Expose Hidden Data"},"content":{"rendered":"\n<p id=\"h-\"><strong>A common pre-SQL Server 2016 pattern for row-level access control used views: create a view that filters rows based on the current user, grant users access to the view but not the underlying table. This article demonstrates a specific information disclosure vulnerability in this approach: even if a user cannot SELECT a row from the view, they can infer the row exists by attempting to INSERT, UPDATE, or DELETE against the view and reading the resulting error message. <\/strong><\/p>\n\n\n\n<p id=\"h-\"><strong>SQL Server&#8217;s error messages for constraint violations, duplicate key errors, and other integrity errors can reveal information about rows the view is supposed to hide. The fix is SQL Server&#8217;s native Row Level Security feature, introduced in SQL Server 2016.<\/strong><\/p>\n\n\n\n<p>Maintaining a secure environment is very hard. There are so many threats that can be exploited that it demands a specialized security team to continuously evaluate, monitor, and audit the many known and unknown threats. SQL Server is just another process that can be exploited and needs to be monitored. Still, since the database\u2019s nature is to store information, including sensitive information, it is one of the main targets chosen by attackers.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-in-this-article-how-to-reveal-information-that-a-user-is-not-supposed-to-see-and-how-to-protect-it\">In this article: how to reveal information that a user is not supposed to see (and how to protect it)<\/h4>\n\n\n\n<p>In this article, I would like to show you a technique that can be used to reveal information that a user is not supposed to see and how to protect it. The technique is very simple, and it relies on SQL Server error messages to reveal information that could be stolen by adversaries. This technique has been used for several years in SQL Injection attacks, and many DBAs still overlook it.<\/p>\n\n\n\n<p>The idea is simple; a non-privileged user can write a query referencing a SQL Server view that causes an exception, such as <strong><em>invalid conversion<\/em><\/strong> to be thrown during query processing if certain row values exist in the underlying tables. Depending on the query plan, these exceptions may bypass SQL Server permission validations and are thrown even if existing data cannot be retrieved through the view.<\/p>\n\n\n\n<p>Before I move forward with recommendations, let\u2019s understand the problem starting by looking at a simple example.<\/p>\n\n\n\n<p>Note: I\u2019m running all tests on Microsoft SQL Server 2017 (RTM-CU20) (KB4541283) &#8211; 14.0.3294.2 (X64) . Other versions may have different results.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-a-simple-view-based-row-level-security\">A simple view-based row-level security<\/h2>\n\n\n\n<p>Before SQL Server 2016 and <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/security\/row-level-security?view=sql-server-ver15\">row-level-security<\/a>, it was very difficult to implement restrictions on data at the row level. One of the most common solutions for this requirement is to use a view with a predicate filter used to reveal only the information a user has access.<\/p>\n\n\n\n<p>For instance, consider the following scenario:<\/p>\n\n\n\n<p>Create two user accounts that will demonstrate different access capabilities.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">USE tempdb\nGO\nDROP USER IF EXISTS Manager\nDROP USER IF EXISTS User1\nCREATE USER Manager WITHOUT LOGIN  \nCREATE USER User1 WITHOUT LOGIN  \nGO<\/pre>\n\n\n\n<p>Create a table to hold test data.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DROP TABLE IF EXISTS TabSalary\nCREATE TABLE TabSalary  \n(  \n    EmpID      INT,\n    Employee   VARCHAR(10),  \n    Salary     NUMERIC(8,2),\n    HideSalary BIT\n);\nGO<\/pre>\n\n\n\n<p>Populate the table with four rows of data.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">INSERT INTO TabSalary VALUES(1, 'Bob', 1000, 0), \n                            (2, 'Jonh', 5000, 0),\n                            (3, 'Mark', 8000, 1),\n                            (4, 'Robert', 9500, 1)\nGO\n-- 4 rows...\nSELECT * FROM TabSalary\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"657\" height=\"262\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-27.png\" alt=\"\" class=\"wp-image-88724\"\/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-what-is-a-row-level-security-view\">What is a row-level security view?<\/h4>\n\n\n\n<p>A row-level security view controls which rows each user can see. For instance, the Manager user will be able to see all rows, while any other user will only see rows where HideSalary is equal to 0.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DROP VIEW IF EXISTS vw_LowSalary\nGO\nCREATE VIEW vw_LowSalary\nAS\n  SELECT EmpID, Employee, Salary FROM TabSalary\n   WHERE (HideSalary = 0 OR USER_NAME() = 'Manager')\nGO<\/pre>\n\n\n\n<p>Grant read access on the view to users.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">GRANT SELECT ON vw_LowSalary TO Manager \nGRANT SELECT ON vw_LowSalary TO User1 \nGO<\/pre>\n\n\n\n<p>Now, if User1 tries to access the data, it will return the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXECUTE AS USER = 'User1'\nSELECT * FROM vw_LowSalary\nGO\nREVERT\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"559\" height=\"195\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-28.png\" alt=\"\" class=\"wp-image-88725\"\/><\/figure>\n\n\n\n<p>User Manager will have access to all rows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXECUTE AS USER = 'Manager'\nSELECT * FROM vw_LowSalary\nGO\nREVERT\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"609\" height=\"266\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-29.png\" alt=\"\" class=\"wp-image-88726\"\/><\/figure>\n\n\n\n<p><strong><em>The problem with this approach is that any user with read access to the view can write a carefully crafted query that uses an expression executed in a specific order by the query optimizer. This causes an information leak through the use of the exception error message.<\/em><\/strong><\/p>\n\n\n\n<p>For instance, if a query that attempts to convert the Employee column to an Integer, it returns the following message:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXECUTE AS USER = 'User1'\nSELECT * FROM vw_LowSalary\nWHERE CONVERT(INT, Employee) = 1\nGO\nREVERT\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"825\" height=\"135\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-30.png\" alt=\"\" class=\"wp-image-88727\"\/><\/figure>\n\n\n\n<p>Bob is a name that User1 could already access, so there is no \u201cleaked information\u201d here. But what about a query that ignores employees Bob and Jonh that User1 can already access?<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXECUTE AS USER = 'User1'\nSELECT * FROM vw_LowSalary\nWHERE Employee NOT IN ('Bob', 'Jonh')\n  AND CONVERT(INT, Employee) = 1\nGO\nREVERT\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"810\" height=\"128\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-31.png\" alt=\"\" class=\"wp-image-88728\"\/><\/figure>\n\n\n\n<p>User1 shouldn\u2019t be able to see Mark\u2019s row, right? How can User1 see Marks salary information? Easy, just change the query to convert the salary column.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXECUTE AS USER = 'User1'\nSELECT * FROM vw_LowSalary\nWHERE Employee NOT IN ('Bob', 'Jonh')\n  AND CONVERT(INT, CONVERT(VARCHAR, Salary)) = 0\nGO\nREVERT\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"841\" height=\"127\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-32.png\" alt=\"\" class=\"wp-image-88729\"\/><\/figure>\n\n\n\n<p>As you can see, even though there is a security predicate in place to prevent a malicious user from directly querying other people&#8217;s salary, User1 was able to determine the data by running a query that returns a SQL Server error message to leak the data.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-important-to-note\">Important to note!<\/h4>\n\n\n\n<p><em>To leak the correct information, you need to make sure that the query processor is evaluating the expression in the correct order. Otherwise, it would first try to convert the data in the row the user already has access to and not leak the desired data. The query optimizer will have to create a plan that is pushing the predicate down to the table access. Look at the execution plan of the query to confirm the expression evaluation order. If necessary, you may need to force a short-circuit using a <a href=\"https:\/\/sqlperformance.com\/2014\/06\/t-sql-queries\/dirty-secrets-of-the-case-expression\">CASE<\/a> expression.<\/em><\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-row-level-security-in-sql-server-2016-does-it-save-the-day\">Row-level security in SQL Server 2016 &#8211; does it save the day?<\/h4>\n\n\n\n<p><a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/security\/row-level-security?view=sql-server-ver15#SecNote\">Row-level security<\/a> in SQL Server 2016 was introduced to address this problem. Well, kind of. Considering the same scenario, you would need to do the following:<\/p>\n\n\n\n<p>The first step is to create a function with the predicate that will be used in a security policy.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DROP SECURITY POLICY IF EXISTS SalaryFilter\nGO\nDROP FUNCTION IF EXISTS dbo.fn_SecurityPredicate\nGO\nCREATE FUNCTION dbo.fn_SecurityPredicate(@HideSalary CHAR(1))  \nRETURNS TABLE  \nWITH SCHEMABINDING  \nAS  \n  RETURN SELECT 1 AS fn_securitypredicate_result\n          WHERE (@HideSalary = 0 OR USER_NAME() = 'Manager');  \nGO<\/pre>\n\n\n\n<p>Create a security policy on table TabSalary.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CREATE SECURITY POLICY SalaryFilter\nADD FILTER PREDICATE dbo.fn_SecurityPredicate(HideSalary)\nON dbo.TabSalary\nWITH (STATE = ON); \nGO<\/pre>\n\n\n\n<p>Grant access to table TabSalary to users.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">GRANT SELECT ON TabSalary TO Manager;  \nGRANT SELECT ON TabSalary TO User1;  \nGO<\/pre>\n\n\n\n<p>Just like when using the view, User1 only has access to employees \u201cBob\u201d and \u201cJonh\u201d.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXECUTE AS USER = 'User1'\nSELECT * FROM TabSalary\nGO\nREVERT\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"577\" height=\"201\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-33.png\" alt=\"\" class=\"wp-image-88730\"\/><\/figure>\n\n\n\n<p>The user Manager can see all rows.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXECUTE AS USER = 'Manager'\nSELECT * FROM TabSalary\nGO\nREVERT\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"515\" height=\"263\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-34.png\" alt=\"\" class=\"wp-image-88731\"\/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-what-is-the-main-difference-between-the-view-based-solution-and-the-row-level-security-feature\">What is the main difference between the view-based solution and the row-level security feature?<\/h4>\n\n\n\n<p>Here is the main difference between the view-based solution and the row-level security feature. If User1 tries to run the implicit conversion query, the query will return the following error message:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXECUTE AS USER = 'User1'\nSELECT * FROM TabSalary\nWHERE Employee NOT IN ('Bob', 'Jonh')\n  AND CONVERT(INT, CONVERT(VARCHAR, Employee)) = 0\nGO\nREVERT\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"845\" height=\"132\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-35.png\" alt=\"\" class=\"wp-image-88732\"\/><\/figure>\n\n\n\n<p>As you can see, the data is masked. Because the query failed, the attacker can still infer that there are more rows in the underlying data. They can even write a query to identify the salary value, but it will be more difficult, and the data will never be leaked to the user screen. A query to infer the salary would be something like:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">EXECUTE AS USER = 'User1'\nGO\nSELECT * FROM TabSalary\nWHERE Employee NOT IN ('Bob', 'Jonh')\n  AND Salary = 7999\n  AND CONVERT(INT, CONVERT(VARCHAR, Employee)) = 0\nGO\nSELECT * FROM TabSalary\nWHERE Employee NOT IN ('Bob', 'Jonh')\n  AND Salary = 8000\n  AND CONVERT(INT, CONVERT(VARCHAR, Employee)) = 0\nGO\nSELECT * FROM TabSalary\nWHERE Employee NOT IN ('Bob', 'Jonh')\n  AND Salary = 8001\n  AND CONVERT(INT, CONVERT(VARCHAR, Employee)) = 0\nGO\nREVERT\nGO<\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"847\" height=\"473\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/10\/word-image-36.png\" alt=\"\" class=\"wp-image-88733\"\/><\/figure>\n\n\n\n<p>As you can see, a user could write a query in a loop to test a range of values and infer that a value exists. It is limited information, but in some scenarios, this could be a security problem.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-minimize-the-risk\">How to minimize the risk<\/h2>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Consider using the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/blogs\/sql-server-rls-setup\/\">row-level security feature<\/a> to minimize the information exposed to the attacker.<br><br><\/li>\n\n\n\n<li>View-based level security should not be used as an isolated measure to fully secure sensitive data from users running ad-hoc queries on the database. It is appropriate for preventing sensitive data exposure but will not protect against malicious users trying to infer or reveal the underlying data.<br><br><\/li>\n\n\n\n<li>Any SQL Server or associated application providing too much information in error messages on the screen or printout risks compromising the data and security of the system. The structure and content of error messages need to be carefully considered by the organization and development team.<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Databases can inadvertently provide a wealth of information to an attacker through improperly handled error messages. In addition to sensitive business or personal information, database errors can provide hostnames, IP addresses, user names, and other system information not required for end-user troubleshooting but very useful to someone targeting the system.<br><br><\/li>\n<\/ul>\n<\/div><\/li>\n\n\n\n<li>Detailed error messages must be visible only to those who are authorized to view them. General users must receive only generalized acknowledgments that errors have occurred. These generalized messages must appear only when relevant to the user&#8217;s task.<br><br><\/li>\n\n\n\n<li>Configure audit logging, tracing or custom code in the database or application to record detailed error messages generated by SQL Server for review by authorized personnel.<br><br><\/li>\n\n\n\n<li>Consider enabling trace flag 3625 to mask certain system-level error information returned to non-administrative users.<br><br><\/li>\n\n\n\n<li>Inspect application source code, which will require collaboration with the application developers. It is recognized that, in many cases, the database administrator (DBA) is organizationally separate from the application developers and may have limited, if any, access to source code. Nevertheless, protections of this type are so important to the secure operation of databases that they must not be ignored. At a minimum, the DBA must attempt to obtain assurances from the development organization that this issue has been addressed and must document what has been discovered.<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-final-thoughts\">Final thoughts<\/h2>\n\n\n\n<p>It is important to understand that this approach to secure data using <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/tracking-underlying-object-changes-in-view\/\">row level views<\/a> is not very safe. Any arbitrary T-SQL and access to all views (including system views) should be limited to only specific users.<\/p>\n\n\n\n<p>One other thing I like to do in my environments is to get rid of any permissions granted to the &#8220;public&#8221; role. You know, by default, SQL Server system objects have permissions granted to public. That means any login will automatically have access to those tables. But do they really need it? I don\u2019t think so. Of course, you\u2019ll have to test it to confirm, but many applications never use system objects. You may want to remove public access to all system objects. Following is a link with a Microsoft article that will help you with that: <a href=\"https:\/\/techcommunity.microsoft.com\/t5\/sql-server\/remove-public-and-guest-permissions\/ba-p\/383594\">https:\/\/techcommunity.microsoft.com\/t5\/sql-server\/remove-public-and-guest-permissions\/ba-p\/383594<\/a><\/p>\n\n\n\n<p>Sensitive environments require special attention to many things, including application users that may look harmless. Remember, most attacks come from inside; that means any user that already has access to the system should be limited to do only what is intended to do and nothing else. Deny everything and grant access as you go (developers will hate me for that \ud83d\ude0a).<\/p>\n\n\n\n<p>It would be nice if there were an option on SQL Server to limit the information returned in an error message. In my opinion, this is the primary security issue here which has been exploited for many years with SQL Injection. Something like TF3625 \u201cLimits the amount of information returned to users who are not members of the sysadmin fixed server role, by masking the parameters of some error messages using &#8216;******&#8217;. This can help prevent disclosure of sensitive information.\u201d.<\/p>\n\n\n\n<p>Am I too cautious? What do you think?<\/p>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: Exploring errors to reveal unauthorized information<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. Can SQL Server view-based row filtering be bypassed by users?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes. View-based row filtering prevents users from selecting hidden rows, but it does not prevent them from learning about those rows through indirect means. Specifically: (1) Attempting to INSERT a row that would violate a unique constraint involving a hidden row returns an error that reveals the conflicting value. (2) Attempting to UPDATE a hidden row through the view returns an error revealing the row&#8217;s state. (3) Error messages can expose column values that the user is not supposed to see. SQL Server&#8217;s built-in Row Level Security feature (SQL Server 2016+) resolves these vulnerabilities by blocking inference at the engine level.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. Is SQL Server Row Level Security (RLS) more secure than view-based filtering?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes, for row-level access control. RLS is implemented at the table level in the storage engine &#8211; filter predicates apply before any query execution, preventing inference attacks through error messages or timing channels. RLS also applies to INSERT\/UPDATE\/DELETE operations (BLOCK predicates), not just SELECT. The only meaningful security limitation in RLS itself is side-channel attacks on masked columns &#8211; covered in Part 3 of the DDM series. For row visibility control, RLS is significantly more secure than view-based approaches.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. What is the risk of using SQL Server views for data security?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Views as a security boundary are appropriate for hiding columns (column-level access control) but are unreliable for row-level access control. Risks include: information disclosure through error messages (as demonstrated in this article); views that become stale when underlying tables change (see the Tracking Underlying Object Changes in Views article); and views that can be bypassed by users with access to the underlying tables (if table permissions are misconfigured). For row-level control, use SQL Server RLS. For column-level control, views remain a valid and lightweight approach.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. What SQL Server version introduced native Row Level Security?<\/h3>\n            <div class=\"faq-answer\">\n                <p>SQL Server 2016 introduced Row Level Security (RLS) as a built-in feature. It is also available in Azure SQL Database and Azure SQL Managed Instance. For SQL Server 2014 and earlier, where RLS is not available, the view-based approach with awareness of its limitations (as documented in this article) is the fallback, supplemented by application-layer access control.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>View-based row-level security in SQL Server leaks information through error messages &#8211; a user who can&#8217;t see a row can infer it exists by attempting to modify it and reading the error. Learn why and how to remediate with proper SQL Server RLS.&hellip;<\/p>\n","protected":false},"author":65554,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143514,143527],"tags":[5134],"coauthors":[6809],"class_list":["post-88723","post","type-post","status-publish","format-standard","hentry","category-data-privacy-and-protection","category-database-administration-sql-server","tag-sql-prompt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/88723","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/65554"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=88723"}],"version-history":[{"count":9,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/88723\/revisions"}],"predecessor-version":[{"id":110083,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/88723\/revisions\/110083"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=88723"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=88723"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=88723"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=88723"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}