<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Leonardo Viana Leite</title>
    <description>The latest articles on DEV Community by Leonardo Viana Leite (@leooelx).</description>
    <link>https://dev.to/leooelx</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3939068%2F90c524d4-479a-409f-9e17-4a987d74a8c4.png</url>
      <title>DEV Community: Leonardo Viana Leite</title>
      <link>https://dev.to/leooelx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/leooelx"/>
    <language>en</language>
    <item>
      <title>Platform Engineering in Practice: Hardening Backstage with SRE Watchdogs, Zero-Touch RBAC, and SSRF Mitigation</title>
      <dc:creator>Leonardo Viana Leite</dc:creator>
      <pubDate>Mon, 18 May 2026 23:50:55 +0000</pubDate>
      <link>https://dev.to/leooelx/platform-engineering-in-practice-hardening-backstage-with-sre-watchdogs-zero-touch-rbac-and-ssrf-5b0</link>
      <guid>https://dev.to/leooelx/platform-engineering-in-practice-hardening-backstage-with-sre-watchdogs-zero-touch-rbac-and-ssrf-5b0</guid>
      <description>&lt;p&gt;Spotify Backstage has revolutionized Platform Engineering by centralizing the Developer Experience (DX) into Internal Developer Portals (IDPs). However, when you scale an IDP to serve thousands of developers in complex enterprise environments, concurrency issues, permission bottlenecks, and critical security vulnerabilities start to emerge.&lt;/p&gt;

&lt;p&gt;Today, I want to share three advanced architectural patterns we designed to solve these problems at their root, along with our recent Open-Source contributions to the community.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. SRE Watchdogs: Preventing "TCP Hangs" in Catalog Synchronization
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; &lt;br&gt;
When building a &lt;em&gt;Custom Entity Provider&lt;/em&gt; to sync thousands of repositories from Azure DevOps or users from MS Graph/EntraID, external APIs can often exhibit instability. If the network ingestion process stalls (TCP Hangs), it blocks the Node.js Event Loop within Backstage, causing widespread unavailability and request queuing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution (Decorator Pattern &amp;amp; Mutex):&lt;/strong&gt;&lt;br&gt;
Instead of modifying the ingestion code directly, we applied the &lt;em&gt;Decorator&lt;/em&gt; design pattern to wrap our providers with an &lt;strong&gt;SRE Watchdog&lt;/strong&gt;. This "watchdog" injects a Mutex (mutual exclusion) and a strict Timeout (e.g., 15 minutes).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SRE Watchdog Wrapper&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResilientEntityProviderWrapper&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;EntityProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntityProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EntityProviderConnection&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Safely initializes the connection&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Injects Mutex and Timeout to prevent silent Azure API hangs&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;withTimeoutAndMutex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AzureDevOpsSyncTimeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees that network bottlenecks or third-party API instability will never bring down the IDP.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Zero-Touch RBAC: From Static YAML to Dynamic Conditional Policies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;
Backstage utilizes a robust permissions framework, but the community standard frequently relies on creating static Roles within extensive YAML files. In a dynamic organization, manually maintaining the pairing between developers, squads, and software components simply does not scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution (Push-Down SQL):&lt;/strong&gt;&lt;br&gt;
We designed a &lt;strong&gt;Zero-Touch RBAC&lt;/strong&gt; architecture. We abolished manual configurations by replacing static identity checks with dynamic authorization conditional policies (&lt;code&gt;createCatalogConditionalDecision&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Backstage now natively mirrors hierarchies from Azure Active Directory, dynamically mapping resource &lt;code&gt;ownership&lt;/code&gt; at query time (Push-Down SQL). A user is granted or denied administrative access over a software entity based solely on their Identity Provider "Claims," resulting in zero infrastructure friction and automated governance.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Open Source Contribution: Zero-Leak Policy (Mitigating SSRF in the Scaffolder)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt;&lt;br&gt;
Within the Backstage ecosystem, the &lt;code&gt;http:backstage:request&lt;/code&gt; template action (maintained by the excellent RoadieHQ team) is widely used for HTTP integrations. However, we noticed that it lacked native &lt;em&gt;guardrails&lt;/em&gt; when templates received dynamic inputs, opening severe vulnerabilities to &lt;strong&gt;Server-Side Request Forgery (SSRF)&lt;/strong&gt; or &lt;strong&gt;Confused Deputy&lt;/strong&gt; attacks. A malicious user could exploit the Scaffolder to scan ports on the internal network or mutate confidential endpoints via restricted methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution:&lt;/strong&gt;&lt;br&gt;
Today, I took the lead in patching this vector and &lt;a href="https://github.com/RoadieHQ/roadie-backstage-plugins/pull/2236" rel="noopener noreferrer"&gt;submitted an official Pull Request&lt;/a&gt; to the community's Open Source repository. &lt;/p&gt;

&lt;p&gt;We injected &lt;code&gt;coreServices.rootConfig&lt;/code&gt; directly into the HTTP module's constructor and created a parameterized &lt;strong&gt;Zero-Leak Policy&lt;/strong&gt; via &lt;code&gt;app-config.yaml&lt;/code&gt;. Now, Platform Administrators can enforce a strict security &lt;em&gt;Whitelist&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scaffolder.http.allowedMethods&lt;/code&gt;: To restrict accidental deletions (e.g., blocking &lt;code&gt;DELETE&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scaffolder.http.allowedHosts&lt;/code&gt;: To guarantee that Scaffolder HTTP requests only reach authorized hosts, effectively isolating the network infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a glimpse of the architecture we injected into the action's handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 🛡️ ZERO-LEAK POLICY: SSRF and Confused Deputy Mitigation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedMethods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getOptionalStringArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scaffolder.http.allowedMethods&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedHosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getOptionalStringArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scaffolder.http.allowedHosts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allowedMethods&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;allowedMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`Security Policy Violation: HTTP method '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' is not allowed. `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`Allowed methods: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;allowedMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allowedHosts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;allowedHosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Security Policy Violation: Host '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;requestUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' is not in the allowed list.`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app-config.yaml Hardening&lt;/span&gt;
&lt;span class="na"&gt;scaffolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;allowedMethods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GET'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PUT'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;allowedHosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.dev.azure.com'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;api.myinternal.system'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Giving Back to the Community: Open-Sourcing Our Modules to NPM
&lt;/h2&gt;

&lt;p&gt;Beyond architectural improvements, we strongly believe in the open-source philosophy. To help other organizations struggling with similar challenges, we have decoupled our internal solutions and officially published them to the NPM registry! &lt;/p&gt;

&lt;p&gt;You can check out our new standalone Backstage packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@leooelx/plugin-catalog-backend-module-azure-autodiscovery" rel="noopener noreferrer"&gt;@leooelx/plugin-catalog-backend-module-azure-autodiscovery&lt;/a&gt;&lt;/strong&gt;: A resilient Entity Provider for Azure DevOps that automatically discovers repositories while bypassing strict rate limits via Audit Logs.&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@leooelx/plugin-scaffolder-environment-matrix-field" rel="noopener noreferrer"&gt;@leooelx/plugin-scaffolder-environment-matrix-field&lt;/a&gt;&lt;/strong&gt;: A dynamic React extension for the Backstage Scaffolder allowing users to inject environment-specific variables and secrets seamlessly.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Implementing an IDP like Backstage is only the first step. The true challenge of Platform Engineering lies in ensuring that the platform operates continuously, invisibly, and inviolably under the highest standards of governance (SRE &amp;amp; CyberSec).&lt;/p&gt;

&lt;p&gt;If you also work in Platform Engineering or want to debate resilient architectures in enterprise Node/React ecosystems, let's connect! Feel free to share your thoughts on how your teams handle permissions and resilience in your IDPs.&lt;/p&gt;

&lt;h1&gt;
  
  
  PlatformEngineering #Backstage #DevOps #SRE #CyberSecurity #OpenSource #NodeJS #Architecture
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>devops</category>
      <category>security</category>
      <category>sre</category>
    </item>
  </channel>
</rss>
