<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>topicincidentrecovery &amp;mdash; heyimusa</title>
    <link>https://heyimusa.blog/tag:topicincidentrecovery</link>
    <description></description>
    <pubDate>Tue, 01 Sep 2026 07:24:49 +0000</pubDate>
    <item>
      <title>A vendor remediation is not account recovery: lessons from CosmosEscape</title>
      <link>https://heyimusa.blog/cosmosescape-vendor-remediation-not-account-recovery</link>
      <description>&lt;![CDATA[  A cloud provider can remove a platform-wide attack path and still leave every customer with work to prove that their own recovery is complete.&#xA;&#xA;On July 30, 2026, Wiz published CosmosEscape, a report about a critical vulnerability chain in Azure Cosmos DB&#39;s Gremlin API. The report says the chain could have produced read and write access across Cosmos DB accounts, including accounts isolated from the public network. Microsoft says it remediated the issue, found no evidence of unauthorized activity beyond the researchers&#39; testing, and says no customer action is required.&#xA;&#xA;That is good news. It is also not the same thing as an account owner having evidence that their Cosmos DB estate is ready for the next credential or isolation failure.&#xA;&#xA;!--more--&#xA;&#xA;This article is about the operational work that remains useful after a cloud-service incident is fixed: separating vendor remediation from local recovery evidence, reducing the power of long-lived account keys, and making data-plane and control-plane activity reviewable. It does not reproduce CosmosEscape, interact with Azure, send Gremlin queries, or publish exploit material.&#xA;&#xA;What CosmosEscape was, at the level operators need&#xA;&#xA;According to Wiz, the entry point was the Apache Gremlin API in Azure Cosmos DB. Its researchers describe finding a way to escape the restrictions around query execution while running queries against a database they controlled. The chain allegedly reached a multi-tenant internal query service, then credentials available to that service, then a platform-wide signing capability Wiz calls the &#34;Cosmos Master Key.&#34;&#xA;&#xA;Wiz says that key could retrieve an account&#39;s primary key across tenants, regions, and API families. It also says the same capability reached an internal configuration store containing account metadata such as account names, tenant and subscription identifiers, network settings, and tags. In the reported chain, the configuration store made targeting possible and the account-key path made access possible.&#xA;&#xA;That is the part worth sitting with: a tenant-controlled query reached a service boundary that could resolve another tenant&#39;s account credentials. The report is not mainly a story about graph queries. It is a story about privileged service-to-service authority sitting too close to tenant-controlled execution.&#xA;&#xA;Microsoft&#39;s statement, published in the Wiz report, says it investigated access logs and found no unauthorized activity outside the researchers&#39; testing and no customer data access. It says Microsoft blocked the reported Gremlin entry point within 48 hours, performed penetration testing for similar vectors, then completed additional hardening around service-to-service authentication, network protections, monitoring, and detection.&#xA;&#xA;Those statements should be quoted accurately:&#xA;&#xA;Microsoft says the issue is fully remediated and no customer action is required for this incident.&#xA;Wiz says Microsoft eliminated the reported Cosmos Master Key and rolled out a long-term architectural fix across regions before public disclosure.&#xA;Neither statement means a customer should invent evidence of compromise, rotate every key blindly, or treat an unrelated 401 as proof of exploitation.&#xA;&#xA;The responsible response is narrower: record the vendor statement, decide what local assurance is still appropriate for the account&#39;s risk, and avoid claiming that a platform fix proves every customer environment is recovered.&#xA;&#xA;Why private networking was not the whole boundary&#xA;&#xA;Private endpoints and firewall rules are still worth deploying. Microsoft recommends restricting Cosmos DB access to a virtual network, disabling public network access, and using private endpoints for connecting services. Those controls reduce ordinary internet exposure and narrow which network paths can reach an account.&#xA;&#xA;CosmosEscape is a reminder that network isolation is not a complete answer when an internal service that enforces or brokers that isolation is itself compromised. Wiz reports that the affected query service could reach private and network-isolated accounts, and that the configuration store held network settings. If that chain is accurate, the control plane and the enforcement point become part of the threat boundary.&#xA;&#xA;This does not make private endpoints useless. It changes the claim we should make about them.&#xA;&#xA;Private endpoint only&#xA;  -  reduces direct client reachability&#xA;  -  does not prove every provider-internal path is unable to reach the account&#xA;&#xA;Data-plane RBAC + managed identity&#xA;  -  removes or reduces application reliance on a shared account key&#xA;  -  does not replace provider-side tenant isolation&#xA;&#xA;Provider remediation&#xA;  -  removes the reported platform attack path&#xA;  -  does not create local audit evidence, rotation records, or restore tests&#xA;&#xA;A mature design uses more than one of those controls. It also records what each control cannot prove.&#xA;&#xA;The shared account key problem&#xA;&#xA;The most useful customer-side lesson is not &#34;rotate keys because CosmosEscape happened.&#34; Microsoft says customers do not need to take incident action. The lesson is that account keys have a large authorization surface and are hard to reason about once copied into applications, jobs, CI systems, vaults, and third-party integrations.&#xA;&#xA;Microsoft&#39;s Cosmos DB security guidance recommends managed identities, control-plane RBAC, data-plane RBAC, separate identities for control-plane and data-plane work, and regular rotation of primary and secondary keys when key-based authentication remains in use. The guidance also recommends moving toward Microsoft Entra ID authentication rather than embedding credentials in code.&#xA;&#xA;The account-key rotation documentation makes the operational cost explicit. Primary and secondary keys are designed for an overlap period, but regeneration can take from one minute to multiple hours. An application needs to use one key consistently before the other can be validated and the old key regenerated. In June 2026, Microsoft documented a preview feature that reports when account keys were last used before rotation or before local authentication is disabled. Microsoft also labels that preview as unsuitable for production workloads under an SLA.&#xA;&#xA;That last detail matters. Key-usage metadata can support a migration decision. It should not become the only thing that proves an application has stopped using keys.&#xA;&#xA;A reasonable target state is:&#xA;&#xA;| Capability | Transitional state | Preferred state |&#xA;| --- | --- | --- |&#xA;| Application data access | Primary or secondary account key from a secret store | Managed identity with a narrowly scoped data-plane RBAC role |&#xA;| Administrative changes | SDK operations through an account key | Azure RBAC through a dedicated control-plane identity |&#xA;| Network path | Public endpoint with allowlists | Public network disabled and private endpoints used where the workload permits |&#xA;| Evidence | Ad hoc portal screenshots | Diagnostic logs, deployment records, and tested runbooks |&#xA;| Recovery | &#34;the vendor fixed it&#34; | Documented account review, tested restore, and an explicit decision about keys |&#xA;&#xA;There will be exceptions. Legacy SDKs, API compatibility constraints, migration windows, and partner products can keep keys in service longer than anyone wants. The point is to make that an explicit temporary state with an owner and a measurable exit path.&#xA;&#xA;Do not confuse key rotation with a universal incident requirement&#xA;&#xA;There are two bad reactions to a public cloud vulnerability.&#xA;&#xA;The first is doing nothing because the vendor says the service is fixed. The second is rotating every credential across every account without knowing which applications use which key, then calling the resulting outage &#34;security.&#34; Both are avoidance.&#xA;&#xA;For CosmosEscape specifically, Microsoft&#39;s published position is that no customer action is required. An organization should not contradict that statement with a blanket claim that every Cosmos DB key is known compromised. There is no public evidence for that claim, and Microsoft says its review found no unauthorized activity beyond research testing.&#xA;&#xA;But a high-assurance organization can still choose a post-incident review. The review should ask questions that have useful answers even if the incident never touched the account:&#xA;&#xA;Which Cosmos DB accounts use Gremlin, and which accounts have a business reason to retain that API?&#xA;Which workloads still authenticate with account keys rather than Entra ID and data-plane RBAC?&#xA;Do primary and secondary keys have named consumers, an owner, and a documented rotation procedure?&#xA;Is public network access disabled where the service dependencies allow it?&#xA;Can the team retrieve data-plane and control-plane evidence for a defined investigation window?&#xA;Has the restore path been tested, not merely configured?&#xA;&#xA;Those are posture questions, not indicators of compromise.&#xA;&#xA;Evidence is different on the data plane and the control plane&#xA;&#xA;Cosmos DB has two operational surfaces that teams often log separately, if they log them at all.&#xA;&#xA;The data plane is where applications read, write, delete, and query data. Microsoft documents Cosmos DB resource logs as per-request data-plane logs. Diagnostic settings can send them to Log Analytics, storage, Event Hubs, or another destination. Microsoft also warns that enabling full-text query logging reveals deobfuscated query text and can increase cost; it recommends disabling that troubleshooting feature afterward.&#xA;&#xA;The control plane is where an account&#39;s configuration changes: regions, throughput, failover, virtual networks, and other resource settings. Microsoft documents control-plane diagnostic logging as start and complete events. It specifically gives firewall-rule changes and region changes as cases where alerts are useful.&#xA;&#xA;These logs answer different questions.&#xA;&#xA;| Question | Better evidence |&#xA;| --- | --- |&#xA;| Did a client read, write, delete, or query data? | Data-plane diagnostic logs |&#xA;| Did someone change a network rule, add a region, or alter account configuration? | Control-plane diagnostic logs and Azure Activity Log |&#xA;| Did the service still work after key migration? | Application metrics, synthetic transactions, and deployment evidence |&#xA;| Can we restore a database or container to a useful state? | A timed restore exercise with documented result |&#xA;&#xA;There is an important sequencing rule in Microsoft&#39;s control-plane audit guidance. Before auditing control-plane operations, it recommends disabling key-based metadata write access through disableKeyBasedMetadataWriteAccess. Once enabled, SDK clients using account keys cannot make metadata-changing calls; those changes need an identity with appropriate Azure role permissions through Azure Resource Manager, CLI, PowerShell, or the portal.&#xA;&#xA;That is a security improvement only after dependency discovery. Turning it on without inventory can break workflows that create containers, update throughput, or change resources through an SDK. The correct change is staged: observe, identify dependencies, migrate them, validate, then enforce.&#xA;&#xA;I tested the recovery distinction without touching Azure&#xA;&#xA;I built a small synthetic checker to make one narrow point: a vendor remediation flag does not create account-level recovery evidence.&#xA;&#xA;The checker contains no Azure SDK, no credentials, no network client, no Gremlin parser, and no exploitation logic. It evaluates two invented account records:&#xA;&#xA;platform-remediated-account-without-local-evidence&#xA;account-recovery-evidence-complete&#xA;&#xA;The first record confirms vendor remediation but has no documented investigation, no account-key usage review, no primary or secondary key-rotation evidence, no data-plane or control-plane logs, no tested restore, and no evidence of public-network reduction, local-auth disablement, or data-plane RBAC.&#xA;&#xA;The second record has the same vendor-remediation confirmation plus all of those local controls and evidence markers.&#xA;&#xA;I ran the checker in a disposable Docker container with no network, a read-only root filesystem, a 16 MiB noexec temporary filesystem, all Linux capabilities dropped, no-new-privileges, UID 65532, one CPU, 128 MiB memory, and a PID limit of 64.&#xA;&#xA;The result for the first synthetic record was:&#xA;&#xA;{&#xA;  &#34;case&#34;: &#34;platform-remediated-account-without-local-evidence&#34;,&#xA;  &#34;platformremediationconfirmed&#34;: true,&#xA;  &#34;status&#34;: &#34;vendorremediationnotaccountrecovery&#34;,&#xA;  &#34;missingrecoveryevidence&#34;: [&#xA;    &#34;documented account-scoped investigation&#34;,&#xA;    &#34;account-key usage review&#34;,&#xA;    &#34;primary key rotation evidence&#34;,&#xA;    &#34;secondary key rotation evidence&#34;,&#xA;    &#34;data-plane diagnostic logs&#34;,&#xA;    &#34;control-plane diagnostic logs&#34;,&#xA;    &#34;tested restore evidence&#34;&#xA;  ]&#xA;}&#xA;&#xA;The complete synthetic record returned:&#xA;&#xA;{&#xA;  &#34;case&#34;: &#34;account-recovery-evidence-complete&#34;,&#xA;  &#34;platformremediationconfirmed&#34;: true,&#xA;  &#34;status&#34;: &#34;recoveryevidencecomplete&#34;,&#xA;  &#34;missingrecoveryevidence&#34;: [],&#xA;  &#34;futureblastradiushardeninggaps&#34;: []&#xA;}&#xA;&#xA;This is not a scanner. It cannot tell whether a real Cosmos DB account was exposed, whether a key was used by an attacker, or whether Microsoft telemetry is complete. It is a guard against a weaker claim: that a vendor remediation announcement alone is proof of a customer&#39;s recovery state.&#xA;&#xA;A bounded review runbook&#xA;&#xA;If I were responsible for a Cosmos DB estate after this disclosure, I would open a time-boxed review ticket rather than an uncontrolled incident.&#xA;&#xA;1. Record the vendor position and scope the estate&#xA;&#xA;Record the Wiz disclosure date, Microsoft&#39;s statement that no customer action is required, and the fact that the reported entry point involved Gremlin. Inventory accounts by API type, environment, data classification, public-network setting, and authentication mode. Do not put account keys, connection strings, tenant identifiers, or customer data into the ticket.&#xA;&#xA;2. Preserve the evidence path before changing it&#xA;&#xA;Confirm whether data-plane and control-plane diagnostics are enabled, where they go, their retention period, and who can query them. If logs need to be enabled, capture the activation timestamp. Evidence does not exist retroactively just because a setting is enabled today.&#xA;&#xA;3. Review authentication deliberately&#xA;&#xA;For every account still using keys, name the primary and secondary key consumers. Check whether the application can move to managed identity and data-plane RBAC. If rotation is chosen, use the dual-key sequence and validate the replacement key before regenerating the old one. Treat key-usage metadata as supporting evidence, not the only evidence.&#xA;&#xA;4. Reduce future blast radius without breaking the workload&#xA;&#xA;Plan private endpoints and public-network disablement where feasible. Separate identities for data access from identities that alter account configuration. Identify SDK metadata operations before enabling disableKeyBasedMetadataWriteAccess. Run the migration as a change with a rollback plan, not as a checkbox.&#xA;&#xA;5. Test recovery&#xA;&#xA;Continuous backup is useful only if the organization can restore a database or container, validate application behavior, and account for time and cost. Microsoft recommends regular restore testing. Record the objective, point-in-time selected, recovery time, validation result, and cleanup.&#xA;&#xA;6. Close with an explicit conclusion&#xA;&#xA;The conclusion should be one of these, not a vague green status:&#xA;&#xA;No local action selected: vendor statement accepted; posture review deferred with owner/date.&#xA;&#xA;Posture review complete: account inventory, logging, auth posture, and restore evidence recorded.&#xA;&#xA;Migration in progress: named key consumers and enforcement changes tracked to a dated plan.&#xA;&#xA;A decision that nothing more is needed can be correct. It should still be a decision with evidence behind it.&#xA;&#xA;What this incident changes&#xA;&#xA;CosmosEscape does not establish that Azure Cosmos DB customers were compromised. Microsoft&#39;s published position is the opposite: it found no evidence of unauthorized activity or customer-data access, and it says the vulnerability has been fully remediated.&#xA;&#xA;It does establish a useful design lesson. In a multi-tenant cloud service, the most dangerous boundary is often not the public endpoint. It is the place where tenant-controlled execution can touch an internal service identity, a credential broker, or a configuration system with authority beyond that tenant.&#xA;&#xA;Customers cannot audit provider internals. They can avoid duplicating that same pattern in their own architecture. Keep tenant-controlled input away from privileged brokers. Prefer identity-scoped access over shared account keys. Treat private networking as one layer, not a certificate that no internal path exists. Keep enough data-plane and control-plane evidence to answer a real question later.&#xA;&#xA;The platform fix closes the reported path. The customer work is to make the next path smaller, easier to see, and less powerful.&#xA;&#xA;---&#xA;&#xA;Experiment notes: I created a synthetic recovery-contract checker, not an Azure client or vulnerability reproducer. It made no network calls and used no cloud credentials. It ran once in Docker using python:3.12-alpine with network disabled, read-only root filesystem, 16 MiB noexec tmpfs, UID 65532, all Linux capabilities dropped, no-new-privileges, one CPU, 128 MiB memory, and PID limit 64.&#xA;&#xA;Sources: Wiz: CosmosEscape · Microsoft Learn: Secure your Cosmos DB account · Microsoft Learn: Rotate keys · Microsoft Learn: data-plane diagnostic logs · Microsoft Learn: control-plane audit logs&#xA;&#xA;!-- taxonomy: #topiccloudsecurity #topicincidentrecovery --&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<blockquote><p>A cloud provider can remove a platform-wide attack path and still leave every customer with work to prove that their own recovery is complete.</p></blockquote>

<p>On July 30, 2026, Wiz published <a href="https://www.wiz.io/blog/cosmosescape-taking-over-every-database-in-azure-cosmos-db">CosmosEscape</a>, a report about a critical vulnerability chain in Azure Cosmos DB&#39;s Gremlin API. The report says the chain could have produced read and write access across Cosmos DB accounts, including accounts isolated from the public network. Microsoft says it remediated the issue, found no evidence of unauthorized activity beyond the researchers&#39; testing, and says no customer action is required.</p>

<p>That is good news. It is also not the same thing as an account owner having evidence that their Cosmos DB estate is ready for the next credential or isolation failure.</p>



<p>This article is about the operational work that remains useful after a cloud-service incident is fixed: separating vendor remediation from local recovery evidence, reducing the power of long-lived account keys, and making data-plane and control-plane activity reviewable. It does <strong>not</strong> reproduce CosmosEscape, interact with Azure, send Gremlin queries, or publish exploit material.</p>

<h2 id="what-cosmosescape-was-at-the-level-operators-need">What CosmosEscape was, at the level operators need</h2>

<p>According to Wiz, the entry point was the Apache Gremlin API in Azure Cosmos DB. Its researchers describe finding a way to escape the restrictions around query execution while running queries against a database they controlled. The chain allegedly reached a multi-tenant internal query service, then credentials available to that service, then a platform-wide signing capability Wiz calls the “Cosmos Master Key.”</p>

<p>Wiz says that key could retrieve an account&#39;s primary key across tenants, regions, and API families. It also says the same capability reached an internal configuration store containing account metadata such as account names, tenant and subscription identifiers, network settings, and tags. In the reported chain, the configuration store made targeting possible and the account-key path made access possible.</p>

<p>That is the part worth sitting with: a tenant-controlled query reached a service boundary that could resolve another tenant&#39;s account credentials. The report is not mainly a story about graph queries. It is a story about privileged service-to-service authority sitting too close to tenant-controlled execution.</p>

<p>Microsoft&#39;s statement, published in the Wiz report, says it investigated access logs and found no unauthorized activity outside the researchers&#39; testing and no customer data access. It says Microsoft blocked the reported Gremlin entry point within 48 hours, performed penetration testing for similar vectors, then completed additional hardening around service-to-service authentication, network protections, monitoring, and detection.</p>

<p>Those statements should be quoted accurately:</p>
<ul><li>Microsoft says the issue is fully remediated and <strong>no customer action is required</strong> for this incident.</li>
<li>Wiz says Microsoft eliminated the reported Cosmos Master Key and rolled out a long-term architectural fix across regions before public disclosure.</li>
<li>Neither statement means a customer should invent evidence of compromise, rotate every key blindly, or treat an unrelated 401 as proof of exploitation.</li></ul>

<p>The responsible response is narrower: record the vendor statement, decide what local assurance is still appropriate for the account&#39;s risk, and avoid claiming that a platform fix proves every customer environment is recovered.</p>

<h2 id="why-private-networking-was-not-the-whole-boundary">Why private networking was not the whole boundary</h2>

<p>Private endpoints and firewall rules are still worth deploying. Microsoft recommends restricting Cosmos DB access to a virtual network, disabling public network access, and using private endpoints for connecting services. Those controls reduce ordinary internet exposure and narrow which network paths can reach an account.</p>

<p>CosmosEscape is a reminder that network isolation is not a complete answer when an internal service that enforces or brokers that isolation is itself compromised. Wiz reports that the affected query service could reach private and network-isolated accounts, and that the configuration store held network settings. If that chain is accurate, the control plane and the enforcement point become part of the threat boundary.</p>

<p>This does not make private endpoints useless. It changes the claim we should make about them.</p>

<pre><code class="language-text">Private endpoint only
  -&gt; reduces direct client reachability
  -&gt; does not prove every provider-internal path is unable to reach the account

Data-plane RBAC + managed identity
  -&gt; removes or reduces application reliance on a shared account key
  -&gt; does not replace provider-side tenant isolation

Provider remediation
  -&gt; removes the reported platform attack path
  -&gt; does not create local audit evidence, rotation records, or restore tests
</code></pre>

<p>A mature design uses more than one of those controls. It also records what each control cannot prove.</p>

<h2 id="the-shared-account-key-problem">The shared account key problem</h2>

<p>The most useful customer-side lesson is not “rotate keys because CosmosEscape happened.” Microsoft says customers do not need to take incident action. The lesson is that account keys have a large authorization surface and are hard to reason about once copied into applications, jobs, CI systems, vaults, and third-party integrations.</p>

<p>Microsoft&#39;s Cosmos DB security guidance recommends managed identities, control-plane RBAC, data-plane RBAC, separate identities for control-plane and data-plane work, and regular rotation of primary and secondary keys when key-based authentication remains in use. The guidance also recommends moving toward Microsoft Entra ID authentication rather than embedding credentials in code.</p>

<p>The account-key rotation documentation makes the operational cost explicit. Primary and secondary keys are designed for an overlap period, but regeneration can take from one minute to multiple hours. An application needs to use one key consistently before the other can be validated and the old key regenerated. In June 2026, Microsoft documented a preview feature that reports when account keys were last used before rotation or before local authentication is disabled. Microsoft also labels that preview as unsuitable for production workloads under an SLA.</p>

<p>That last detail matters. Key-usage metadata can support a migration decision. It should not become the only thing that proves an application has stopped using keys.</p>

<p>A reasonable target state is:</p>

<table>
<thead>
<tr>
<th>Capability</th>
<th>Transitional state</th>
<th>Preferred state</th>
</tr>
</thead>

<tbody>
<tr>
<td>Application data access</td>
<td>Primary or secondary account key from a secret store</td>
<td>Managed identity with a narrowly scoped data-plane RBAC role</td>
</tr>

<tr>
<td>Administrative changes</td>
<td>SDK operations through an account key</td>
<td>Azure RBAC through a dedicated control-plane identity</td>
</tr>

<tr>
<td>Network path</td>
<td>Public endpoint with allowlists</td>
<td>Public network disabled and private endpoints used where the workload permits</td>
</tr>

<tr>
<td>Evidence</td>
<td>Ad hoc portal screenshots</td>
<td>Diagnostic logs, deployment records, and tested runbooks</td>
</tr>

<tr>
<td>Recovery</td>
<td>“the vendor fixed it”</td>
<td>Documented account review, tested restore, and an explicit decision about keys</td>
</tr>
</tbody>
</table>

<p>There will be exceptions. Legacy SDKs, API compatibility constraints, migration windows, and partner products can keep keys in service longer than anyone wants. The point is to make that an explicit temporary state with an owner and a measurable exit path.</p>

<h2 id="do-not-confuse-key-rotation-with-a-universal-incident-requirement">Do not confuse key rotation with a universal incident requirement</h2>

<p>There are two bad reactions to a public cloud vulnerability.</p>

<p>The first is doing nothing because the vendor says the service is fixed. The second is rotating every credential across every account without knowing which applications use which key, then calling the resulting outage “security.” Both are avoidance.</p>

<p>For CosmosEscape specifically, Microsoft&#39;s published position is that no customer action is required. An organization should not contradict that statement with a blanket claim that every Cosmos DB key is known compromised. There is no public evidence for that claim, and Microsoft says its review found no unauthorized activity beyond research testing.</p>

<p>But a high-assurance organization can still choose a post-incident review. The review should ask questions that have useful answers even if the incident never touched the account:</p>
<ol><li>Which Cosmos DB accounts use Gremlin, and which accounts have a business reason to retain that API?</li>
<li>Which workloads still authenticate with account keys rather than Entra ID and data-plane RBAC?</li>
<li>Do primary and secondary keys have named consumers, an owner, and a documented rotation procedure?</li>
<li>Is public network access disabled where the service dependencies allow it?</li>
<li>Can the team retrieve data-plane and control-plane evidence for a defined investigation window?</li>
<li>Has the restore path been tested, not merely configured?</li></ol>

<p>Those are posture questions, not indicators of compromise.</p>

<h2 id="evidence-is-different-on-the-data-plane-and-the-control-plane">Evidence is different on the data plane and the control plane</h2>

<p>Cosmos DB has two operational surfaces that teams often log separately, if they log them at all.</p>

<p>The data plane is where applications read, write, delete, and query data. Microsoft documents Cosmos DB resource logs as per-request data-plane logs. Diagnostic settings can send them to Log Analytics, storage, Event Hubs, or another destination. Microsoft also warns that enabling full-text query logging reveals deobfuscated query text and can increase cost; it recommends disabling that troubleshooting feature afterward.</p>

<p>The control plane is where an account&#39;s configuration changes: regions, throughput, failover, virtual networks, and other resource settings. Microsoft documents control-plane diagnostic logging as start and complete events. It specifically gives firewall-rule changes and region changes as cases where alerts are useful.</p>

<p>These logs answer different questions.</p>

<table>
<thead>
<tr>
<th>Question</th>
<th>Better evidence</th>
</tr>
</thead>

<tbody>
<tr>
<td>Did a client read, write, delete, or query data?</td>
<td>Data-plane diagnostic logs</td>
</tr>

<tr>
<td>Did someone change a network rule, add a region, or alter account configuration?</td>
<td>Control-plane diagnostic logs and Azure Activity Log</td>
</tr>

<tr>
<td>Did the service still work after key migration?</td>
<td>Application metrics, synthetic transactions, and deployment evidence</td>
</tr>

<tr>
<td>Can we restore a database or container to a useful state?</td>
<td>A timed restore exercise with documented result</td>
</tr>
</tbody>
</table>

<p>There is an important sequencing rule in Microsoft&#39;s control-plane audit guidance. Before auditing control-plane operations, it recommends disabling key-based metadata write access through <code>disableKeyBasedMetadataWriteAccess</code>. Once enabled, SDK clients using account keys cannot make metadata-changing calls; those changes need an identity with appropriate Azure role permissions through Azure Resource Manager, CLI, PowerShell, or the portal.</p>

<p>That is a security improvement only after dependency discovery. Turning it on without inventory can break workflows that create containers, update throughput, or change resources through an SDK. The correct change is staged: observe, identify dependencies, migrate them, validate, then enforce.</p>

<h2 id="i-tested-the-recovery-distinction-without-touching-azure">I tested the recovery distinction without touching Azure</h2>

<p>I built a small synthetic checker to make one narrow point: a vendor remediation flag does not create account-level recovery evidence.</p>

<p>The checker contains no Azure SDK, no credentials, no network client, no Gremlin parser, and no exploitation logic. It evaluates two invented account records:</p>
<ul><li><code>platform-remediated-account-without-local-evidence</code></li>
<li><code>account-recovery-evidence-complete</code></li></ul>

<p>The first record confirms vendor remediation but has no documented investigation, no account-key usage review, no primary or secondary key-rotation evidence, no data-plane or control-plane logs, no tested restore, and no evidence of public-network reduction, local-auth disablement, or data-plane RBAC.</p>

<p>The second record has the same vendor-remediation confirmation plus all of those local controls and evidence markers.</p>

<p>I ran the checker in a disposable Docker container with no network, a read-only root filesystem, a 16 MiB <code>noexec</code> temporary filesystem, all Linux capabilities dropped, <code>no-new-privileges</code>, UID <code>65532</code>, one CPU, 128 MiB memory, and a PID limit of 64.</p>

<p>The result for the first synthetic record was:</p>

<pre><code class="language-json">{
  &#34;case&#34;: &#34;platform-remediated-account-without-local-evidence&#34;,
  &#34;platform_remediation_confirmed&#34;: true,
  &#34;status&#34;: &#34;vendor_remediation_not_account_recovery&#34;,
  &#34;missing_recovery_evidence&#34;: [
    &#34;documented account-scoped investigation&#34;,
    &#34;account-key usage review&#34;,
    &#34;primary key rotation evidence&#34;,
    &#34;secondary key rotation evidence&#34;,
    &#34;data-plane diagnostic logs&#34;,
    &#34;control-plane diagnostic logs&#34;,
    &#34;tested restore evidence&#34;
  ]
}
</code></pre>

<p>The complete synthetic record returned:</p>

<pre><code class="language-json">{
  &#34;case&#34;: &#34;account-recovery-evidence-complete&#34;,
  &#34;platform_remediation_confirmed&#34;: true,
  &#34;status&#34;: &#34;recovery_evidence_complete&#34;,
  &#34;missing_recovery_evidence&#34;: [],
  &#34;future_blast_radius_hardening_gaps&#34;: []
}
</code></pre>

<p>This is not a scanner. It cannot tell whether a real Cosmos DB account was exposed, whether a key was used by an attacker, or whether Microsoft telemetry is complete. It is a guard against a weaker claim: that a vendor remediation announcement alone is proof of a customer&#39;s recovery state.</p>

<h2 id="a-bounded-review-runbook">A bounded review runbook</h2>

<p>If I were responsible for a Cosmos DB estate after this disclosure, I would open a time-boxed review ticket rather than an uncontrolled incident.</p>

<h3 id="1-record-the-vendor-position-and-scope-the-estate">1. Record the vendor position and scope the estate</h3>

<p>Record the Wiz disclosure date, Microsoft&#39;s statement that no customer action is required, and the fact that the reported entry point involved Gremlin. Inventory accounts by API type, environment, data classification, public-network setting, and authentication mode. Do not put account keys, connection strings, tenant identifiers, or customer data into the ticket.</p>

<h3 id="2-preserve-the-evidence-path-before-changing-it">2. Preserve the evidence path before changing it</h3>

<p>Confirm whether data-plane and control-plane diagnostics are enabled, where they go, their retention period, and who can query them. If logs need to be enabled, capture the activation timestamp. Evidence does not exist retroactively just because a setting is enabled today.</p>

<h3 id="3-review-authentication-deliberately">3. Review authentication deliberately</h3>

<p>For every account still using keys, name the primary and secondary key consumers. Check whether the application can move to managed identity and data-plane RBAC. If rotation is chosen, use the dual-key sequence and validate the replacement key before regenerating the old one. Treat key-usage metadata as supporting evidence, not the only evidence.</p>

<h3 id="4-reduce-future-blast-radius-without-breaking-the-workload">4. Reduce future blast radius without breaking the workload</h3>

<p>Plan private endpoints and public-network disablement where feasible. Separate identities for data access from identities that alter account configuration. Identify SDK metadata operations before enabling <code>disableKeyBasedMetadataWriteAccess</code>. Run the migration as a change with a rollback plan, not as a checkbox.</p>

<h3 id="5-test-recovery">5. Test recovery</h3>

<p>Continuous backup is useful only if the organization can restore a database or container, validate application behavior, and account for time and cost. Microsoft recommends regular restore testing. Record the objective, point-in-time selected, recovery time, validation result, and cleanup.</p>

<h3 id="6-close-with-an-explicit-conclusion">6. Close with an explicit conclusion</h3>

<p>The conclusion should be one of these, not a vague green status:</p>

<pre><code class="language-text">No local action selected: vendor statement accepted; posture review deferred with owner/date.

Posture review complete: account inventory, logging, auth posture, and restore evidence recorded.

Migration in progress: named key consumers and enforcement changes tracked to a dated plan.
</code></pre>

<p>A decision that nothing more is needed can be correct. It should still be a decision with evidence behind it.</p>

<h2 id="what-this-incident-changes">What this incident changes</h2>

<p>CosmosEscape does not establish that Azure Cosmos DB customers were compromised. Microsoft&#39;s published position is the opposite: it found no evidence of unauthorized activity or customer-data access, and it says the vulnerability has been fully remediated.</p>

<p>It does establish a useful design lesson. In a multi-tenant cloud service, the most dangerous boundary is often not the public endpoint. It is the place where tenant-controlled execution can touch an internal service identity, a credential broker, or a configuration system with authority beyond that tenant.</p>

<p>Customers cannot audit provider internals. They can avoid duplicating that same pattern in their own architecture. Keep tenant-controlled input away from privileged brokers. Prefer identity-scoped access over shared account keys. Treat private networking as one layer, not a certificate that no internal path exists. Keep enough data-plane and control-plane evidence to answer a real question later.</p>

<p>The platform fix closes the reported path. The customer work is to make the next path smaller, easier to see, and less powerful.</p>

<hr>

<p><strong>Experiment notes:</strong> I created a synthetic recovery-contract checker, not an Azure client or vulnerability reproducer. It made no network calls and used no cloud credentials. It ran once in Docker using <code>python:3.12-alpine</code> with network disabled, read-only root filesystem, 16 MiB <code>noexec</code> tmpfs, UID <code>65532</code>, all Linux capabilities dropped, <code>no-new-privileges</code>, one CPU, 128 MiB memory, and PID limit 64.</p>

<p><strong>Sources:</strong> <a href="https://www.wiz.io/blog/cosmosescape-taking-over-every-database-in-azure-cosmos-db">Wiz: CosmosEscape</a> · <a href="https://learn.microsoft.com/en-us/azure/cosmos-db/security">Microsoft Learn: Secure your Cosmos DB account</a> · <a href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-rotate-keys">Microsoft Learn: Rotate keys</a> · <a href="https://learn.microsoft.com/en-us/azure/cosmos-db/monitor-resource-logs">Microsoft Learn: data-plane diagnostic logs</a> · <a href="https://learn.microsoft.com/en-us/azure/cosmos-db/audit-control-plane-logs">Microsoft Learn: control-plane audit logs</a></p>


]]></content:encoded>
      <guid>https://heyimusa.blog/cosmosescape-vendor-remediation-not-account-recovery</guid>
      <pubDate>Thu, 30 Jul 2026 14:11:04 +0000</pubDate>
    </item>
    <item>
      <title>A Rails security patch is not a complete incident response</title>
      <link>https://heyimusa.blog/rails-security-patch-not-complete-incident-response</link>
      <description>&lt;![CDATA[  A fixed gem closes a vulnerable code path. It does not tell you whether the process secrets behind that path are still safe.&#xA;&#xA;The Rails security advisory for CVE-2026-66066 is unusually direct about what comes after the upgrade.&#xA;&#xA;!--more--&#xA;&#xA;The flaw sits in Active Storage variant processing. In the affected configuration, an unauthenticated attacker may be able to read arbitrary files available to the Rails process, including its environment. That matters because the environment is where many applications keep secretkeybase, database credentials, storage credentials, and third-party API tokens.&#xA;&#xA;The obvious response is to update activestorage. It is necessary. It is not the whole response.&#xA;&#xA;The patch answers only one question&#xA;&#xA;Rails lists fixed Active Storage releases as 7.2.3.2, 8.0.5.1, and 8.1.3.1. The advisory also says libvips must be at least 8.13 for the protection Active Storage relies on. An application is in the affected shape when it uses libvips for Active Storage image processing and accepts image uploads from untrusted users. Variant generation does not need to be a separate application feature for that shape to matter.&#xA;&#xA;That gives an operator a good patch checklist:&#xA;&#xA;Rails / Active Storage version: fixed&#xA;libvips version:                  = 8.13&#xA;untrusted image uploads:        understood&#xA;&#xA;It does not answer whether a secret was read before the fix reached production.&#xA;&#xA;Rails makes that distinction explicit. Its advisory says that upgrading does not undo an exfiltrated secret. It recommends treating every secret readable by the affected process as potentially exposed and changing it.&#xA;&#xA;That changes the incident from a dependency update into a recovery exercise.&#xA;&#xA;I tested the difference as a contract, not an exploit&#xA;&#xA;I did not reproduce the vulnerability, upload crafted files, or run Rails or libvips. The advisory intentionally withholds the attack chain until at least August 28, 2026, and there is no reason to make an exploit easier to copy here.&#xA;&#xA;Instead, I wrote a small synthetic checker that asks a narrower question: does the incident record contain evidence that the patch and the recovery work both happened?&#xA;&#xA;It evaluated two synthetic cases in a disposable Docker container. The container had no network, a read-only filesystem, a 16 MiB noexec temporary filesystem, no Linux capabilities, no-new-privileges, one CPU, 128 MiB memory, a PID limit of 64, and UID 65532.&#xA;&#xA;The first case had a fixed Rails version and libvips 8.13.0. It had no recorded credential rotation and no session invalidation evidence. The second case added evidence for five secret classes plus invalidation of sessions affected by secretkeybase rotation.&#xA;&#xA;{&#xA;  &#34;case&#34;: &#34;patch-only&#34;,&#xA;  &#34;railsfixed&#34;: true,&#xA;  &#34;libvipssafe&#34;: true,&#xA;  &#34;missingrotationevidence&#34;: [&#xA;    &#34;activestorageservicecredentials&#34;,&#xA;    &#34;databasecredentials&#34;,&#xA;    &#34;railsmasterkeyanddecryptedcredentials&#34;,&#xA;    &#34;secretkeybase&#34;,&#xA;    &#34;thirdpartytokens&#34;&#xA;  ],&#xA;  &#34;sessioninvalidationevidence&#34;: false,&#xA;  &#34;status&#34;: &#34;patchednotrecovered&#34;&#xA;}&#xA;&#xA;The fully recorded case produced a different result:&#xA;&#xA;{&#xA;  &#34;case&#34;: &#34;patched-and-recovered&#34;,&#xA;  &#34;railsfixed&#34;: true,&#xA;  &#34;libvipssafe&#34;: true,&#xA;  &#34;missingrotationevidence&#34;: [],&#xA;  &#34;sessioninvalidationevidence&#34;: true,&#xA;  &#34;status&#34;: &#34;recovered&#34;&#xA;}&#xA;&#xA;The checker does not prove that a real secret was or was not stolen. It cannot inspect an attacker, a production image pipeline, or a cloud audit trail. It does make a common operational gap visible: a successful package update is not evidence of recovery.&#xA;&#xA;Rotation has product consequences&#xA;&#xA;secretkeybase is not another API token that can quietly disappear into a vault rotation. Rails says changing it expires active sessions and affects encrypted cookies, signed cookies, signed Global IDs, and Active Storage URLs. Users will need to sign in again.&#xA;&#xA;That is a business-facing change. It deserves an owner, a support plan, and a rollback discussion before the change window begins.&#xA;&#xA;The same applies to storage credentials. If an Active Storage service can read or write object storage, rotate its credentials with the same care as a database password. A rotation that breaks uploads or background jobs without an observable error path is only a different outage.&#xA;&#xA;I would want an incident record to name at least these things:&#xA;&#xA;| Evidence | Why it matters |&#xA;| --- | --- |&#xA;| Fixed Rails and libvips versions in the deployed image | Shows the known vulnerable path is no longer present. |&#xA;| Affected upload surface and deployment inventory | Bounds which applications need recovery work. |&#xA;| Rotation record for secretkey_base, Rails credentials, storage, database, and third-party credentials | Shows the process environment was not treated as harmless after patching. |&#xA;| Session invalidation and user communication plan | Makes the authentication impact intentional rather than surprising. |&#xA;| Post-rotation smoke checks | Confirms uploads, variants, workers, storage access, and normal login still work. |&#xA;&#xA;A checklist is not a substitute for incident investigation. It is a way to stop the update step from being mistaken for the whole incident.&#xA;&#xA;The image pipeline has more authority than it looks&#xA;&#xA;The advisory is about more than image processing. The processor runs beside application secrets and is fed content from outside the trust boundary.&#xA;&#xA;That pattern appears in thumbnailers, document conversion workers, media transcoders, preview generators, and report renderers. The exact vulnerability will differ. The response question is stable: if untrusted input reached a process, what could that process read, and what evidence would show that the exposure has been closed and recovered from?&#xA;&#xA;For CVE-2026-66066, the Rails project gives a clear starting point: patch Active Storage, use a secure libvips version, and rotate secrets readable by the process. The operational work begins after the version bump.&#xA;&#xA;If you operate affected Rails applications&#xA;&#xA;Inventory applications that use Active Storage with libvips and accept untrusted image uploads. Upgrade to the fixed Rails release for the relevant branch and make sure deployed libvips is at least 8.13.&#xA;Treat secret rotation as a planned recovery change. Rotate the application secret, encrypted credentials, storage credentials, database credentials, and service tokens that the process could read. Test the login and upload impact before declaring the incident closed.&#xA;&#xA;---&#xA;&#xA;Test notes: The experiment was a synthetic JSON contract checker, not a Rails, Active Storage, libvips, or exploit test. It ran once in Docker using python:3.12-alpine, with no network, read-only root filesystem, 16 MiB noexec tmpfs, all capabilities dropped, no-new-privileges, one CPU, 128 MiB memory, PID limit 64, and UID 65532.&#xA;&#xA;Sources: Rails security advisory GHSA-xr9x-r78c-5hrm · Rails security announcement · Ethiack research note&#xA;&#xA;!-- taxonomy: #topiccloudsecurity #topicincidentrecovery --&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<blockquote><p>A fixed gem closes a vulnerable code path. It does not tell you whether the process secrets behind that path are still safe.</p></blockquote>

<p>The Rails security advisory for CVE-2026-66066 is unusually direct about what comes after the upgrade.</p>



<p>The flaw sits in Active Storage variant processing. In the affected configuration, an unauthenticated attacker may be able to read arbitrary files available to the Rails process, including its environment. That matters because the environment is where many applications keep <code>secret_key_base</code>, database credentials, storage credentials, and third-party API tokens.</p>

<p>The obvious response is to update <code>activestorage</code>. It is necessary. It is not the whole response.</p>

<h2 id="the-patch-answers-only-one-question">The patch answers only one question</h2>

<p>Rails lists fixed Active Storage releases as <code>7.2.3.2</code>, <code>8.0.5.1</code>, and <code>8.1.3.1</code>. The advisory also says libvips must be at least <code>8.13</code> for the protection Active Storage relies on. An application is in the affected shape when it uses libvips for Active Storage image processing and accepts image uploads from untrusted users. Variant generation does not need to be a separate application feature for that shape to matter.</p>

<p>That gives an operator a good patch checklist:</p>

<pre><code class="language-text">Rails / Active Storage version: fixed
libvips version:                &gt;= 8.13
untrusted image uploads:        understood
</code></pre>

<p>It does not answer whether a secret was read before the fix reached production.</p>

<p>Rails makes that distinction explicit. Its advisory says that upgrading does not undo an exfiltrated secret. It recommends treating every secret readable by the affected process as potentially exposed and changing it.</p>

<p>That changes the incident from a dependency update into a recovery exercise.</p>

<h2 id="i-tested-the-difference-as-a-contract-not-an-exploit">I tested the difference as a contract, not an exploit</h2>

<p>I did not reproduce the vulnerability, upload crafted files, or run Rails or libvips. The advisory intentionally withholds the attack chain until at least August 28, 2026, and there is no reason to make an exploit easier to copy here.</p>

<p>Instead, I wrote a small synthetic checker that asks a narrower question: does the incident record contain evidence that the patch and the recovery work both happened?</p>

<p>It evaluated two synthetic cases in a disposable Docker container. The container had no network, a read-only filesystem, a 16 MiB <code>noexec</code> temporary filesystem, no Linux capabilities, <code>no-new-privileges</code>, one CPU, 128 MiB memory, a PID limit of 64, and UID <code>65532</code>.</p>

<p>The first case had a fixed Rails version and libvips <code>8.13.0</code>. It had no recorded credential rotation and no session invalidation evidence. The second case added evidence for five secret classes plus invalidation of sessions affected by <code>secret_key_base</code> rotation.</p>

<pre><code class="language-json">{
  &#34;case&#34;: &#34;patch-only&#34;,
  &#34;rails_fixed&#34;: true,
  &#34;libvips_safe&#34;: true,
  &#34;missing_rotation_evidence&#34;: [
    &#34;active_storage_service_credentials&#34;,
    &#34;database_credentials&#34;,
    &#34;rails_master_key_and_decrypted_credentials&#34;,
    &#34;secret_key_base&#34;,
    &#34;third_party_tokens&#34;
  ],
  &#34;session_invalidation_evidence&#34;: false,
  &#34;status&#34;: &#34;patched_not_recovered&#34;
}
</code></pre>

<p>The fully recorded case produced a different result:</p>

<pre><code class="language-json">{
  &#34;case&#34;: &#34;patched-and-recovered&#34;,
  &#34;rails_fixed&#34;: true,
  &#34;libvips_safe&#34;: true,
  &#34;missing_rotation_evidence&#34;: [],
  &#34;session_invalidation_evidence&#34;: true,
  &#34;status&#34;: &#34;recovered&#34;
}
</code></pre>

<p>The checker does not prove that a real secret was or was not stolen. It cannot inspect an attacker, a production image pipeline, or a cloud audit trail. It does make a common operational gap visible: a successful package update is not evidence of recovery.</p>

<h2 id="rotation-has-product-consequences">Rotation has product consequences</h2>

<p><code>secret_key_base</code> is not another API token that can quietly disappear into a vault rotation. Rails says changing it expires active sessions and affects encrypted cookies, signed cookies, signed Global IDs, and Active Storage URLs. Users will need to sign in again.</p>

<p>That is a business-facing change. It deserves an owner, a support plan, and a rollback discussion before the change window begins.</p>

<p>The same applies to storage credentials. If an Active Storage service can read or write object storage, rotate its credentials with the same care as a database password. A rotation that breaks uploads or background jobs without an observable error path is only a different outage.</p>

<p>I would want an incident record to name at least these things:</p>

<table>
<thead>
<tr>
<th>Evidence</th>
<th>Why it matters</th>
</tr>
</thead>

<tbody>
<tr>
<td>Fixed Rails and libvips versions in the deployed image</td>
<td>Shows the known vulnerable path is no longer present.</td>
</tr>

<tr>
<td>Affected upload surface and deployment inventory</td>
<td>Bounds which applications need recovery work.</td>
</tr>

<tr>
<td>Rotation record for <code>secret_key_base</code>, Rails credentials, storage, database, and third-party credentials</td>
<td>Shows the process environment was not treated as harmless after patching.</td>
</tr>

<tr>
<td>Session invalidation and user communication plan</td>
<td>Makes the authentication impact intentional rather than surprising.</td>
</tr>

<tr>
<td>Post-rotation smoke checks</td>
<td>Confirms uploads, variants, workers, storage access, and normal login still work.</td>
</tr>
</tbody>
</table>

<p>A checklist is not a substitute for incident investigation. It is a way to stop the update step from being mistaken for the whole incident.</p>

<h2 id="the-image-pipeline-has-more-authority-than-it-looks">The image pipeline has more authority than it looks</h2>

<p>The advisory is about more than image processing. The processor runs beside application secrets and is fed content from outside the trust boundary.</p>

<p>That pattern appears in thumbnailers, document conversion workers, media transcoders, preview generators, and report renderers. The exact vulnerability will differ. The response question is stable: if untrusted input reached a process, what could that process read, and what evidence would show that the exposure has been closed and recovered from?</p>

<p>For CVE-2026-66066, the Rails project gives a clear starting point: patch Active Storage, use a secure libvips version, and rotate secrets readable by the process. The operational work begins after the version bump.</p>

<h2 id="if-you-operate-affected-rails-applications">If you operate affected Rails applications</h2>
<ol><li>Inventory applications that use Active Storage with libvips and accept untrusted image uploads. Upgrade to the fixed Rails release for the relevant branch and make sure deployed libvips is at least <code>8.13</code>.</li>
<li>Treat secret rotation as a planned recovery change. Rotate the application secret, encrypted credentials, storage credentials, database credentials, and service tokens that the process could read. Test the login and upload impact before declaring the incident closed.</li></ol>

<hr>

<p><strong>Test notes:</strong> The experiment was a synthetic JSON contract checker, not a Rails, Active Storage, libvips, or exploit test. It ran once in Docker using <code>python:3.12-alpine</code>, with no network, read-only root filesystem, 16 MiB <code>noexec</code> tmpfs, all capabilities dropped, <code>no-new-privileges</code>, one CPU, 128 MiB memory, PID limit 64, and UID <code>65532</code>.</p>

<p><strong>Sources:</strong> <a href="https://github.com/rails/rails/security/advisories/GHSA-xr9x-r78c-5hrm">Rails security advisory GHSA-xr9x-r78c-5hrm</a> · <a href="https://discuss.rubyonrails.org/t/cve-2026-66066-possible-arbitrary-file-read-and-remote-code-execution-in-active-storage-variant-processing/91432">Rails security announcement</a> · <a href="https://ethiack.com/info-hub/research/kindarails2shell-rails-rce-cve-2026-66066">Ethiack research note</a></p>


]]></content:encoded>
      <guid>https://heyimusa.blog/rails-security-patch-not-complete-incident-response</guid>
      <pubDate>Thu, 30 Jul 2026 13:24:56 +0000</pubDate>
    </item>
    <item>
      <title>A rollout needs a return address</title>
      <link>https://heyimusa.blog/a-rollout-needs-a-return-address</link>
      <description>&lt;![CDATA[A small declarative rollout and rollback experiment&#xA;&#xA;Original diagram for this note. It describes a Docker-only simulated release flow, not a production deployment or benchmark.&#xA;&#xA;I do not trust a deployment plan until I can explain what happens when the new version is the problem.&#xA;&#xA;!--more--&#xA;&#xA;That sounds obvious. It is still easy to build a delivery process around the happy path: create an image, update a manifest, watch the rollout, call it done. The awkward part starts when a release looks healthy enough to leave the pipeline but is not healthy enough to keep.&#xA;&#xA;To keep this small, I ran a toy release flow in a disposable Docker container. There was no host change, cluster, registry, or real service. The container held a desired-state file for a fictional checkout service, plus a saved copy of the known-good release.&#xA;&#xA;apply: checkout:1.4.3&#xA;healthcheck: /readyz -  FAIL (simulated)&#xA;rollback: checkout:1.4.2&#xA;healthcheck: /readyz -  OK (simulated)&#xA;finaldesiredstate: image=checkout:1.4.2 replicas=3&#xA;&#xA;The experiment is intentionally boring. That is the point. A rollback should not require somebody to reconstruct the previous state from memory while a production graph turns red.&#xA;&#xA;The release needs a return address&#xA;&#xA;A deployment is a state transition. The candidate release is only one half of that transition; the other half is the state you can return to when the candidate fails.&#xA;&#xA;In the tiny experiment, that state was just a saved file:&#xA;&#xA;image=checkout:1.4.2&#xA;replicas=3&#xA;&#xA;Real systems are less neat. There may be configuration changes, schema compatibility, feature flags, asynchronous workers, or traffic shifts. But the basic question does not change: what exact state are we restoring, and can the deployment system express it?&#xA;&#xA;If the answer is &#34;we will figure it out,&#34; the rollback plan is not really a plan.&#xA;&#xA;Rollback is not an apology button&#xA;&#xA;People often talk about rollback as if it is the opposite of deployment. It is not. It is another deployment, with the same need for identity, evidence, and verification.&#xA;&#xA;A useful rollback path has at least three properties:&#xA;&#xA;The previous artifact or desired state is identifiable.&#xA;The path to apply it is known before the incident.&#xA;The system has a signal that says the restored version is actually healthy.&#xA;&#xA;The third item gets skipped surprisingly often. Reverting an image tag is not proof that the service recovered. It only proves that the deployment controller accepted another instruction.&#xA;&#xA;Why declarative state helps&#xA;&#xA;This is where GitOps and other declarative delivery patterns earn their keep. They make the intended state visible. They also make a reversal more concrete: restore a reviewed revision, reconcile it, and watch the same health signals that justified the rollout.&#xA;&#xA;That does not make every rollback safe. Database migrations can make a simple reversal impossible. A downstream dependency may have changed underneath you. A feature flag may be the safer first lever. Declarative state is not magic; it just removes one common source of panic: having to guess which version and configuration were running before the change.&#xA;&#xA;The part I would test next&#xA;&#xA;The toy flow did not cover the hard cases. It did not include a database, traffic management, or an actual Kubernetes controller. It only checked the shape of the idea: a failed health check should lead to a named previous state, then to a second health check.&#xA;&#xA;The next useful step is to run the same exercise against a non-critical service in an isolated environment:&#xA;&#xA;deploy a known candidate;&#xA;deliberately fail a readiness condition;&#xA;reconcile the previous revision;&#xA;verify the restored service through the same route and alert signal users depend on.&#xA;&#xA;If that feels cumbersome in a test environment, it will feel worse during an incident.&#xA;&#xA;---&#xA;&#xA;Test notes: This note is based on a disposable Docker container limited to 0.25 CPU and 128 MiB memory. It simulated a declarative checkout release changing from 1.4.2 to 1.4.3, a failed readiness check, and restoration to 1.4.2. No production infrastructure, repository, cluster, registry, or database was accessed.&#xA;&#xA;Related work: Turning deployments into a repeatable platform capability&#xA;&#xA;!-- taxonomy: #topicgitopsdelivery #topicincidentrecovery --&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="/img/articles/rollbacks-need-a-place-to-land.svg" alt="A small declarative rollout and rollback experiment"></p>

<p><em>Original diagram for this note. It describes a Docker-only simulated release flow, not a production deployment or benchmark.</em></p>

<p>I do not trust a deployment plan until I can explain what happens when the new version is the problem.</p>



<p>That sounds obvious. It is still easy to build a delivery process around the happy path: create an image, update a manifest, watch the rollout, call it done. The awkward part starts when a release looks healthy enough to leave the pipeline but is not healthy enough to keep.</p>

<p>To keep this small, I ran a toy release flow in a disposable Docker container. There was no host change, cluster, registry, or real service. The container held a desired-state file for a fictional <code>checkout</code> service, plus a saved copy of the known-good release.</p>

<pre><code class="language-text">apply: checkout:1.4.3
healthcheck: /readyz -&gt; FAIL (simulated)
rollback: checkout:1.4.2
healthcheck: /readyz -&gt; OK (simulated)
final_desired_state: image=checkout:1.4.2 replicas=3
</code></pre>

<p>The experiment is intentionally boring. That is the point. A rollback should not require somebody to reconstruct the previous state from memory while a production graph turns red.</p>

<h2 id="the-release-needs-a-return-address">The release needs a return address</h2>

<p>A deployment is a state transition. The candidate release is only one half of that transition; the other half is the state you can return to when the candidate fails.</p>

<p>In the tiny experiment, that state was just a saved file:</p>

<pre><code class="language-text">image=checkout:1.4.2
replicas=3
</code></pre>

<p>Real systems are less neat. There may be configuration changes, schema compatibility, feature flags, asynchronous workers, or traffic shifts. But the basic question does not change: <strong>what exact state are we restoring, and can the deployment system express it?</strong></p>

<p>If the answer is “we will figure it out,” the rollback plan is not really a plan.</p>

<h2 id="rollback-is-not-an-apology-button">Rollback is not an apology button</h2>

<p>People often talk about rollback as if it is the opposite of deployment. It is not. It is another deployment, with the same need for identity, evidence, and verification.</p>

<p>A useful rollback path has at least three properties:</p>
<ul><li>The previous artifact or desired state is identifiable.</li>
<li>The path to apply it is known before the incident.</li>
<li>The system has a signal that says the restored version is actually healthy.</li></ul>

<p>The third item gets skipped surprisingly often. Reverting an image tag is not proof that the service recovered. It only proves that the deployment controller accepted another instruction.</p>

<h2 id="why-declarative-state-helps">Why declarative state helps</h2>

<p>This is where GitOps and other declarative delivery patterns earn their keep. They make the intended state visible. They also make a reversal more concrete: restore a reviewed revision, reconcile it, and watch the same health signals that justified the rollout.</p>

<p>That does not make every rollback safe. Database migrations can make a simple reversal impossible. A downstream dependency may have changed underneath you. A feature flag may be the safer first lever. Declarative state is not magic; it just removes one common source of panic: having to guess which version and configuration were running before the change.</p>

<h2 id="the-part-i-would-test-next">The part I would test next</h2>

<p>The toy flow did not cover the hard cases. It did not include a database, traffic management, or an actual Kubernetes controller. It only checked the shape of the idea: a failed health check should lead to a named previous state, then to a second health check.</p>

<p>The next useful step is to run the same exercise against a non-critical service in an isolated environment:</p>
<ol><li>deploy a known candidate;</li>
<li>deliberately fail a readiness condition;</li>
<li>reconcile the previous revision;</li>
<li>verify the restored service through the same route and alert signal users depend on.</li></ol>

<p>If that feels cumbersome in a test environment, it will feel worse during an incident.</p>

<hr>

<p><strong>Test notes:</strong> This note is based on a disposable Docker container limited to 0.25 CPU and 128 MiB memory. It simulated a declarative <code>checkout</code> release changing from <code>1.4.2</code> to <code>1.4.3</code>, a failed readiness check, and restoration to <code>1.4.2</code>. No production infrastructure, repository, cluster, registry, or database was accessed.</p>

<p><strong>Related work:</strong> <a href="/portfolio/work/#work">Turning deployments into a repeatable platform capability</a></p>


]]></content:encoded>
      <guid>https://heyimusa.blog/a-rollout-needs-a-return-address</guid>
      <pubDate>Sun, 19 Jul 2026 17:31:09 +0000</pubDate>
    </item>
  </channel>
</rss>