<?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: Benjamin Tetteh</title>
    <description>The latest articles on DEV Community by Benjamin Tetteh (@benjamin_tetteh).</description>
    <link>https://dev.to/benjamin_tetteh</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%2F2923117%2Ff3d1ecaa-89e9-4456-afb0-d3217c2bb874.png</url>
      <title>DEV Community: Benjamin Tetteh</title>
      <link>https://dev.to/benjamin_tetteh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/benjamin_tetteh"/>
    <language>en</language>
    <item>
      <title>Your AI Writes Code Fast. Here’s How to Check It Before Shipping</title>
      <dc:creator>Benjamin Tetteh</dc:creator>
      <pubDate>Sat, 23 May 2026 14:55:54 +0000</pubDate>
      <link>https://dev.to/benjamin_tetteh/your-ai-writes-code-fast-heres-how-to-check-it-before-shipping-24cj</link>
      <guid>https://dev.to/benjamin_tetteh/your-ai-writes-code-fast-heres-how-to-check-it-before-shipping-24cj</guid>
      <description>&lt;h2&gt;
  
  
  A beginner-friendly guide to building an automated security pipeline with GitHub Actions — from zero, in under an hour.
&lt;/h2&gt;

&lt;p&gt;Here is a scenario that plays out constantly in the world of AI-assisted development.&lt;/p&gt;

&lt;p&gt;You build something. It works. You are excited. It is late. You run through a mental checklist — or most of it — and you push to production. Three items got checked. Two did not. You tell yourself you will fix it tomorrow.&lt;/p&gt;

&lt;p&gt;Tomorrow, you are already building the next thing.&lt;/p&gt;

&lt;p&gt;This is not a discipline problem. This is a systems problem. Checklists depend on human memory and human energy, and both of those fail under shipping pressure. &lt;strong&gt;Automation does not forget. It does not get tired.&lt;/strong&gt; It does not skip the last two items because it is midnight and the feature finally works.&lt;/p&gt;

&lt;p&gt;That is the entire argument for automated security gates. Not that you are careless — but that no human is perfectly consistent, and consistency is exactly what security requires.&lt;/p&gt;

&lt;p&gt;If you read the first article in this series about vibe coding and security, you saw the Moltbook breach — a weekend-built app that exposed 1.5 million API tokens and 35,000 emails because a few security defaults were never configured. The checklist at the end of that article is a good start. This article is about making that checklist run automatically, every single time you push code, without you having to think about it.&lt;/p&gt;

&lt;p&gt;One of the tools that makes this possible is GitHub Actions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is GitHub Actions, Really?
&lt;/h2&gt;

&lt;p&gt;If you have a GitHub repository — a place where your code lives online — GitHub Actions is a system that lets you run automatic tasks whenever something happens in that repository.&lt;/p&gt;

&lt;p&gt;Something happens could mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You push new code&lt;/li&gt;
&lt;li&gt;You open a pull request&lt;/li&gt;
&lt;li&gt;You merge into your main branch&lt;/li&gt;
&lt;li&gt;You create a new release&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When that trigger fires, GitHub spins up a temporary computer in the cloud, runs whatever instructions you give it, and reports back whether everything passed or failed. You do not manage that computer. You do not pay for it beyond GitHub's free tier limits. It just runs.&lt;/p&gt;

&lt;p&gt;Those instructions live in a file called a &lt;strong&gt;workflow&lt;/strong&gt; — a plain text file written in a format called YAML that lives inside your repository at &lt;code&gt;.github/workflows/&lt;/code&gt;. You can have as many workflow files as you want, each doing different things.&lt;/p&gt;

&lt;p&gt;Here is the simplest possible workflow to make this concrete:&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="c1"&gt;# .github/workflows/hello.yml&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;My First Workflow&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;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;              &lt;span class="c1"&gt;# Run this whenever code is pushed&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;say-hello&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="c1"&gt;# Use a fresh Linux computer&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;Print a message&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;echo "Code was pushed. The pipeline is running."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. A name, a trigger, a machine to run on, and steps to execute. Everything we build in this article follows exactly that same structure — we are just swapping &lt;code&gt;echo "hello"&lt;/code&gt; for real security tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters More for Vibe Coders
&lt;/h2&gt;

&lt;p&gt;Traditional developers have something vibe coders are still building: years of conditioned habits. The reflex to check for exposed secrets before pushing. The instinct to verify that a new endpoint requires authentication. The muscle memory of running a linter before committing.&lt;/p&gt;

&lt;p&gt;Those habits took years to form. AI-assisted development is compressing timelines so fast that many people are shipping production apps before those habits exist.&lt;/p&gt;

&lt;p&gt;Automated security gates close that gap. They are not a replacement for understanding security — but they are a safety net that catches common mistakes before they reach production. Think of it less like a replacement for a trained developer and more like spell-check. Spell-check does not make you a better writer, but it catches the embarrassing errors while you focus on the ideas.&lt;/p&gt;

&lt;p&gt;The goal of this article is to give you a working security pipeline by the end. Not a theoretical one. An actual &lt;code&gt;.yml&lt;/code&gt; file you can drop into any project today.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Set It Up: From Zero
&lt;/h2&gt;

&lt;p&gt;You need two things before anything else:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A GitHub repository.&lt;/strong&gt; If your project is not on GitHub yet, create a free account at github.com and push your code there. GitHub has a beginner guide for this if you have never done it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A &lt;code&gt;.github/workflows/&lt;/code&gt; folder&lt;/strong&gt; in your repository. You can create this directly on GitHub by clicking "Add file" inside your repository, or in your code editor locally. GitHub Actions picks up any &lt;code&gt;.yml&lt;/code&gt; file inside that folder automatically.&lt;/p&gt;

&lt;p&gt;That is genuinely all the setup required. No servers, no accounts, no credit cards.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the Pipeline: Layer by Layer
&lt;/h2&gt;

&lt;p&gt;We are going to build one workflow file that runs three security checks in sequence. Each check catches a different category of problem. You can start with just one and add the others when you are ready — the pipeline is modular by design.&lt;/p&gt;

&lt;p&gt;Here is the full picture of what we are building:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;On every push to main:
│
├── Step 1: Gitleaks — scan for exposed secrets and API keys
├── Step 2: Semgrep — scan for insecure code patterns  
└── Step 3: Snyk or CodeQL — scan for vulnerable dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each scan catches a different category of problem — secrets, insecure code patterns, and vulnerable dependencies. If any one fails, GitHub stops the pipeline before the code reaches production. You get an email, a red badge on your repository, and a detailed report showing exactly what was found and where.&lt;/p&gt;

&lt;p&gt;The diagram below maps how the pieces connect. Think of it as your security assembly line — every push goes through it automatically, without you having to remember to run anything manually.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2q3cb8p9ugvuky5m1oe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2q3cb8p9ugvuky5m1oe.png" alt="GitHub actions workflow" width="799" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's build each layer.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1: Catching Exposed Secrets with Gitleaks
&lt;/h3&gt;

&lt;p&gt;Gitleaks scans your codebase for anything that looks like a secret — API keys, database passwords, tokens, private keys. It knows the patterns for hundreds of services: AWS, OpenAI, Stripe, Supabase, GitHub itself.&lt;/p&gt;

&lt;p&gt;This is the Moltbook failure in automated form. If Gitleaks had been running on that repository, the exposed Supabase key would have been flagged before the first commit ever reached production.&lt;/p&gt;

&lt;p&gt;Create a file at &lt;code&gt;.github/workflows/security.yml&lt;/code&gt; and add this:&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;Security Pipeline&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;push&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;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;secret-scan&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;Scan for Exposed Secrets&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="c1"&gt;# Step 1: Download your code onto the pipeline machine&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="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;    &lt;span class="c1"&gt;# Scan the full git history, not just the latest commit&lt;/span&gt;

      &lt;span class="c1"&gt;# Step 2: Run Gitleaks&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 Gitleaks&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;gitleaks/gitleaks-action@v2&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;  &lt;span class="c1"&gt;# GitHub provides this automatically&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does in plain English:&lt;/strong&gt; Every time you push to main or open a pull request, GitHub downloads your code onto a fresh machine and runs Gitleaks across every file and every commit in your history. If it finds anything that looks like a secret, the pipeline fails and you get a detailed report showing exactly which file and which line contains the problem.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;fetch-depth: 0&lt;/code&gt; line is important — without it, Gitleaks only scans your most recent commit. With it, it scans your entire history, which catches secrets that were committed weeks ago and never removed.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Catching Insecure Code Patterns with Semgrep
&lt;/h3&gt;

&lt;p&gt;Gitleaks catches secrets. Semgrep catches insecure patterns — things like SQL queries that trust user input directly, authentication checks that can be bypassed, or configuration values that should never be public.&lt;/p&gt;

&lt;p&gt;Semgrep has a free tier and a library of pre-written rules maintained by security researchers. You do not need to write the rules yourself. You just point it at a ruleset and it does the work.&lt;/p&gt;

&lt;p&gt;Add this job to the same &lt;code&gt;security.yml&lt;/code&gt; file, underneath the &lt;code&gt;secret-scan&lt;/code&gt; job:&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;code-scan&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;Scan for Insecure Code Patterns&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 Semgrep&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;returntocorp/semgrep-action@v1&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;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
            &lt;span class="s"&gt;p/security-audit&lt;/span&gt;
            &lt;span class="s"&gt;p/secrets&lt;/span&gt;
            &lt;span class="s"&gt;p/javascript&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SEMGREP_APP_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SEMGREP_APP_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does in plain English:&lt;/strong&gt; Semgrep reads through your code looking for patterns that security researchers have identified as dangerous. The &lt;code&gt;p/security-audit&lt;/code&gt; ruleset covers common vulnerabilities. The &lt;code&gt;p/javascript&lt;/code&gt; ruleset adds JavaScript and TypeScript-specific checks. If your project uses Python, swap &lt;code&gt;p/javascript&lt;/code&gt; for &lt;code&gt;p/python&lt;/code&gt;. If it uses both, list both.&lt;/p&gt;

&lt;p&gt;To get your &lt;code&gt;SEMGREP_APP_TOKEN&lt;/code&gt;, create a free account at semgrep.dev, go to Settings, and copy your token. Then in your GitHub repository, go to Settings → Secrets and Variables → Actions → New Repository Secret, and paste it there as &lt;code&gt;SEMGREP_APP_TOKEN&lt;/code&gt;. GitHub stores it encrypted and injects it into the pipeline automatically — your token never appears in your code.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Catching Vulnerable Dependencies
&lt;/h3&gt;

&lt;p&gt;Your app almost certainly uses packages written by other people — React, Express, Axios, whichever libraries your AI reached for. Those packages sometimes contain known security vulnerabilities. You did not write the vulnerability, but if it is in your app, it is your problem.&lt;/p&gt;

&lt;p&gt;You have two solid options here depending on your comfort level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: Snyk (beginner-friendly, generous free tier)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Snyk is built for accessibility. It has a VS Code extension, a web dashboard, and a GitHub integration that makes setup straightforward. Add this job to your workflow:&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;dependency-scan-snyk&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;Scan Dependencies with Snyk&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 Snyk&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;snyk/actions/node@master&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SNYK_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SNYK_TOKEN }}&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;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--severity-threshold=high&lt;/span&gt;  &lt;span class="c1"&gt;# Only fail on high and critical issues&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get your &lt;code&gt;SNYK_TOKEN&lt;/code&gt; by creating a free account at snyk.io, going to Account Settings, and copying your Auth Token. Add it to GitHub Secrets the same way you added the Semgrep token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B: CodeQL (built into GitHub, no extra account needed)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CodeQL is GitHub's own static analysis engine. It is more powerful than Snyk for code-level analysis, slightly more complex to configure, but requires no external account. Replace the Snyk job with this:&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;dependency-scan-codeql&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;Scan with CodeQL&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;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;security-events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&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;Initialize CodeQL&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/init@v3&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;languages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;javascript&lt;/span&gt;  &lt;span class="c1"&gt;# Change to python, ruby, etc. if needed&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;Perform Analysis&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/analyze@v3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results from CodeQL appear directly in your GitHub repository under the Security tab — no external dashboard needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which should you choose?&lt;/strong&gt; Start with Snyk if you want immediate, readable results in plain English. Move to CodeQL when you want deeper analysis integrated directly into GitHub. There is no wrong answer — running either is infinitely better than running neither.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Pipeline
&lt;/h2&gt;

&lt;p&gt;Here is the full &lt;code&gt;security.yml&lt;/code&gt; file with all three jobs combined. Drop this into &lt;code&gt;.github/workflows/security.yml&lt;/code&gt; in any project and your automated security gate is live:&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;Security Pipeline&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;push&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;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;secret-scan&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;Scan for Exposed Secrets&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="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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;gitleaks/gitleaks-action@v2&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

  &lt;span class="na"&gt;code-scan&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;Scan for Insecure Code Patterns&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;returntocorp/semgrep-action@v1&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;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
            &lt;span class="s"&gt;p/security-audit&lt;/span&gt;
            &lt;span class="s"&gt;p/secrets&lt;/span&gt;
            &lt;span class="s"&gt;p/javascript&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SEMGREP_APP_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SEMGREP_APP_TOKEN }}&lt;/span&gt;

  &lt;span class="na"&gt;dependency-scan&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;Scan Dependencies&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;snyk/actions/node@master&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SNYK_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SNYK_TOKEN }}&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;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--severity-threshold=high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three jobs. Three categories of risk. All running automatically on every push.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reading the Results
&lt;/h2&gt;

&lt;p&gt;When a pipeline run completes, GitHub shows you one of two things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Green checkmarks&lt;/strong&gt; — all scans passed. You are clear to merge or deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A red X&lt;/strong&gt; — something was found. Click into the failed job, read the output, and it will tell you exactly what was flagged, in which file, and often why it is a problem.&lt;/p&gt;

&lt;p&gt;Do not treat a red X as a failure. Treat it as the system working. The pipeline caught something before it reached production — which is precisely what it is there to do.&lt;/p&gt;




&lt;h2&gt;
  
  
  One Thing to Do Right Now
&lt;/h2&gt;

&lt;p&gt;If setting up all three jobs at once feels like too much, start with just Gitleaks. Create the &lt;code&gt;.github/workflows/security.yml&lt;/code&gt; file with only the &lt;code&gt;secret-scan&lt;/code&gt; job. Push it to your repository. Watch it run.&lt;/p&gt;

&lt;p&gt;Once you have seen a pipeline run — green or red — the rest becomes much less intimidating. The structure is always the same: trigger, machine, steps. You are just swapping in different tools.&lt;/p&gt;

&lt;p&gt;Security automation is not a one-time setup. It is a habit that compounds. Every project you start from now on gets the pipeline from day one. Every collaborator who opens a pull request gets their code scanned automatically. Every push gets checked before it ships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is what consistency looks like when you remove the human from the loop.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where This Fits in Your Stack
&lt;/h2&gt;

&lt;p&gt;This pipeline covers the code layer — what is in your repository. Combined with the platform-level defaults from the previous article — RLS enabled, rate limiting on, secrets in the right environment variables — you now have security running at two levels simultaneously:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before the code ships&lt;/strong&gt; → GitHub Actions catches it in the pipeline&lt;br&gt;
&lt;strong&gt;After the code ships&lt;/strong&gt; → Platform defaults limit the damage if something slips through&lt;/p&gt;

&lt;p&gt;Neither layer is perfect on its own. Together they cover the vast majority of the failures that appear in real-world breaches — including the ones that made Moltbook a case study.&lt;/p&gt;

&lt;p&gt;You did not need a Computer Science degree to set this up. You needed a &lt;code&gt;.github/workflows/&lt;/code&gt; folder and about an hour. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;The next article in this series covers something slightly different: what to do when the AI itself is part of your pipeline — and the new attack surface that creates.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Found this useful? Share it with someone who just shipped their first repo. The best time to add a security pipeline is before the first breach. The second best time is right now.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>beginners</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>The Hidden Layer Every AI Developer Must Learn</title>
      <dc:creator>Benjamin Tetteh</dc:creator>
      <pubDate>Fri, 22 May 2026 12:22:52 +0000</pubDate>
      <link>https://dev.to/benjamin_tetteh/the-hidden-layer-every-ai-developer-must-learn-be0</link>
      <guid>https://dev.to/benjamin_tetteh/the-hidden-layer-every-ai-developer-must-learn-be0</guid>
      <description>&lt;p&gt;Late January 2026. A developer ships a social network over a weekend. No traditional code written — just prompts, a vision, and an AI that turned ideas into a working product in days. The platform goes viral. Andrej Karpathy, OpenAI co-founder, calls it "the most incredible sci-fi takeoff-adjacent thing I have seen recently."&lt;/p&gt;

&lt;p&gt;Then a security researcher opens the browser's developer tools.&lt;br&gt;
Within minutes, they find an API key sitting in plain JavaScript — visible to anyone who knows how to press F12. They use it to query the production database. No login required. No special tools. Just a simple command and a coffee.&lt;/p&gt;

&lt;p&gt;What comes back: &lt;strong&gt;1.5 million API authentication tokens. 35,000 email addresses. Thousands of private messages.&lt;/strong&gt; The entire platform — every agent, every credential, every conversation — sitting wide open. &lt;/p&gt;

&lt;p&gt;The platform was called Moltbook. The fix, when it came, took two SQL statements.&lt;/p&gt;

&lt;p&gt;This is not a story about a bad developer. It is a story about a gap that AI does not fill automatically — and what you can do about it before you ship.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6tvprhmdlon5xqi7vg0.jpg" alt="developer working behind desk" width="799" height="436"&gt;
&lt;/h2&gt;
&lt;h2&gt;
  
  
  The Illusion That Catches Everyone
&lt;/h2&gt;

&lt;p&gt;A few years ago, building software required crossing a painful barrier. You had to learn syntax, frameworks, databases, APIs, Git, deployment — and break things repeatedly along the way. For most people outside tech, software engineering felt like a locked room with a very small door.&lt;/p&gt;

&lt;p&gt;Then AI arrived.&lt;/p&gt;

&lt;p&gt;Now someone with little traditional programming experience can build a SaaS app over a weekend, connect a database to a frontend, integrate payments, and deploy an API — all from prompts. That shift is extraordinary, and I think it is genuinely amazing. I am a vibe coder too. I understand the excitement of watching an idea move from imagination to a working product faster than ever before.&lt;/p&gt;

&lt;p&gt;But there is a dangerous illusion forming around AI-generated software: &lt;strong&gt;If the app works, people assume it's safe. Those are not the same thing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A beautifully designed application can still expose private user data, leak API keys, allow unauthorized access, and accidentally disable database protections — all while the UI looks polished and the login flow works perfectly. Security problems live underneath visible functionality. That is exactly what happened with Moltbook.&lt;/p&gt;


&lt;h2&gt;
  
  
  What AI Gets Right, and What It Quietly Skips
&lt;/h2&gt;

&lt;p&gt;AI is very good at helping you build the happy path — the feature works, the button responds, the API returns data, the page renders. That part is genuinely impressive.&lt;br&gt;
Security lives in the unhappy paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if someone queries your database without logging in?&lt;/li&gt;
&lt;li&gt;What if an attacker manipulates a request from the browser?&lt;/li&gt;
&lt;li&gt;What if your API keys are visible in the page source?&lt;/li&gt;
&lt;li&gt;What if someone calls your registration endpoint ten thousand times in a loop?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Experienced developers ask these questions automatically. Not because they are smarter — but because they built the habit slowly, over years of debugging painful issues, reading post-mortems, and making mistakes in lower-stakes environments.&lt;/p&gt;

&lt;p&gt;AI compresses the implementation timeline dramatically. It does not compress the experience required to ask the right security questions. That gap is where breaches happen.&lt;/p&gt;

&lt;p&gt;And to be clear: this is not only a beginner problem. Even experienced developers can become overconfident when AI accelerates output speed. Because AI-generated code often looks extremely convincing. The explanations sound confident. The architecture appears plausible. The feature functions correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But plausible code is not the same as safe code.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Moltbook Breakdown: What Actually Went Wrong
&lt;/h2&gt;

&lt;p&gt;Moltbook was built on Supabase — a popular, well-documented backend service that is excellent for fast development. Supabase is designed to work with a public API key exposed on the client side. That is intentional and not, by itself, a security failure.&lt;/p&gt;

&lt;p&gt;The security failure is what you configure that key to be able to do.&lt;/p&gt;

&lt;p&gt;Supabase ships with a feature called Row-Level Security — a database setting that ensures users can only access their own data. It is not enabled by default. You have to turn it on. And if you are vibe coding and the AI generates a working backend without you asking about security, there is a good chance that step never comes up.&lt;/p&gt;

&lt;p&gt;At Moltbook, it didn't.&lt;/p&gt;

&lt;p&gt;The result: anyone with basic technical knowledge could query the entire production database — every user's credentials, private messages, and authentication tokens — using the key sitting in plain sight on the website. Write access was also open, meaning an attacker could have edited any post on the platform without logging in.&lt;/p&gt;

&lt;p&gt;The founder's public statement captured the situation honestly: &lt;em&gt;"I didn't write a single line of code for Moltbook. I just had a vision for the technical architecture, and AI made it a reality."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The AI made a working platform. It did not make a secure one.&lt;/p&gt;


&lt;h2&gt;
  
  
  It Is Not Just Moltbook
&lt;/h2&gt;

&lt;p&gt;Six months before Moltbook, security researchers at Wiz found a critical vulnerability in Base44 — a vibe coding platform used by actual enterprises to build internal HR systems, customer databases, and knowledge bases containing sensitive employee data.&lt;/p&gt;

&lt;p&gt;The flaw was shockingly simple: two API endpoints for registering and verifying users required no authentication whatsoever. Using only a value visible in the app's public URL, a researcher could create a verified account inside any private enterprise application on the platform — bypassing SSO and every other access control entirely.&lt;/p&gt;

&lt;p&gt;Here is the detail that changes the conversation: &lt;strong&gt;Base44 builders did not write that vulnerable endpoint. The platform did.&lt;/strong&gt; Individual developers had no visibility into the flaw. This is the second dimension of vibe coding security risk that almost nobody talks about. The first is what AI generates when you prompt it. The second is what the platform you are building on introduces independently of your code.&lt;/p&gt;

&lt;p&gt;Both matter. Both can be addressed.&lt;/p&gt;


&lt;h2&gt;
  
  
  Security in Plain English
&lt;/h2&gt;

&lt;p&gt;One reason security feels intimidating is because the terminology sounds abstract and technical. Most of it isn't. Here is a plain-English translation of the concepts that come up most often:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;What It Actually Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Verifying who someone is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorization&lt;/td&gt;
&lt;td&gt;Deciding what they are allowed to access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API Key&lt;/td&gt;
&lt;td&gt;A secret password your app uses to talk to another service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row-Level Security&lt;/td&gt;
&lt;td&gt;Preventing users from reading other users' data in your database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate Limiting&lt;/td&gt;
&lt;td&gt;Stopping someone from making thousands of requests in a loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret Scanning&lt;/td&gt;
&lt;td&gt;Automatically detecting exposed passwords or keys in your code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Least Privilege&lt;/td&gt;
&lt;td&gt;Only giving a system the minimum access it actually needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input Validation&lt;/td&gt;
&lt;td&gt;Making sure users can't send dangerous or unexpected data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Security is not magic. Most of the time it is about reducing obvious mistakes, limiting damage when mistakes happen, and protecting user trust. That's it.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Five Things AI Consistently Misses
&lt;/h2&gt;

&lt;p&gt;These are the patterns that show up repeatedly in AI-generated code — not as exotic edge cases, but as defaults. Each one comes with a practical fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. API Keys Exposed in Client-Side Code&lt;/strong&gt;&lt;br&gt;
When you ask AI to connect your app to an external service, it will often put the credentials directly in the frontend code. Frontend code is public. Anyone can open the browser's developer tools and find it. Automated scanners crawl the web looking for exactly this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Run &lt;a href="https://gitleaks.io/" rel="noopener noreferrer"&gt;Gitleaks&lt;/a&gt; before you push code. It scans for secrets and blocks the commit if it finds any. GitHub's built-in secret scanning does the same thing automatically on public repositories — no setup required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Row-Level Security Not Enabled&lt;/strong&gt;&lt;br&gt;
This is the Moltbook failure. AI can generate a complete Supabase schema without ever enabling RLS, because RLS requires understanding your data access model — who should see what — and AI often skips that reasoning unless you ask for it explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; In your Supabase dashboard, check that RLS is enabled on every table containing user data. Then write policies that define exactly who can access each row. The Supabase documentation has a beginner-friendly walkthrough that takes about twenty minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Enable RLS on a table&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Users can only read their own records&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="nv"&gt;"owner_only"&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owner_id&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. API Endpoints With No Authentication&lt;/strong&gt;&lt;br&gt;
AI generates routes that respond to requests. Whether it adds authentication to those routes depends on whether you asked — and whether the AI remembered your earlier requirements by the time it got to that file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Go through every API route and ask: what happens if someone calls this without logging in? Tools like &lt;a href="https://www.bearer.com/" rel="noopener noreferrer"&gt;Bearer&lt;/a&gt; scan your codebase and flag unprotected routes for free. Better still, apply authentication at the middleware layer so every route is protected by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Secrets on the Wrong Side of the App&lt;/strong&gt;&lt;br&gt;
Many AI tools know not to hardcode secrets and will suggest environment variables. What they sometimes miss is the difference between variables available to the client (public) and variables available only to the server (private).&lt;/p&gt;

&lt;p&gt;In Next.js, any variable prefixed with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; is bundled into the client JavaScript and visible to everyone. Your OpenAI API key, Stripe secret key, and database credentials should never have that prefix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Search your project for &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; and verify that nothing sensitive uses it. Server-only secrets get no prefix. Public configuration values — like your Supabase URL — can use the prefix safely, but only if RLS is properly configured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. No Rate Limiting&lt;/strong&gt;&lt;br&gt;
AI generates endpoints that respond to requests. It does not add rate limiting unless you ask. This means your registration endpoint, login endpoint, and data endpoints will accept unlimited requests from anyone.&lt;/p&gt;

&lt;p&gt;At Moltbook, this allowed a single bot to create 500,000 fake accounts. The same pattern can be used to exhaust your API quota overnight and run up a bill that ends your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; If you are on Vercel, enable rate limiting in your project's security settings — it takes five minutes and requires no code. For Supabase, the same option exists under Project Settings. Do this before you go public.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mistake Almost Everyone Makes: "I'll Secure It Later"
&lt;/h2&gt;

&lt;p&gt;This mindset is understandable. When you are learning, shipping feels difficult enough already. Security can feel like an advanced topic for a future version of yourself.&lt;/p&gt;

&lt;p&gt;The problem is that insecure architecture hardens very quickly. Once real users arrive, technical debt compounds, insecure patterns spread through the codebase, and leaked secrets cannot be unexposed. The Moltbook breach did not happen because the founder planned to fix security later. It happened because the platform went viral before that moment came.&lt;/p&gt;

&lt;p&gt;Security is not a decorative layer added at the end. It is part of the design. That does not mean you need perfection before you launch. It means security awareness needs to begin at the same time as everything else.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Security Stack, Kept Simple
&lt;/h2&gt;

&lt;p&gt;You do not need to become a security engineer. You need a short checklist and the discipline to run it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start here — these take under an hour total:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Secret Scanning&lt;/strong&gt; — enabled by default on public repos, catches exposed keys automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gitleaks&lt;/strong&gt; — run it locally before pushing; blocks commits containing secrets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase RLS&lt;/strong&gt; — enable it on every table in your dashboard; follow the docs walkthrough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel Rate Limiting&lt;/strong&gt; — enable it in your project settings before going public&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snyk free tier&lt;/strong&gt; — scans your dependencies for known vulnerabilities; integrates with VS Code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When your project gets more serious:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Semgrep&lt;/strong&gt; — static analysis that catches insecure code patterns in CI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CodeQL&lt;/strong&gt; — deeper analysis integrated into your GitHub pull request workflow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-commit hooks&lt;/strong&gt; — stop dangerous commits before they leave your machine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of these tools as an automated second opinion. If AI is your fast-moving junior developer, security tooling is your automated reviewer. That combination is far safer than relying on intuition alone — especially when moving quickly.&lt;/p&gt;




&lt;h2&gt;
  
  
  One More Thing: Ask Your AI
&lt;/h2&gt;

&lt;p&gt;One of the most underused techniques in vibe coding security is simply prompting for it. Before you ship any feature that touches user data, try this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Review this code for security issues. Check whether any credentials are exposed in client-side code, whether database tables have Row-Level Security enabled, whether API endpoints require authentication, and whether there is rate limiting on registration and data endpoints."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;AI is quite capable of security review when asked explicitly. The problem is that it does not apply that review automatically. Make it a habit to ask. It takes thirty seconds and it will catch things.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Fair Hearing for the Critics
&lt;/h2&gt;

&lt;p&gt;The people arguing against vibe coding are not wrong. They are pointing at a real pattern: AI generates working code that skips assumptions a trained developer would never skip. Moltbook and Base44 are valid evidence of that.&lt;/p&gt;

&lt;p&gt;But the conclusion — that people without traditional coding backgrounds should not build things — does not follow. What the evidence actually shows is that vibe coding without security awareness is dangerous. That is a different claim, and it has a different solution.&lt;/p&gt;

&lt;p&gt;The Wiz Research team — the same team that found both breaches — put it clearly: the opportunity is not to slow down vibe coding but to elevate it. AI tools that generate Supabase backends can enable RLS by default. Deployment platforms can scan for exposed credentials automatically. The infrastructure for secure-by-default vibe coding exists. It just is not the default yet.&lt;/p&gt;

&lt;p&gt;Until it is, the gap has to be filled by builders who know what to check — and who check it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before You Ship
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ] No API keys or secrets in frontend code
[ ] Gitleaks run before pushing to the repository
[ ] Row-Level Security enabled on every database table with user data
[ ] Every API endpoint requires authentication, or is explicitly marked public
[ ] Rate limiting enabled on registration and data endpoints
[ ] NEXT_PUBLIC_ prefix checked — nothing sensitive uses it
[ ] Snyk or equivalent scanned your dependencies
[ ] Asked AI to review your code specifically for security issues
[ ] Opened the browser dev tools and checked what a stranger can see
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;AI is making software creation more accessible than ever. More people building means more innovation, more creativity, more diverse voices entering technology. That is a genuinely good thing.&lt;/p&gt;

&lt;p&gt;But software engineering has always been more than generating working code. There is a layer underneath every application — trust, security, permissions, responsibility — that still belongs to the person who shipped it.&lt;/p&gt;

&lt;p&gt;You do not need to learn everything before you build. You just need to know the layer exists, and make a habit of checking it.&lt;/p&gt;

&lt;p&gt;The checklist is above. The tools are free. The rest is discipline. The future of software may be AI-assisted. But responsibility is still human.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this useful? Share it with someone who just shipped their first AI-built app. The person who needs it most is usually the one who doesn't know they need it yet.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>security</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building My First AWS VPC and EC2 Environment with Terraform: A Beginner-Friendly Guide for Career Changers</title>
      <dc:creator>Benjamin Tetteh</dc:creator>
      <pubDate>Fri, 08 May 2026 20:47:37 +0000</pubDate>
      <link>https://dev.to/benjamin_tetteh/building-my-first-aws-vpc-with-terraform-a-beginner-friendly-guide-for-career-changers-1elm</link>
      <guid>https://dev.to/benjamin_tetteh/building-my-first-aws-vpc-with-terraform-a-beginner-friendly-guide-for-career-changers-1elm</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F534ixd1wrw7l27rkmb7j.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F534ixd1wrw7l27rkmb7j.jpeg" alt="Terraform VPC setup" width="800" height="600"&gt;&lt;/a&gt;Not long ago, terms like “VPC,” “subnet,” and “Terraform” would have made my eyes glaze over. While my background wasn’t originally rooted in cloud engineering, I’ve always been fascinated by how modern infrastructure is designed, automated, and scaled behind the scenes.&lt;/p&gt;

&lt;p&gt;Now, partway through my DevOps journey, I’ve gone from simply reading about cloud infrastructure to actually building it, using Terraform to provision a fully functional AWS network entirely through code.&lt;/p&gt;

&lt;p&gt;If you're reading this while sitting at a career crossroads — maybe you're a teacher, an accountant, a customer service rep, or anyone wondering &lt;em&gt;"can I really break into tech?"&lt;/em&gt; I want this post to be your proof that yes, you absolutely can.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before we touch any code...
&lt;/h2&gt;

&lt;p&gt;Let's understand why everything exists. Tools make a lot more sense that way. Think of it like building a neighbourhood. Imagine you're a city planner given a plot of land. Your job is to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Draw the boundary of the land&lt;/strong&gt; — this is your VPC (Virtual Private Cloud). It defines your space in AWS. Nothing gets in or out unless you say so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Divide the land into zones&lt;/strong&gt; — some areas are public (like a shopping street anyone can visit) and some are private (like a gated estate — no outsiders allowed).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build a gate to the outside world&lt;/strong&gt; — this is the Internet Gateway (IGW). The single controlled entrance between your network and the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set the traffic rules&lt;/strong&gt; — Route Tables tell your network traffic exactly where to go, like road signs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Construct buildings inside the neighbourhood&lt;/strong&gt; — these are your EC2 instances, the virtual servers that actually run applications and services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hire security guards for the buildings&lt;/strong&gt; — these are your Security Groups, which act like firewalls controlling who can access your servers and through which doors (ports).&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Why Terraform instead of clicking around in AWS?&lt;/strong&gt;&lt;br&gt;
Clicking in the AWS console is slow, hard to repeat, and easy to mess up. Terraform lets you write your infrastructure as code — describe what you want, run one command, and it builds everything consistently every time. This is called &lt;strong&gt;Infrastructure as Code (IaC)&lt;/strong&gt; and employers actively look for this skill.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The project structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform-vpc/
├── main.tf       # The blueprint — everything to build
├── variables.tf  # The settings — values we can change
└── outputs.tf    # The receipt — shows what got created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of &lt;code&gt;main.tf&lt;/code&gt; as the architect's drawing, &lt;code&gt;variables.tf&lt;/code&gt; as the customizable options, and &lt;code&gt;outputs.tf&lt;/code&gt; as the summary report after construction is done.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Step 1: Setting up Terraform — the provider block&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="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.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;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="kd"&gt;var&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first block tells Terraform: &lt;em&gt;"We're working with AWS, and we want version 5 of the AWS plugin."&lt;/em&gt; That plugin is called a &lt;strong&gt;provider&lt;/strong&gt; — think of it as an adapter that lets Terraform talk to AWS.&lt;/p&gt;

&lt;p&gt;The second block specifies which AWS region to build in. A &lt;strong&gt;region&lt;/strong&gt; is a physical location with Amazon data centres — &lt;code&gt;us-east-1&lt;/code&gt; is Northern Virginia, USA.&lt;/p&gt;

&lt;p&gt;Notice &lt;code&gt;var.region&lt;/code&gt;? That means: &lt;em&gt;"look up the value of region in my variables file."&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Step 2: Creating the VPC — your cloud neighbourhood&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="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_vpc"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-vpc"&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;The &lt;code&gt;cidr_block&lt;/code&gt; defines the total size of your network. &lt;code&gt;10.0.0.0/16&lt;/code&gt; gives us room for up to 65,536 addresses — a big plot of land!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tags&lt;/code&gt; are just labels to help you find your resources in the AWS console. Always tag. Your future self will thank you.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Step 3: Internet Gateway — the front gate&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="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_internet_gateway"&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&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;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-igw"&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;Without this, your VPC is sealed off — like a neighbourhood with no road to the outside world. Notice &lt;code&gt;vpc_id = aws_vpc.main.id&lt;/code&gt;? Terraform links resources like this. It figures out the build order automatically.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Step 4: Subnets — carving the zones&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;# Public subnet (the shopping street)&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&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;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_subnet_cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;map_public_ip_on_launch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# Automatically assign public IPs to instances launched in this subnet&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-public-subnet"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Private subnet (the gated estate)&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_subnet"&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&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;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnet_cidr_block&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-private-subnet"&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;We're carving our big VPC into zones. The &lt;strong&gt;public subnet&lt;/strong&gt; (&lt;code&gt;10.0.1.0/24&lt;/code&gt;) is for resources that need internet access, like web servers. The &lt;strong&gt;private subnet&lt;/strong&gt; (&lt;code&gt;10.0.2.0/24&lt;/code&gt;) is for sensitive things like databases — no direct internet access. The &lt;code&gt;/24&lt;/code&gt; gives each subnet 256 addresses.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;map_public_ip_on_launch = true&lt;/code&gt; For resources inside a subnet to actually receive public internet access, instances launched there need public IP addresses. Without a public IP, an EC2 instance cannot be reached from the internet, even if everything else is configured correctly.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Step 5: Route tables — the traffic signs&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;# Public route table + association + internet route&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&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;tags&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-public-rt"&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_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"public"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&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;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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_route"&lt;/span&gt; &lt;span class="s2"&gt;"public_igw"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&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;destination_cidr_block&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="nx"&gt;gateway_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Private route table + association (no internet route)&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_route_table"&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&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;tags&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-private-rt"&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_route_table_association"&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&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;route_table_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key line is &lt;code&gt;destination_cidr_block = "0.0.0.0/0"&lt;/code&gt; pointing to the IGW. &lt;code&gt;0.0.0.0/0&lt;/code&gt; means &lt;em&gt;"any address on the internet"&lt;/em&gt; — this is what makes the public subnet actually public.&lt;/p&gt;

&lt;p&gt;The private subnet gets its own route table &lt;strong&gt;but no internet route&lt;/strong&gt; — isolated by design. The associations are the connectors that glue each table to its subnet.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Step 6: Security groups - for the public subnet&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="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"public_sg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"public-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow SSH and HTTP access"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="c1"&gt;# Allow SSH access from anywhere&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="c1"&gt;# Allow HTTP access from anywhere&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;80&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;80&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="c1"&gt;# Allow all outbound traffic&lt;/span&gt;
  &lt;span class="nx"&gt;egress&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;0&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;0&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;"-1"&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="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-public-sg"&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;A Security Group is basically a firewall attached to your EC2 instance. It decides who can enter, which ports are open, what type of traffic is allowed.&lt;/p&gt;

&lt;p&gt;This Security Group allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH access (port 22)&lt;/li&gt;
&lt;li&gt;HTTP web traffic (port 80)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;cidr_blocks = ["0.0.0.0/0"]&lt;/code&gt; means &lt;em&gt;“Allow traffic from anywhere on the internet.”&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Step 7: Create EC2 instance in the public subnet&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="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"public_ec2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ami&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ami_id&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_type&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="c1"&gt;# This places the EC2 inside the public subnet&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_sg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-public-ec2"&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;code&gt;ami = var.ami_id&lt;/code&gt; An AMI (Amazon Machine Image) is essentially a template for the operating system. Think of it like installing Windows or Linux onto a new computer. I used an Amazon Linux AMI for this project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;instance_type = var.instance_type&lt;/code&gt; This defines the size and power of the virtual server. I used &lt;code&gt;t3.micro&lt;/code&gt; (referenced in the &lt;code&gt;variables.tf&lt;/code&gt; file) which is lightweight and great for learning purposes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;subnet_id = aws_subnet.public.id&lt;/code&gt; This places the EC2 instance inside the public subnet we created earlier.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;security_groups = [aws_security_group.public_sg.id]&lt;/code&gt; attaches the firewall rules to the server.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Step 8: Variables — the settings file&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="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS region to deploy resources in"&lt;/span&gt;
  &lt;span class="nx"&gt;default&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"vpc_cidr_block"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CIDR block for the VPC"&lt;/span&gt;
  &lt;span class="nx"&gt;default&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="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"public_subnet_cidr_block"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CIDR block for the public subnet"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"private_subnet_cidr_block"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CIDR block for the private subnet"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"ami_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AMI ID for the EC2 instance"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0c94855ba95c71c99"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"instance_type"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Instance type for the EC2 instance"&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of hardcoding values everywhere, I used variables for &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;subnet CIDRs&lt;/code&gt;, &lt;code&gt;AMI ID&lt;/code&gt;, &lt;code&gt;instance type&lt;/code&gt;. This makes the infrastructure easier to modify and reuse later.&lt;/p&gt;

&lt;p&gt;For instance, instead of repeating &lt;code&gt;"us-east-1"&lt;/code&gt; everywhere, we define it once in variables. Want to deploy to a different region? Change it in one place, not everywhere. Clean, reusable, professional. &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Step 9: Outputs — the receipt&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;# output the VPC id&lt;/span&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# output the public subnet id&lt;/span&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"public_subnet_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# output the private subnet id&lt;/span&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"private_subnet_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# EC2 Public IP&lt;/span&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ec2_public_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ip&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# EC2 Instance ID&lt;/span&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ec2_instance_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Terraform finishes, outputs are printed in your terminal — like a receipt. Instead of logging into AWS to find your VPC ID, Public IP, EC2 Instance ID, Terraform just hands it to you. That means you can immediately locate the server, connect to it and verify the deployment worked, without manually digging through the AWS console.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deploying the infrastructure
&lt;/h2&gt;

&lt;p&gt;Once everything was configured, I used the standard Terraform workflow:&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="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;     &lt;span class="c1"&gt;# Download the AWS provider and initialize Terraform&lt;/span&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;fmt&lt;/span&gt;      &lt;span class="c1"&gt;# Format the code neatly&lt;/span&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;validate&lt;/span&gt; &lt;span class="c1"&gt;# Check for configuration errors&lt;/span&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;     &lt;span class="c1"&gt;# Preview what Terraform will create&lt;/span&gt;
&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;    &lt;span class="c1"&gt;# Provision the infrastructure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when you're done experimenting:&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="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;destroy&lt;/span&gt;  &lt;span class="c1"&gt;# Tear everything down to avoid unnecessary AWS charges&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The full architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8kbvi57yhvxb4hvatrm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8kbvi57yhvxb4hvatrm.png" alt=" " width="800" height="867"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenges I faced
&lt;/h2&gt;

&lt;p&gt;Like every beginner, I made mistakes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using quotes incorrectly around variables inside &lt;code&gt;main.tf&lt;/code&gt; which broke my configuration&lt;/li&gt;
&lt;li&gt;Confusion around route tables and associations&lt;/li&gt;
&lt;li&gt;Understanding how IGW actually connects to subnets&lt;/li&gt;
&lt;li&gt;Debugging Terraform errors for the first time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But each error helped me understand Terraform and AWS networking better.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;I plan to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connecting to the EC2 instance via SSH&lt;/li&gt;
&lt;li&gt;Installing NGINX and hosting a simple webpage&lt;/li&gt;
&lt;li&gt;Terraform modules&lt;/li&gt;
&lt;li&gt;Remote state management with S3&lt;/li&gt;
&lt;li&gt;CI/CD pipelines with GitHub Actions&lt;/li&gt;
&lt;li&gt;Docker and container deployment
I'm still learning and still building. Follow along!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>aws</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Enhancing Cybersecurity in Healthcare: A NIST Cybersecurity Framework Assessment</title>
      <dc:creator>Benjamin Tetteh</dc:creator>
      <pubDate>Sat, 08 Mar 2025 19:10:57 +0000</pubDate>
      <link>https://dev.to/benjamin_tetteh/enhancing-cybersecurity-in-healthcare-a-nist-cybersecurity-framework-assessment-23kd</link>
      <guid>https://dev.to/benjamin_tetteh/enhancing-cybersecurity-in-healthcare-a-nist-cybersecurity-framework-assessment-23kd</guid>
      <description>&lt;p&gt;Cybersecurity threats are an ever-growing concern, especially in industries handling sensitive data like healthcare. To address these risks, I conducted a NIST Cybersecurity Framework (CSF) Assessment for a fictional mid-sized healthcare provider, MediHealth Solutions Inc., as part of my cybersecurity portfolio.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project Overview&lt;/strong&gt;&lt;br&gt;
The goal of this assessment was to evaluate MediHealth’s security posture, identify vulnerabilities, and recommend remediation strategies in alignment with NIST CSF and HIPAA requirements. The assessment covered key cybersecurity domains, including identifying assets, implementing protective measures, detecting threats, responding to incidents, and ensuring recovery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Findings&lt;/strong&gt;&lt;br&gt;
One of the major findings was the presence of legacy system risks. The organization relied on an outdated Electronic Health Records (EHR) system, increasing its exposure to unpatched vulnerabilities. To mitigate this risk, I recommended system upgrades and the deployment of automated patch management. Another critical issue was human factors in cybersecurity. A phishing simulation revealed that 30% of employees fell for phishing attempts, highlighting the need for increased awareness. I proposed a cybersecurity training program using platforms like KnowBe4 and GoPhish to educate employees on recognizing and avoiding phishing attacks.&lt;/p&gt;

&lt;p&gt;Additionally, I identified the absence of an Incident Response Plan (IRP) to handle ransomware and data breaches. Without a structured IRP, the organization risked delayed responses to security incidents. To address this, I developed a comprehensive IRP based on NIST SP 800-61 Rev. 2, outlining clear response procedures and implementing quarterly tabletop exercises to ensure readiness. Weak access controls were another major concern, as critical systems lacked Multi-Factor Authentication (MFA) and Role-Based Access Controls (RBAC). Enforcing MFA for all high-risk accounts and restricting access based on user roles significantly improved the security posture. Furthermore, the lack of centralized monitoring meant that the organization had no Security Information and Event Management (SIEM) system to detect and analyze threats in real-time. To remedy this, I recommended deploying SIEM tools such as Splunk or ELK Stack, along with Intrusion Detection Systems (IDS/IPS) to enhance threat detection and mitigation capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relevance to My Cybersecurity Journey&lt;/strong&gt;&lt;br&gt;
As a self-motivated cybersecurity enthusiast, this project was instrumental in refining my skills in risk assessment, incident response, compliance, and security control implementation. Conducting this assessment independently showcased my ability to analyze real-world cybersecurity threats, design security solutions, and align them with industry standards. This hands-on experience reinforced my understanding of security governance, risk management, and compliance (GRC), which are crucial skills for cybersecurity professionals. It also highlights my capability to work autonomously, proactively learn, and apply best practices in enterprise security.&lt;/p&gt;

&lt;p&gt;This project provided invaluable experience in conducting enterprise-wide cybersecurity assessments, aligning security controls with compliance frameworks, and implementing actionable security improvements. It reinforced the importance of a structured approach to risk management, proactive threat detection, and continuous cybersecurity awareness training. Cybersecurity is a constantly evolving field that requires a mix of technical expertise and risk-based decision-making. This NIST assessment has been a valuable addition to my cybersecurity portfolio, demonstrating my ability to analyze security gaps and implement industry-standard security measures.&lt;/p&gt;

&lt;p&gt;📌 Check out the full assessment &lt;a href="https://github.com/BenjaminTetteh/Cybersecurity-Portfolio/blob/main/Enterprise-wide%20NIST%20cybersecurity%20framework%20assessment.pdf" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;💬 Let’s discuss! Have you worked with the NIST Cybersecurity Framework before? How do you approach security risk assessments in your projects?&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>governance</category>
      <category>infosec</category>
      <category>compliance</category>
    </item>
    <item>
      <title>From Data Breach to Insight: Exploring the Intersection of Cybersecurity and Communication</title>
      <dc:creator>Benjamin Tetteh</dc:creator>
      <pubDate>Sat, 08 Mar 2025 18:22:00 +0000</pubDate>
      <link>https://dev.to/benjamin_tetteh/from-data-breach-to-insight-exploring-the-intersection-of-cybersecurity-and-communication-1f3b</link>
      <guid>https://dev.to/benjamin_tetteh/from-data-breach-to-insight-exploring-the-intersection-of-cybersecurity-and-communication-1f3b</guid>
      <description>&lt;p&gt;I recently received an email notifying me of a data breach at a major public service provider in London that I rely on. Before this, I never truly considered that I could be directly impacted by a breach, even though they’re frequently reported in the media. With incidents like these becoming increasingly common, it was unsettling to think that my personal data may have been compromised. However, the service provider has been proactive, sending follow-up emails in the weeks following the initial notification, outlining the incident and their remediation efforts. As a communications professional, I found their response reassuring.&lt;/p&gt;

&lt;p&gt;Data breaches are becoming so frequent that they’re starting to feel like notifications from my telecom provider—constant, annoying, and impossible to ignore.&lt;/p&gt;

&lt;p&gt;Coincidentally, I’d been enrolled in the Google Cybersecurity Certificate course. My initial foray into cybersecurity was driven by the assumption that I would be diving into a highly technical world—one filled with firewalls, encryption, and endless lines of code, likely while wearing a hoodie in a dark room. While I certainly encountered that (minus the hoodie), what piqued my interest was discovering how crucial communication is within the cybersecurity landscape.&lt;/p&gt;

&lt;p&gt;In today's hyper-connected world, cybersecurity has become one of the most critical aspects of every organization's operational strategy. It’s not just about protecting sensitive data or ensuring business continuity; it’s also about building and maintaining trust with customers. I’ve come to realize that cybersecurity is more than just a technical responsibility; it’s also a communications challenge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Intersection&lt;/strong&gt;&lt;br&gt;
Why? Because no matter how sophisticated an organization's defenses are, human error remains one of the most significant risks. This is where effective communication plays a pivotal role. While studying incident response plans, I realized that when a data breach occurs, it’s not just the IT team scrambling behind the scenes to secure systems and data. The communications team is equally essential in ensuring that stakeholders—whether customers, employees, or partners—are informed and reassured. Organizations are legally obligated to disclose breaches, and the quality, clarity, and timeliness of that communication often determine whether trust is preserved or lost. Cybersecurity professionals may patch vulnerabilities and mitigate future risks, but without clear, strategic communication, even the best technical response can leave people in the dark and cause unnecessary panic.&lt;/p&gt;

&lt;p&gt;One of the most critical elements of cybersecurity is awareness. Many threats—from phishing to social engineering—target the weakest link: people. Ensuring that employees and stakeholders understand the risks and how to avoid them requires more than a one-time memo or a check-the-box training module. It requires consistent, clear, and engaging communication. By translating complex technical concepts into easily understandable information, communicators can help create a culture of security. This extends to everything from regular awareness campaigns to engaging content that demystifies topics like password security, device protection, and data privacy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gaining Technical Expertise: The Next Frontier&lt;/strong&gt;&lt;br&gt;
On the flip side, my experience with the technical aspects of cybersecurity has also been eye-opening. Through the Google Cybersecurity Certificate, I’ve gained hands-on experience with tools like Python, Linux, and SQL, and I’ve worked with Security Information and Event Management (SIEM) tools to identify risks and mitigate threats. Understanding these technologies has allowed me to better appreciate the technical side of cybersecurity.&lt;/p&gt;

&lt;p&gt;My observations and learnings from the past weeks have made me appreciate the relationship between cybersecurity and communications. Both disciplines require a keen understanding of risk, an ability to anticipate and mitigate problems, and a focus on protecting people—whether through securing data or ensuring that information is clear and accessible. As I continue to explore both fields, I’m excited by the possibilities that lie at this intersection.&lt;/p&gt;

&lt;p&gt;On to the next.&lt;/p&gt;

&lt;p&gt;PS: I first published this article on my LinkedIn profile on 16/9/24.&lt;/p&gt;

</description>
      <category>security</category>
      <category>awareness</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
