<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged application-security</title>
<link>https://coderlegion.com/tag/application-security</link>
<description>Powered by Question2Answer</description>
<item>
<title>Safe Public Demo Environments for Android Apps</title>
<link>https://coderlegion.com/30332/safe-public-demo-environments-for-android-apps</link>
<description>&lt;p&gt;Offering a public demo of an Android application presents a unique architectural challenge. A demo build is exposed to strangers and needs frequent, destructive data resets, yet it must never be able to target or corrupt the real customer database by accident. Relying on a simple runtime UI flag or environment toggle inside the production codebase creates an unacceptable risk of accidental data leakage. This article examines how to build a robust isolation boundary using separate build variants, dedicated demo backends, and fail-closed identity validation.&lt;/p&gt;
&lt;h3 id=&quot;why-a-demo-toggle-fails-as-an-isolation-boundary&quot;&gt;Why a Demo Toggle Fails as an Isolation Boundary&lt;/h3&gt;
&lt;p&gt;Many development teams initially attempt to support public demonstrations by adding a toggle within the primary application and backend. The logic often checks whether a demo mode flag is active before skipping certain write operations or routing requests to mock data. This approach fails because a single code path or misconfigured deployment can easily bypass the flag. If a build script accidentally packages the production credentials or connects the demo backend to the primary database, a public reset operation can wipe out real customer records.&lt;/p&gt;
&lt;p&gt;True isolation requires separating the trust boundary at every layer of the architecture. The application package name, the backend service instance, and the database project reference must be distinct. If any component in the chain does not match the expected demo configuration, the service should refuse to start entirely rather than attempting to recover or fallback.&lt;/p&gt;
&lt;h3 id=&quot;separate-android-build-variants-and-app-identity&quot;&gt;Separate Android Build Variants and App Identity&lt;/h3&gt;
&lt;p&gt;The first layer of defense begins in the Android client. Using build variants allows developers to maintain different application IDs, source sets, and configuration values within the same repository. By modifying the application ID for the demo variant, the operating system treats the demo build and the production build as entirely separate apps on the test device.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;android {
    defaultConfig {
        applicationId &quot;com.example.app&quot;
    }
    buildTypes {
        release {
            minifyEnabled true
        }
    }
    flavorDimensions &quot;mode&quot;
    productFlavors {
        production {
            dimension &quot;mode&quot;
            applicationIdSuffix &quot;&quot;
        }
        demo {
            dimension &quot;mode&quot;
            applicationIdSuffix &quot;.demo&quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration ensures that installation artifacts are distinct. More importantly, keeping the choice fixed in the build artifact prevents users or automated scripts from accidentally activating a demo mode inside a production binary.&lt;/p&gt;
&lt;h3 id=&quot;verify-backend-and-database-identity&quot;&gt;Verify Backend and Database Identity&lt;/h3&gt;
&lt;p&gt;Client-side separation alone is insufficient if the backend can connect to any database via environment variables. A dedicated demo backend must require explicit configuration flags and perform strict startup validation. During initialization, the backend should compare the database project reference derived from its active connection string against an expected demo project reference.&lt;/p&gt;
&lt;p&gt;If the strings do not match, the application must immediately throw an unhandled exception and shut down. This fail-closed mechanism ensures that a server misconfiguration becomes immediately visible during deployment rather than remaining a latent vulnerability.&lt;/p&gt;
&lt;h3 id=&quot;seed-and-reset-guarded-disposable-data&quot;&gt;Seed and Reset Guarded Disposable Data&lt;/h3&gt;
&lt;p&gt;Public demo environments require a mechanism to restore clean state after users alter or delete records. However, this reset mechanism must be guarded against accidental execution against live systems. Implementation evidence suggests a two-step validation model for data lifecycle management:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Initial seeding should only proceed if every demo table is entirely empty. If existing records are detected, the seed routine halts.&lt;/li&gt;
&lt;li&gt;Subsequent resets require a persisted security sentinel string to match the configured sentinel, and the database identity must be re-verified immediately before any deletion takes place.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Furthermore, developers should strip all non-essential integrations from the demo service. Production push notification credentials, third-party payment hooks, and external spreadsheet integrations should be omitted so that a compromised or heavily tested demo environment cannot trigger external side effects.&lt;/p&gt;
&lt;h3 id=&quot;what-process-local-locks-do-not-protect&quot;&gt;What Process-Local Locks Do Not Protect&lt;/h3&gt;
&lt;p&gt;When implementing automated reset endpoints, developers frequently reach for concurrency primitives such as a language-specific mutex to prevent overlapping reset requests. While a process-local mutex successfully serializes concurrent coroutines or threads within a single backend instance, it offers no protection in a horizontally scaled architecture. For a related implementation, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/managing-concurrent-git-commits-during-automated-publishing/&quot;&gt;Managing Concurrent Git Commits During Automated&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If the demo backend is deployed behind a load balancer with multiple active instances, each instance maintains its own memory space and its own mutex. Two distinct server processes could execute a database reset simultaneously. Protecting a multi-instance demo environment requires database-level locking, a dedicated single-instance worker queue, or another distributed coordination mechanism. For a related implementation, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/safe-multi-environment-database-orchestration/&quot;&gt;Safe Multi Environment Database Orchestration&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;release-checklist-for-a-public-demo&quot;&gt;Release Checklist for a Public Demo&lt;/h3&gt;
&lt;p&gt;Before launching a public demo environment, verify the following architectural controls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The demo application variant uses a distinct application ID suffix.&lt;/li&gt;
&lt;li&gt;The backend enforces explicit demo mode flags and fails startup if the database project reference does not match.&lt;/li&gt;
&lt;li&gt;Initial data seeding refuses to run unless all target tables are empty.&lt;/li&gt;
&lt;li&gt;Destructive reset operations require a valid persisted sentinel check and a pre-execution database identity re-validation.&lt;/li&gt;
&lt;li&gt;Session secrets and sentinels meet minimum length requirements.&lt;/li&gt;
&lt;li&gt;Non-essential production integrations and credentials are completely removed from the demo service deployment.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30332/safe-public-demo-environments-for-android-apps</guid>
<pubDate>Thu, 08 Oct 2026 23:37:04 +0000</pubDate>
</item>
<item>
<title>Safely Rejecting Redirects in Cloudflare Workers</title>
<link>https://coderlegion.com/29539/safely-rejecting-redirects-in-cloudflare-workers</link>
<description>&lt;p&gt;When writing a Cloudflare Worker that fetches remote assets from third party APIs or origin servers, an outbound request might return a 3xx redirect status code. Automatically following that redirect can bypass internal URL allowlists, leak authorization headers to unintended destinations, and compromise application security. Standard browser or Node.js fetch configurations do not always translate directly to edge runtimes. If you rely on unsupported fetch options or assume the runtime will automatically sanitize redirect hops, your application can fail silently or forward sensitive data to external hosts.&lt;/p&gt;
&lt;p&gt;This guide explains how to fetch remote assets securely in Cloudflare Workers by using manual redirect handling, validating your initial targets, and explicitly rejecting unexpected 3xx responses.&lt;/p&gt;
&lt;h3 id=&quot;the-edge-runtime-redirect-problem&quot;&gt;The Edge Runtime Redirect Problem&lt;/h3&gt;
&lt;p&gt;The Fetch API standard defines several modes for handling HTTP redirect responses, including follow, error, and manual. However, edge environments like Cloudflare Workers execute under specific constraints. When an outbound request triggers a redirect, the runtime must decide how to handle the subsequent network hop. Developers often attempt to use options that are either unsupported for outbound Worker requests or behave unexpectedly under specific compatibility dates, causing runtime exceptions before a useful response can be parsed.&lt;/p&gt;
&lt;p&gt;Furthermore, if your Worker fetches an asset on behalf of a user, following redirects blindly can lead to Server Side Request Forgery (SSRF) vulnerabilities. If an allowlist permits a safe domain, but that domain issues an HTTP 302 pointing to an internal metadata service or an untrusted external host, an automated redirect will bridge the gap. To prevent this, you must inspect the intermediate response rather than letting the runtime follow it automatically. For a related implementation, see &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/managing-concurrent-git-commits-during-automated-publishing/&quot;&gt;Managing Concurrent Git Commits During Automated&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;validating-the-initial-url&quot;&gt;Validating the Initial URL&lt;/h3&gt;
&lt;p&gt;Before initiating any outbound network call, your Worker should validate the target URL. Relying solely on string matching is often insufficient because URL parsers can interpret malformed inputs in unexpected ways. Always parse the input string using the global &lt;code&gt;URL&lt;/code&gt; constructor and check the protocol and hostname explicitly against a strict allowlist.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function isValidTargetUrl(rawUrl) {
  try {
    const parsed = new URL(rawUrl);
    const allowedHosts = [&quot;api.trusted-domain.com&quot;, &quot;assets.trusted-domain.com&quot;];
    
    if (parsed.protocol !== &quot;https:&quot;) {
      return false;
    }
    
    if (!allowedHosts.includes(parsed.hostname)) {
      return false;
    }
    
    return true;
  } catch (error) {
    return false;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By ensuring the initial URL meets strict protocol and host requirements, you establish a secure foundation for the outbound fetch operation.&lt;/p&gt;
&lt;h3 id=&quot;implementing-manual-redirect-handling&quot;&gt;Implementing Manual Redirect Handling&lt;/h3&gt;
&lt;p&gt;To prevent the edge runtime from following redirects automatically, pass &lt;code&gt;redirect: &quot;manual&quot;&lt;/code&gt; in the options object of your &lt;code&gt;fetch&lt;/code&gt; call. When you specify manual mode, any 3xx response is returned directly to your Worker code as a standard &lt;code&gt;Response&lt;/code&gt; object instead of triggering a transparent follow sequence.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;async function secureFetchAsset(targetUrl) {
  if (!isValidTargetUrl(targetUrl)) {
    throw new Error(&quot;Target URL failed validation checks.&quot;);
  }

  const response = await fetch(targetUrl, {
    redirect: &quot;manual&quot;,
    headers: {
      &quot;User-Agent&quot;: &quot;SecureWorkerAssetFetcher/1.0&quot;
    }
  });

  if (response.status &amp;gt;= 300 &amp;amp;&amp;amp; response.status &amp;lt; 400) {
    const redirectLocation = response.headers.get(&quot;Location&quot;);
    throw new Error(`Outbound request attempted a redirect to: ${redirectLocation || &quot;unknown destination&quot;}`);
  }

  if (!response.ok) {
    throw new Error(`Upstream service returned status: ${response.status}`);
  }

  return response;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this implementation, if the upstream server responds with a 301, 302, 303, 307, or 308, the status check catches the condition immediately. The Worker aborts execution without reading the response body or forwarding sensitive headers to the &lt;code&gt;Location&lt;/code&gt; target.&lt;/p&gt;
&lt;h3 id=&quot;managing-credentials-and-sensitive-headers&quot;&gt;Managing Credentials and Sensitive Headers&lt;/h3&gt;
&lt;p&gt;When a redirect is followed automatically, runtime clients often propagate authorization headers, cookies, or custom tokens to the new destination. If an untrusted server captures a redirect, it can harvest these credentials.&lt;/p&gt;
&lt;p&gt;By keeping redirect handling in manual mode, your Worker maintains complete control over token propagation. If a legitimate business requirement ever dictates that a redirect must be followed, your code must revalidate the new &lt;code&gt;Location&lt;/code&gt; URL against your original security policy before issuing a secondary &lt;code&gt;fetch&lt;/code&gt; call.&lt;/p&gt;
&lt;h3 id=&quot;testing-your-redirect-policy&quot;&gt;Testing Your Redirect Policy&lt;/h3&gt;
&lt;p&gt;To ensure your Worker handles various network responses predictably, maintain a small test matrix covering direct successes, redirects, client errors, and invalid URLs. &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Test Case &lt;/th&gt;&lt;th&gt;Initial URL Status &lt;/th&gt;&lt;th&gt;Expected Behavior&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Direct 200 OK &lt;/td&gt;&lt;td&gt;Valid trusted domain &lt;/td&gt;&lt;td&gt;Returns response body successfully&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;HTTP 302 Redirect &lt;/td&gt;&lt;td&gt;Trusted domain pointing elsewhere &lt;/td&gt;&lt;td&gt;Throws error and stops before following&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;HTTP 404 Not Found &lt;/td&gt;&lt;td&gt;Valid trusted domain &lt;/td&gt;&lt;td&gt;Throws upstream status error&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Disallowed Host &lt;/td&gt;&lt;td&gt;Untrusted domain &lt;/td&gt;&lt;td&gt;Throws validation error immediately&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Adding these verification checks alongside your production fetch path prevents regressions during runtime updates and ensures your edge applications maintain strict security boundaries.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Outbound fetch operations in edge runtimes require deliberate security controls to prevent unintended data exposure and SSRF vulnerabilities. By validating initial target URLs, configuring manual redirect policies, and explicitly inspecting 3xx status codes, you protect your infrastructure from unsafe redirects while maintaining predictable performance at the edge.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29539/safely-rejecting-redirects-in-cloudflare-workers</guid>
<pubDate>Fri, 02 Oct 2026 12:28:25 +0000</pubDate>
</item>
</channel>
</rss>