<?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: FabrizioPerezPeralta</title>
    <description>The latest articles on DEV Community by FabrizioPerezPeralta (@fabrizioperezperalta).</description>
    <link>https://dev.to/fabrizioperezperalta</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4016939%2F58d4862b-9822-407e-9650-3e30e94a09ec.png</url>
      <title>DEV Community: FabrizioPerezPeralta</title>
      <link>https://dev.to/fabrizioperezperalta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fabrizioperezperalta"/>
    <language>en</language>
    <item>
      <title>Testing Management at Speed: Caching and Execution in CircleCI, Bitbucket Pipelines, and GitHub Actions</title>
      <dc:creator>FabrizioPerezPeralta</dc:creator>
      <pubDate>Mon, 06 Jul 2026 05:19:12 +0000</pubDate>
      <link>https://dev.to/fabrizioperezperalta/testing-management-at-speed-caching-and-execution-in-circleci-bitbucket-pipelines-and-github-5g66</link>
      <guid>https://dev.to/fabrizioperezperalta/testing-management-at-speed-caching-and-execution-in-circleci-bitbucket-pipelines-and-github-5g66</guid>
      <description>&lt;p&gt;&lt;strong&gt;Abstract&lt;/strong&gt;&lt;br&gt;
An automated test suite is the ultimate gatekeeper for your code, but if it takes 20 minutes to run, it destroys developer velocity. While my colleague recently explored the foundational setup of GitHub Actions, GitLab CI, and Jenkins, this article tackles the next major hurdle in CI/CD testing management: execution speed.&lt;/p&gt;

&lt;p&gt;Using the same baseline repository (a Node.js Express app with Jest + Supertest), we will compare how three hosted CI platforms—CircleCI, Bitbucket Pipelines, and GitHub Actions—manage dependency caching and environment execution. We will look at real pipeline code to understand how each tool optimizes the feedback loop, and explore where enterprise tools like TeamCity, Tekton, and Harness fit into the testing lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Speed Problem in CI/CD&lt;/strong&gt;&lt;br&gt;
In any Node.js project, running npm ci often takes longer than running the tests themselves. If you don't manage caching correctly, your CI server downloads hundreds of megabytes of dependencies on every single commit. To compare how different tools solve this, we are giving them the exact same task:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check out the code.&lt;/li&gt;
&lt;li&gt;Restore cached dependencies (if they exist).&lt;/li&gt;
&lt;li&gt;Install missing dependencies.&lt;/li&gt;
&lt;li&gt;Run the test suite (npx jest --coverage --ci).&lt;/li&gt;
&lt;li&gt;Save the coverage artifacts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's look at the actual configuration files side-by-side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline 1: CircleCI (The Speed King)&lt;/strong&gt;&lt;br&gt;
CircleCI is famous in the DevOps world for granular resource allocation and advanced caching mechanisms. Configuration lives in .circleci/config.yml.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;YAML&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.1&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cimg/node:22.0&lt;/span&gt;    &lt;span class="c1"&gt;# CircleCI's optimized Node image&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
      &lt;span class="c1"&gt;# 1. Look for a cache matching the exact package-lock.json&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;restore_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;v1-deps-{{ checksum "package-lock.json" }}&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;v1-deps-&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="c1"&gt;# 2. Save the cache for future runs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;save_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/.npm&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1-deps-{{ checksum "package-lock.json" }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx jest --coverage --ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;store_artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;coverage/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What stands out: Explicit control. CircleCI forces you to define exactly how your cache is keyed using checksums. If package-lock.json changes, the cache is busted automatically. Furthermore, CircleCI provides cimg/ (convenience images) which are specifically pre-configured Docker images designed to boot up in milliseconds, shaving crucial seconds off your testing pipeline compared to standard Docker Hub images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline 2: Bitbucket Pipelines (The Zero-Config Approach)&lt;/strong&gt;&lt;br&gt;
For teams heavily invested in Jira and the Atlassian ecosystem, Bitbucket Pipelines offers a highly integrated, YAML-based approach that prioritizes simplicity over granular control. Configuration lives in bitbucket-pipelines.yml.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;YAML&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:22&lt;/span&gt;

&lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Test API&lt;/span&gt;
        &lt;span class="na"&gt;caches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node&lt;/span&gt;                &lt;span class="c1"&gt;# Zero-config dependency caching&lt;/span&gt;
        &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npx jest --coverage --ci&lt;/span&gt;
        &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;coverage/**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What stands out: Extreme brevity. Notice the caches: - node block. Unlike CircleCI or GitLab CI, where you must define paths and lockfile checksums, Bitbucket Pipelines has pre-defined caching logic for major package managers. It automatically knows that "node" means hashing package-lock.json and caching ~/.npm and node_modules. It is less flexible than CircleCI, but incredibly fast to set up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline 3: GitHub Actions (The Balanced Marketplace)&lt;/strong&gt;&lt;br&gt;
As demonstrated in the baseline repository, GitHub Actions strikes a middle ground between CircleCI's explicit control and Bitbucket's implicit magic, thanks to its reusable actions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;YAML&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;              &lt;span class="c1"&gt;# The Marketplace action handles the logic&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx jest --coverage --ci&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What stands out: Abstraction via the Marketplace. Instead of manually writing cache restoration and saving steps, the community-maintained setup-node action does the heavy lifting. It automatically checks the lockfile, restores the cache before the run, and saves it at the end of the job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expanding the Picture: The Broader CI/CD Ecosystem&lt;/strong&gt;&lt;br&gt;
While GitHub Actions, Bitbucket Pipelines, and CircleCI dominate the modern hosted-YAML space, the DevOps landscape offers specialized tools depending on where your testing management needs to scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Pioneers: Travis CI popularized the .yml config approach long before GitHub Actions existed and remains a solid choice for open-source projects.&lt;/li&gt;
&lt;li&gt;The On-Premise Giants: As my colleague noted, Jenkins offers unmatched plugin control. However, GitLab CI/CD is often preferred by enterprises because it offers a similarly robust self-hosted option but with a native, integrated interface rather than relying on a fragmented plugin ecosystem.&lt;/li&gt;
&lt;li&gt;Enterprise Polish: TeamCity (by JetBrains) provides deep IDE integrations and visual pipeline builders. It excels in enterprise environments where complex test reporting and historical flakiness analysis are prioritized over raw YAML scripting.&lt;/li&gt;
&lt;li&gt;The Cloud-Native Frontier: If you manage testing infrastructure at a massive scale, traditional servers become a bottleneck. Tekton solves this by turning your Kubernetes cluster into the CI engine, running every testing step as a native K8s pod.&lt;/li&gt;
&lt;li&gt;Beyond CI to Continuous Delivery: Passing the test suite is only the beginning. Platforms like Harness specialize in complex deployment orchestration. Once your Gitlab or GitHub pipeline turns green, Harness takes over to manage canary releases, AI-assisted rollbacks, and multi-cloud delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
A fast CI pipeline is a feature, not a luxury. When comparing testing management tools, looking purely at syntax is not enough; you must evaluate how they handle the friction of daily development. Bitbucket Pipelines offers unmatched simplicity for caching, CircleCI gives you absolute granular speed control, and GitHub Actions relies on community abstraction.&lt;/p&gt;

&lt;p&gt;The best tool is the one that allows your developers to push code, get a green or red test signal within seconds, and move on. Optimize your caches, containerize your dependencies, and never let your automated gatekeeper become a bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo repository (all three pipelines):&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/GianfrancoArocutipa/ci-testing-comparative" rel="noopener noreferrer"&gt;https://github.com/GianfrancoArocutipa/ci-testing-comparative&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>testing</category>
      <category>performance</category>
    </item>
    <item>
      <title>Applying API Testing Frameworks: Bulletproofing Your REST APIs Against Edge Cases and Bad Actors</title>
      <dc:creator>FabrizioPerezPeralta</dc:creator>
      <pubDate>Mon, 06 Jul 2026 05:00:43 +0000</pubDate>
      <link>https://dev.to/fabrizioperezperalta/applying-api-testing-frameworks-bulletproofing-your-rest-apis-against-edge-cases-and-bad-actors-47d7</link>
      <guid>https://dev.to/fabrizioperezperalta/applying-api-testing-frameworks-bulletproofing-your-rest-apis-against-edge-cases-and-bad-actors-47d7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Abstract&lt;/strong&gt;&lt;br&gt;
When developing a REST API, proving that the system works under ideal conditions (the "happy path") is only a fraction of the job. In the real world, APIs are constantly bombarded with malformed payloads, unauthorized access attempts, and conflicting state changes. This article explores how to use Jest and Supertest to enforce strict security boundaries and robust data validation. Using a real-world vehicle-workshop API as our target, we will dive into "Negative Testing"—writing automated integration tests specifically designed to ensure the API fails gracefully and securely when presented with invalid tokens, bad data, and resource conflicts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Illusion of the Happy Path&lt;/strong&gt;&lt;br&gt;
A test suite that only verifies 200 OK responses provides a false sense of security. An API is essentially a gateway to your database and business logic. If it accepts an incomplete payload or blindly trusts a user's input, it introduces vulnerabilities and corrupts data integrity.&lt;/p&gt;

&lt;p&gt;Using the Node.js ecosystem standard—Jest as the test runner and Supertest to execute HTTP assertions directly against the Express app in memory—we can aggressively simulate bad actors and edge cases in milliseconds. Since the server runs in-memory (bypassing the network layer), we can run hundreds of security checks without slowing down the CI pipeline.&lt;/p&gt;

&lt;p&gt;Let's look at how to implement the three most critical defensive API tests based on a vehicle management system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Validating Security Boundaries (401 Unauthorized)&lt;/strong&gt;&lt;br&gt;
Before processing any business logic, an API must verify the caller's identity. Security testing shouldn't be left to manual QA; it must be an automated contract.&lt;/p&gt;

&lt;p&gt;In our vehicle API, registering a new vehicle requires an API key. We must test not only the absence of the key but also the presence of an invalid one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;JavaScript&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST /api/vehicles - Security Boundaries&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="o"&gt;=&amp;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;newVehicle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ABC-123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Maria Lopez&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Toyota Hilux&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;in_service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rejects the request with 401 when x-api-key header is missing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/vehicles&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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newVehicle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// No .set() called&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/unauthorized/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rejects the request with 401 when the API key is invalid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/vehicles&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="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUPER_SECRET_HACKER_KEY&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="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newVehicle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&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;&lt;strong&gt;2. Strict Payload Validation (400 Bad Request)&lt;/strong&gt;&lt;br&gt;
Never trust the client. If your API expects a specific data structure, you must verify that missing fields, empty strings, or incorrect data types are immediately rejected.&lt;/p&gt;

&lt;p&gt;A robust validation test suite ensures that the API returns a 400 Bad Request and explicitly tells the client what went wrong, preventing malformed data from ever reaching the database layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;JavaScript&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST /api/vehicles - Payload Validation&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="o"&gt;=&amp;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;validKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_VALID_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns 400 when required fields are missing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;incompleteVehicle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jose Quispe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="c1"&gt;// missing 'plate', 'model', and 'status'&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/vehicles&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="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incompleteVehicle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Ensure the API lists exactly what is missing&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plate is required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model is required&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns 400 when sending invalid data types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/vehicles&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="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;plate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// Should be strings&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&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;&lt;strong&gt;3. Handling Resource Conflicts and Idempotency (409 Conflict)&lt;/strong&gt;&lt;br&gt;
What happens if two clients try to register the exact same vehicle at the exact same time, or if a user accidentally double-clicks a submit button? Your API must protect the uniqueness of its resources.&lt;/p&gt;

&lt;p&gt;For our workshop API, the plate acts as a unique identifier. We must ensure that attempting to create a duplicate record results in a 409 Conflict rather than silently overwriting the data or throwing an unhandled 500 Internal Server Error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;JavaScript&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST /api/vehicles - State Conflicts&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="o"&gt;=&amp;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;validKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_VALID_API_KEY&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;vehicle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XYZ-789&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Carlos Ruiz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Honda Civic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns 409 when the vehicle plate is already registered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&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;// 1. Insert the vehicle successfully&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/vehicles&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="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Attempt to insert the exact same vehicle again&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;duplicateRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/vehicles&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="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vehicle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Assert the conflict is caught and handled safely&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duplicateRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;duplicateRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/already registered/i&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;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Building integration tests with Jest and Supertest is about much more than verifying that your endpoints work; it’s about proving that they don't work when they shouldn't. By treating authentication bypasses, validation failures, and data conflicts as core components of your automated testing strategy, you shift security and reliability to the left.&lt;/p&gt;

&lt;p&gt;You no longer have to wait for an integration bug in production or a security audit to find out your endpoint accepts garbage data. With in-memory API testing, you can execute these critical defensive checks in less than a second on every single commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo repository (code + CI workflow):&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/GianfrancoArocutipa/api-testing-demo.git" rel="noopener noreferrer"&gt;https://github.com/GianfrancoArocutipa/api-testing-demo.git&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>testing</category>
      <category>javascript</category>
      <category>api</category>
    </item>
    <item>
      <title>Applying SAST to Infrastructure as Code: Unifying Cloud and Container Security with Trivy</title>
      <dc:creator>FabrizioPerezPeralta</dc:creator>
      <pubDate>Mon, 06 Jul 2026 04:34:39 +0000</pubDate>
      <link>https://dev.to/fabrizioperezperalta/applying-sast-to-infrastructure-as-code-unifying-cloud-and-container-security-with-trivy-2b3m</link>
      <guid>https://dev.to/fabrizioperezperalta/applying-sast-to-infrastructure-as-code-unifying-cloud-and-container-security-with-trivy-2b3m</guid>
      <description>&lt;p&gt;Author: Fabrizio Salvador Elias Perez Peralta&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Abstract&lt;/strong&gt;&lt;br&gt;
As Infrastructure as Code (IaC) evolves, the boundary between cloud provisioning and container configuration blurs. This article explores the application of Static Application Security Testing (SAST) using Trivy, an open-source vulnerability scanner listed in the OWASP tools catalog. While my colleague Gianfranco demonstrated the power of Checkov for AWS (referenced in GianfrancoArocutipa/checkov-iac-demo), this piece expands the scope by scanning both a Terraform deployment and a Docker container configuration in a single pass. The demonstration uncovers critical misconfigurations—including root-level container execution and unencrypted cloud storage—and details how to remediate them. Finally, it showcases how Trivy's SARIF output can be integrated into GitHub Actions, emphasizing the importance of developer experience and automated security gates in modern DevSecOps workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Evolution of IaC and the Need for SAST&lt;/strong&gt;&lt;br&gt;
In the modern DevSecOps landscape, a vulnerable system is rarely just about bad PHP or Python code. Often, the breach happens at the infrastructure level. You can write the most secure application in the world, but if the Docker container runs as root and the Terraform-managed S3 bucket holding the backups is public, you are compromised.&lt;/p&gt;

&lt;p&gt;SAST tools for IaC analyze configuration files (like .tf, Dockerfile, or kubernetes.yaml) against hundreds of security policies before any infrastructure is actually created. This allows developers to catch misconfigurations right in their IDE or Continuous Integration (CI) pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Trivy?&lt;/strong&gt;&lt;br&gt;
While Checkov is an outstanding policy-as-code tool, Trivy (by Aqua Security) takes a "comprehensive scanner" approach. It is widely loved in the Linux and Docker communities because a single binary can scan container images, file systems, Git repositories, and IaC configurations (Terraform, CloudFormation, Kubernetes, Dockerfiles) seamlessly. It is fast, stateless, and requires no prerequisites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Victim: A Containerized App Managed by Terraform&lt;/strong&gt;&lt;br&gt;
For this demonstration, we have a small project containing two critical IaC files: a Dockerfile for our application and a main.tf to deploy cloud resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Vulnerable Dockerfile:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:20.04&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; myapp /app/&lt;/span&gt;
&lt;span class="c"&gt;# VULNERABILITY: No USER specified, runs as root by default&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/app/myapp"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. The Vulnerable Terraform (main.tf):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.tf&lt;/span&gt;
&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"app_data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"company-sensitive-app-data"&lt;/span&gt;
  &lt;span class="c1"&gt;# VULNERABILITY: No encryption at rest configured&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# VULNERABILITY: SSH exposed to the world&lt;/span&gt;
  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&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;&lt;strong&gt;Step 1: Install and Run Trivy&lt;/strong&gt;&lt;br&gt;
Trivy is incredibly easy to use on Linux. You can install it via an apt package, homebrew, or simply pull the binary. Once installed, we instruct Trivy to scan our entire directory for IaC misconfigurations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trivy config ./infrastructure
Step 2: Analyzing the Scan Results
In a matter of seconds, Trivy parses both the Dockerfile and the Terraform code. The output instantly highlights the critical flaws:

Plaintext
infrastructure/Dockerfile (dockerfile)
======================================
Tests: 23 (SUCCESSES: 22, FAILURES: 1, EXCEPTIONS: 0)
Failures: 1 (HIGH: 1, CRITICAL: 0)

+---------------------------+------------+-----------------------------------------+
|           TYPE            |  SEVERITY  |                 MESSAGE                 |
+---------------------------+------------+-----------------------------------------+
| DS002                     | HIGH       | Specify at least 1 USER command in      |
|                           |            | Dockerfile with non-root user as        |
|                           |            | arguments                               |
+---------------------------+------------+-----------------------------------------+

infrastructure/main.tf (terraform)
==================================
Tests: 45 (SUCCESSES: 43, FAILURES: 2, EXCEPTIONS: 0)
Failures: 2 (HIGH: 1, CRITICAL: 1)

+---------------------------+------------+-----------------------------------------+
|           TYPE            |  SEVERITY  |                 MESSAGE                 |
+---------------------------+------------+-----------------------------------------+
| AVD-AWS-0088              | HIGH       | S3 Bucket does not have encryption      |
|                           |            | enabled                                 |
+---------------------------+------------+-----------------------------------------+
| AVD-AWS-0107              | CRITICAL   | Security group rule allows ingress      |
|                           |            | from public internet to port 22         |
+---------------------------+------------+-----------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trivy not only caught the Terraform misconfigurations (unencrypted bucket and open SSH port) but also flagged that our container is violating the principle of least privilege by running as root.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Hardening the Code&lt;/strong&gt;&lt;br&gt;
We remediate the issues by updating our IaC files with secure defaults.&lt;/p&gt;

&lt;p&gt;Fixed Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;FROM&lt;/span&gt; &lt;span class="nx"&gt;ubuntu&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;20.04&lt;/span&gt;
&lt;span class="nx"&gt;RUN&lt;/span&gt; &lt;span class="nx"&gt;useradd&lt;/span&gt; &lt;span class="nx"&gt;-m&lt;/span&gt; &lt;span class="nx"&gt;appuser&lt;/span&gt;
&lt;span class="nx"&gt;COPY&lt;/span&gt; &lt;span class="nx"&gt;myapp&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;
&lt;span class="nx"&gt;USER&lt;/span&gt; &lt;span class="nx"&gt;appuser&lt;/span&gt; &lt;span class="c1"&gt;# Fixed: Running as a non-root user&lt;/span&gt;
&lt;span class="nx"&gt;CMD&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/app/myapp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nx"&gt;Fixed&lt;/span&gt; &lt;span class="nx"&gt;Terraform&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;Terraform&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"app_data"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"company-sensitive-app-data"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_server_side_encryption_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"app_data_encryption"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;apply_server_side_encryption_by_default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sse_algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AES256"&lt;/span&gt; &lt;span class="c1"&gt;# Fixed: Encryption at rest enforced&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;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Fixed: Restricted to internal VPN&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;Running&lt;/span&gt; &lt;span class="nx"&gt;trivy&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="err"&gt;./&lt;/span&gt;&lt;span class="nx"&gt;infrastructure&lt;/span&gt; &lt;span class="nx"&gt;again&lt;/span&gt; &lt;span class="nx"&gt;yields&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;clean&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt; &lt;span class="nx"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;FAILURES&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Automating the Developer Experience (CI/CD)&lt;/strong&gt;&lt;br&gt;
As a developer who works on building custom vulnerability analyzers (like Anzencore) and IDE extensions, I know that CLI tools are only half the battle. If a security tool isn't integrated into the developer workflow, it won't be used.&lt;/p&gt;

&lt;p&gt;By integrating Trivy into GitHub Actions, we can block pull requests that introduce vulnerable infrastructure. Trivy supports outputting to the SARIF (Static Analysis Results Interchange Format) standard, which integrates natively with GitHub Code Scanning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IaC SAST - Trivy Scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;trivy-scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Trivy vulnerability scanner in IaC mode&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aquasecurity/trivy-action@master&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;scan-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;config'&lt;/span&gt;
          &lt;span class="na"&gt;hide-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sarif'&lt;/span&gt;
          &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trivy-results.sarif'&lt;/span&gt;
          &lt;span class="na"&gt;exit-code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Trivy scan results to GitHub Security tab&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trivy-results.sarif'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
By treating our containers and cloud deployments as code, SAST tools like Trivy allow us to push security to the very beginning of the software development lifecycle. Whether we are writing Terraform to spin up AWS resources or crafting Dockerfiles, scanning these text files for known architectural vulnerabilities prevents costly breaches before they happen. Integrating these tools through automated CI/CD gates ensures that security becomes an enabler of quality, rather than a blocker of speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo repository (code + CI workflow):&lt;/strong&gt; &lt;a href="https://github.com/GianfrancoArocutipa/checkov-iac-demo(#)" rel="noopener noreferrer"&gt;https://github.com/GianfrancoArocutipa/checkov-iac-demo(#)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>trivy</category>
      <category>security</category>
      <category>iac</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Shifting Mobile Security Left: Applying SAST to Android Apps with MobSF</title>
      <dc:creator>FabrizioPerezPeralta</dc:creator>
      <pubDate>Mon, 06 Jul 2026 04:17:30 +0000</pubDate>
      <link>https://dev.to/fabrizioperezperalta/shifting-mobile-security-left-applying-sast-to-android-apps-with-mobsf-47l3</link>
      <guid>https://dev.to/fabrizioperezperalta/shifting-mobile-security-left-applying-sast-to-android-apps-with-mobsf-47l3</guid>
      <description>&lt;p&gt;TL;DR — I took an intentionally vulnerable Android application, ran the static analysis engine of MobSF (Mobile Security Framework) via a local Docker container, and immediately identified critical flaws like hardcoded API keys and insecure local data storage. In mobile development, catching these issues at the source code level is vital before the APK is compiled, signed, and distributed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Challenge of Mobile SAST&lt;/strong&gt;&lt;br&gt;
Static Application Security Testing (SAST) is widely discussed in web development, but mobile ecosystems bring their own unique attack vectors. In Android, an attacker isn't just sending malicious payloads to a server; they can decompile the APK, inspect the manifest, and extract hardcoded secrets directly from the binary.&lt;/p&gt;

&lt;p&gt;When architecting mobile vulnerability analyzers—like Anzencore, for example—the primary objective is to inspect the raw code and configuration files before the build process even begins. We need to identify when developers fail to meet critical non-functional requirements, such as secure data synchronization and encrypted local storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why MobSF?&lt;/strong&gt;&lt;br&gt;
The OWASP Source Code Analysis Tools catalog is extensive, but many mainstream commercial tools are heavily web-focused. I chose MobSF because it is an open-source, specialized framework designed explicitly for mobile apps (Android/iOS/Windows). While it can perform dynamic analysis, its static analysis engine is incredibly powerful for parsing AndroidManifest.xml files, reverse-engineering Dalvik bytecode, and scanning Java/Kotlin source code for insecure implementations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Spinning up the Analyzer&lt;/strong&gt;&lt;br&gt;
Because we want to keep our host machine clean and avoid dependency hell, we will deploy MobSF using Docker. It takes just two commands in a Linux environment to get the local server running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pull the latest MobSF image&lt;/span&gt;
docker pull opensecurity/mobile-security-framework-mobsf:latest

&lt;span class="c"&gt;# Run the container on port 8000&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8000 opensecurity/mobile-security-framework-mobsf:latest
Once running, the web interface is accessible at localhost:8000, ready to ingest an APK or a ZIP of the &lt;span class="nb"&gt;source &lt;/span&gt;code.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Victim: A Vulnerable Android App&lt;/strong&gt;&lt;br&gt;
For this test, I uploaded an open-source vulnerable Android application (similar to InsecureBankv2). Instead of executing the app, MobSF decompiles it and runs its SAST ruleset against the extracted source.&lt;/p&gt;

&lt;p&gt;Within 60 seconds, the dashboard populated with critical findings. Here are the two most dangerous vulnerabilities caught by the static scan:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Hardcoded Secrets (CWE-798)&lt;/strong&gt;&lt;br&gt;
MobSF flagged the res/values/strings.xml file. A developer left a production AWS access key directly in the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Flagged by MobSF: Hardcoded sensitive information --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"aws_access_key"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;AKIAIOSFODNN7EXAMPLE&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
Why it matters: Anyone who downloads the app from the Play Store can use a tool like Apktool to extract this string in seconds.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Insecure Data Storage (CWE-312)&lt;/strong&gt;&lt;br&gt;
The analyzer traced a data flow in the Kotlin backend where user session tokens were being saved using SharedPreferences without the EncryptedSharedPreferences wrapper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Flagged by MobSF: Insecure SharedPreferences implementation&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sharedPref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getPreferences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MODE_PRIVATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;with&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sharedPref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;putString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"session_token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Tainted data sink&lt;/span&gt;
    &lt;span class="nf"&gt;apply&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;Why it matters: If the device is rooted, MODE_PRIVATE does not protect the XML file from being read by other malicious applications on the device. Secure storage is a non-functional requirement that must be enforced at the architectural level.&lt;/p&gt;

&lt;p&gt;The Next Evolution: Bringing SAST to the IDE&lt;br&gt;
While running a Dockerized SAST tool is great for CI/CD pipelines, the feedback loop can be tightened even further.&lt;/p&gt;

&lt;p&gt;The future of SAST isn't just in dashboards; it’s in the developer's immediate workspace. The ultimate goal is to transform tools like these into VS Code extensions or AI-assisted IDE plugins. By bringing the vulnerability analyzer directly into the code editor as a language server "skill", developers receive inline warnings the exact moment they type a hardcoded secret or implement an insecure storage method.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Mobile applications require specialized security tooling. By leveraging MobSF’s static analysis capabilities, we found critical data storage and hardcoded secret vulnerabilities without ever launching an emulator. Automating this process ensures that fundamental security requirements are met before a single line of code reaches production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo repository (code + CI workflow):&lt;/strong&gt; &lt;a href="https://github.com/GianfrancoArocutipa/psalm-sast-demo(#)" rel="noopener noreferrer"&gt;https://github.com/GianfrancoArocutipa/psalm-sast-demo(#)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>mobile</category>
      <category>devsecops</category>
      <category>android</category>
    </item>
  </channel>
</rss>
