<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://tatemccauley.com/">
  <title>Tate McCauley</title>
  <subtitle>Tate McCauley is a Forward Deployed Engineer writing about security, compliance, and building AI-native tools for FedRAMP and GRC.</subtitle>
  <link href="https://tatemccauley.com/feed.xml" rel="self"/>
  <link href="https://tatemccauley.com/"/>
  <updated>2026-07-10T00:00:00Z</updated>
  <id>https://tatemccauley.com/</id>
  <author>
    <name>Tate McCauley</name>
    <uri>https://tatemccauley.com/about/</uri>
  </author>
  <entry>
    <title>GRC Is Not Boring</title>
    <link href="https://tatemccauley.com/posts/grc-is-not-boring/"/>
    <published>2026-07-10T00:00:00Z</published>
    <updated>2026-07-10T00:00:00Z</updated>
    <id>https://tatemccauley.com/posts/grc-is-not-boring/</id>
    <summary>I spent two internships convinced GRC was the &#39;boring&#39; side of security. Then I went to work on it full time at Paramify and found the opposite: real tools, real problems, and a field being rebuilt around AI. This is the start of a series on why.</summary>
    <content type="html">&lt;h1&gt;GRC Is Not Boring&lt;/h1&gt;
&lt;p&gt;Ask a room full of security people what they think of GRC, and you can watch the energy leave the room. Governance, risk, and compliance is the part of the field everyone pictures as spreadsheets, screenshots, and a checklist that lands in your inbox once a quarter. It&#39;s the work that sounds like homework. If you got into security to break systems or defend them, GRC is the corner you were quietly hoping to avoid.&lt;/p&gt;
&lt;p&gt;A year ago, I would have nodded along.&lt;/p&gt;
&lt;p&gt;In my first series on this blog, I wrote about seeing security from two sides: the auditor and the analyst, the building inspector and the firefighter. I cast GRC as the inspector. Important, but a step back from the action. Careful, methodical, and if I&#39;m honest, a little dry.&lt;/p&gt;
&lt;p&gt;Then I graduated and went to work on the inspector&#39;s side of the coin full time, building compliance tooling at Paramify. I expected paperwork. What I found was the most interesting engineering problem I&#39;ve worked on.&lt;/p&gt;
&lt;h2&gt;What the Work Actually Looks Like&lt;/h2&gt;
&lt;p&gt;My week is not spreadsheets. I build tools that reach into dozens of cloud accounts and pull back the evidence that proves a system is configured the way it&#39;s supposed to be. I&#39;ve written agents that wake up on their own, notice a control has started failing, work out what changed, and write up why. The problems underneath are the same ones the rest of security cares about: handling credentials safely at scale, proving a system is actually in the state it claims to be in, and doing it continuously instead of once a quarter.&lt;/p&gt;
&lt;p&gt;None of that is homework. It&#39;s distributed systems, automation, and increasingly AI. It just happens to live under a label most engineers wrote off years ago.&lt;/p&gt;
&lt;h2&gt;Why Now&lt;/h2&gt;
&lt;p&gt;The timing isn&#39;t an accident either. Compliance is going through the biggest shift it&#39;s had in years, and most of the industry hasn&#39;t noticed yet.&lt;/p&gt;
&lt;p&gt;For a long time, compliance meant working through a long list of prescriptive controls and writing a document for each one. FedRAMP 20x, the newest version of the U.S. government&#39;s cloud authorization program, throws a lot of that out. Instead of asking whether you wrote a policy, it asks whether you can actually demonstrate a security outcome in a way a machine can check.&lt;/p&gt;
&lt;p&gt;That one change turns compliance from a writing exercise into an engineering one. If the answer has to be machine-checkable, someone has to build the thing that checks it. That someone increasingly looks like a software engineer, and more and more like an AI agent.&lt;/p&gt;
&lt;p&gt;I&#39;ll spend a whole post on FedRAMP 20x soon, because it earns one.&lt;/p&gt;
&lt;h2&gt;Why I&#39;m Writing This&lt;/h2&gt;
&lt;p&gt;Partly because I think I&#39;m early to something, and early is interesting. Partly because I&#39;m learning as I go. GRC turned out to be a great place to learn, because the problems are real and the field is wide open.&lt;/p&gt;
&lt;p&gt;Mostly, though, I want to change a few minds. If you&#39;ve been avoiding this corner of security, I don&#39;t think you&#39;re seeing it clearly.&lt;/p&gt;
&lt;h2&gt;What&#39;s Coming Next&lt;/h2&gt;
&lt;p&gt;Over the next few posts I&#39;ll make the case and show my work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;why FedRAMP 20x is the most interesting thing happening in compliance, and what it actually changes&lt;/li&gt;
&lt;li&gt;the tools I get to build, and the real problems hiding inside the &amp;quot;boring&amp;quot; work&lt;/li&gt;
&lt;li&gt;where all of this goes as it becomes AI-native, and what I&#39;ve learned trying to build for AI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;This kicks off a new series that picks up where &lt;a href=&quot;/posts/the-two-sides-of-the-security-coin/&quot;&gt;The Two Sides of the Cybersecurity Coin&lt;/a&gt; left off, this time from inside the GRC side of the coin.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a Real-Time Chat App: Lessons from Cloud Architecture</title>
    <link href="https://tatemccauley.com/posts/building-a-real-time-chat-app/"/>
    <published>2025-12-31T00:00:00Z</published>
    <updated>2025-12-31T00:00:00Z</updated>
    <id>https://tatemccauley.com/posts/building-a-real-time-chat-app/</id>
    <summary>From AWS acronyms to a working serverless chat application, and what I learned about state, IAM, and modern development tools.</summary>
    <content type="html">&lt;h1&gt;Building a Real-Time Chat App: Lessons from Cloud Architecture&lt;/h1&gt;
&lt;p&gt;Over the past few months, I took a class on cloud architecture. As a senior in the cybersecurity program at BYU, we don&#39;t always spend a lot of time on pure software engineering or complex cloud architecture, so I was curious how the content I learned in this class would tie into the rest of my curriculum.&lt;/p&gt;
&lt;p&gt;For the first few weeks, we went over what felt like a million different AWS acronyms. Knowing when to use one type of compute over another felt very theoretical, and I struggled to have a firm grasp on how to leverage these technologies practically. The final assignment was to create a &amp;quot;cookie-cutter&amp;quot; VPC for a hypothetical business using AWS Academy accounts. While useful, the restrictions meant we couldn&#39;t implement many features ourselves.&lt;/p&gt;
&lt;p&gt;Fortunately, we were given the option to build our own project of a similar scale. I saw this as an opportunity to finally get hands-on and understand how AWS services actually work together.&lt;/p&gt;
&lt;h2&gt;The Goal: Real-Time Communication on a Student Budget&lt;/h2&gt;
&lt;p&gt;I decided to build a real-time chat application. It felt like a project that was just complicated enough to be impressive, but simple enough to demonstrate effectively.&lt;/p&gt;
&lt;p&gt;I had two main constraints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Serverless Architecture&lt;/strong&gt;: I didn&#39;t want to manage EC2 instances or worry about patching servers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost&lt;/strong&gt;: It had to be as cheap as possible (ideally free).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AWS offers a generous (to me) free tier, and since I wasn&#39;t expecting thousands of users, a serverless approach using Lambda and DynamoDB seemed perfect. The free tier includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API Gateway&lt;/strong&gt;: 1M messages/month free&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lambda&lt;/strong&gt;: 1M requests + 400K GB-seconds/month free&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB&lt;/strong&gt;: 25GB storage + 25 read/write units free&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the frontend, I stuck to my preferred stack of React + Vite.&lt;/p&gt;
&lt;h2&gt;The Architecture&lt;/h2&gt;
&lt;p&gt;Deciding how to architect this was the first big challenge. Real-time communication usually requires a persistent connection between the client and server. In a traditional setup, you might run a Node.js server with Socket.io. But in a serverless world, Lambda functions are ephemeral. They spin up, do a job, and die. They can&#39;t hold a WebSocket connection open.&lt;/p&gt;
&lt;p&gt;The solution was AWS API Gateway v2 with WebSocket support.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    Client[React Frontend] --&amp;gt;|WebSocket wss://| APIG[API Gateway]
    APIG --&amp;gt;|Messages| Client
    
    subgraph AWS[&amp;quot;AWS Cloud&amp;quot;]
        APIG --&amp;gt;|Connect| Auth[Lambda: Authorizer]
        APIG --&amp;gt;|OnConnect| L_Conn[Lambda: onConnect]
        APIG --&amp;gt;|OnDisconnect| L_Disconn[Lambda: onDisconnect]
        APIG --&amp;gt;|SendMessage| L_Msg[Lambda: sendMessage]
        APIG --&amp;gt;|GetHistory| L_Msg
        
        L_Conn --&amp;gt;|Save Connection ID| DDB_Conn[(&amp;quot;DynamoDB: Connections&amp;quot;)]
        L_Disconn --&amp;gt;|Remove Connection ID| DDB_Conn
        
        L_Msg --&amp;gt;|Save Msg| DDB_Msg[(&amp;quot;DynamoDB: Messages&amp;quot;)]
        L_Msg --&amp;gt;|Read Active Users| DDB_Conn
        L_Msg --&amp;gt;|Broadcast via| APIG
        L_Msg --&amp;gt;|Query via GSI| DDB_Msg
    end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1. The Gateway (The &amp;quot;Doorman&amp;quot;)&lt;/h3&gt;
&lt;p&gt;API Gateway acts as the persistent layer. It holds the WebSocket connection with the user. When a user sends a message, API Gateway triggers a Lambda function based on the route selection expression.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;route_selection_expression = &amp;quot;$request.body.action&amp;quot;&lt;/code&gt; tells API Gateway to route messages based on an &lt;code&gt;action&lt;/code&gt; field in the JSON body. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Frontend sends: { action: &#39;sendMessage&#39;, message: &#39;Hello&#39; }
// Frontend sends: { action: &#39;getHistory&#39; }
// API Gateway routes based on the action field
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Terraform, setting this up was surprisingly concise. Instead of clicking through a hundred console menus, I defined the API protocol explicitly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;# terraform/api-gateway.tf

resource &amp;quot;aws_apigatewayv2_api&amp;quot; &amp;quot;websocket_api&amp;quot; {
  name                       = &amp;quot;${var.project_name}-websocket&amp;quot;
  protocol_type              = &amp;quot;WEBSOCKET&amp;quot;
  route_selection_expression = &amp;quot;$request.body.action&amp;quot;

  tags = {
    Name        = &amp;quot;${var.project_name}-websocket-api&amp;quot;
    Environment = var.stage_name
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. The Database (DynamoDB)&lt;/h3&gt;
&lt;p&gt;I needed two tables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Connections Table&lt;/strong&gt;: Stores the connectionId of every user currently online.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Messages Table&lt;/strong&gt;: Stores the chat history.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I used &lt;code&gt;PAY_PER_REQUEST&lt;/code&gt; billing mode for both tables, which is perfect for unpredictable student project traffic. No need to provision capacity or worry about throttling.&lt;/p&gt;
&lt;p&gt;One interesting optimization I learned was using Global Secondary Indexes (GSIs). To load chat history efficiently, I needed to grab the last 50 messages sorted by time. DynamoDB is a key-value store, not a relational database, so &amp;quot;SELECT * ORDER BY time&amp;quot; isn&#39;t free.&lt;/p&gt;
&lt;p&gt;The trick is using a constant partition key. I set &lt;code&gt;messageType=&#39;chat&#39;&lt;/code&gt; for all messages, which allows me to query all messages sorted by timestamp. The GSI uses &lt;code&gt;messageType&lt;/code&gt; as the partition key and &lt;code&gt;timestamp&lt;/code&gt; as the sort key:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;# terraform/dynamodb.tf

  global_secondary_index {
    name            = &amp;quot;TimestampIndex&amp;quot;
    hash_key        = &amp;quot;messageType&amp;quot;    # Partition key (constant: &#39;chat&#39;)
    range_key       = &amp;quot;timestamp&amp;quot;      # Sort key (allows time-based queries)
    projection_type = &amp;quot;ALL&amp;quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pattern lets me efficiently query: &amp;quot;Give me all chat messages sorted by timestamp, limit 50.&amp;quot;&lt;/p&gt;
&lt;h3&gt;3. The Logic (Lambda)&lt;/h3&gt;
&lt;p&gt;The hardest part was the logic for broadcasting messages. Since Lambda doesn&#39;t &amp;quot;know&amp;quot; who is connected, it has to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read all active connectionIds from DynamoDB.&lt;/li&gt;
&lt;li&gt;Loop through them.&lt;/li&gt;
&lt;li&gt;Post the message back to API Gateway for each connection.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I also optimized for container reuse by creating AWS SDK clients outside the handler function. Lambda containers can be reused across invocations, so this avoids recreating clients on every request—a small optimization that reduces latency and overhead.&lt;/p&gt;
&lt;p&gt;Here is a snippet of the broadcasting logic I wrote in Node.js:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// lambda/sendMessage.js

const broadcastMessage = async (apiGatewayClient, connectionsTable, messageData) =&amp;gt; {
  // Get all active connections
  const result = await dynamodb.send(new ScanCommand({
    TableName: connectionsTable
  }));

  // Broadcast to all connections
  const broadcastPromises = result.Items.map(async (connection) =&amp;gt; {
    try {
      await apiGatewayClient.send(new PostToConnectionCommand({
        ConnectionId: connection.connectionId,
        Data: JSON.stringify(messageData)
      }));
    } catch (error) {
      // If a user disconnected without telling us (410 Gone), clean up the DB
      if (error.statusCode === 410 || error.name === &#39;GoneException&#39;) {
        console.log(`Removing stale connection: ${connection.connectionId}`);
        await dynamodb.send(new DeleteCommand({
          TableName: connectionsTable,
          Key: { connectionId: connection.connectionId }
        }));
      }
    }
  });
  
  await Promise.all(broadcastPromises);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Error handling was crucial. I also implemented:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Message length validation (1000 character limit)&lt;/li&gt;
&lt;li&gt;Handling of malformed JSON&lt;/li&gt;
&lt;li&gt;Fallback to Scan if GSI query fails&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. Message History (The getHistory Route)&lt;/h3&gt;
&lt;p&gt;A major feature I initially overlooked was loading chat history when users connect. The &lt;code&gt;getHistory&lt;/code&gt; route uses the same &lt;code&gt;sendMessage&lt;/code&gt; Lambda function but handles a different action. When triggered, it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uses the GSI to efficiently fetch the last 50 messages&lt;/li&gt;
&lt;li&gt;Automatically loads when users first connect&lt;/li&gt;
&lt;li&gt;Returns messages sorted by timestamp (newest first)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This route is triggered when the frontend sends &lt;code&gt;{ action: &#39;getHistory&#39; }&lt;/code&gt;. The Lambda queries the Messages table using the GSI with &lt;code&gt;messageType=&#39;chat&#39;&lt;/code&gt; and sorts by timestamp, then returns the results to the client. Without this, users would only see messages sent after they joined—not ideal for a chat app.&lt;/p&gt;
&lt;h3&gt;5. Security: The Authorizer&lt;/h3&gt;
&lt;p&gt;Security was a critical consideration. I implemented server-side validation in both the authorizer and &lt;code&gt;onConnect&lt;/code&gt; Lambda (defense in depth):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Username validation&lt;/strong&gt;: 2-20 characters, alphanumeric plus spaces/hyphens/underscores&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XSS pattern detection&lt;/strong&gt;: Blocks &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, &lt;code&gt;javascript:&lt;/code&gt;, and other injection patterns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query string validation&lt;/strong&gt;: The authorizer validates usernames from query strings before connections are established, preventing malicious usernames from even reaching the connection handler&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The authorizer runs before any connection is established, so invalid usernames are rejected at the gateway level.&lt;/p&gt;
&lt;h2&gt;Infrastructure as Code: Why Terraform?&lt;/h2&gt;
&lt;p&gt;I wanted to use this project to learn Terraform, and it turned out to be the best decision I made.&lt;/p&gt;
&lt;p&gt;In the AWS Console, if you delete a Lambda function by mistake, you have to remember exactly how you configured the permissions, the environment variables, and the triggers. With Terraform, I just ran &lt;code&gt;terraform apply&lt;/code&gt; and my entire infrastructure, database, permissions (IAM), API routes, and functions, was rebuilt in minutes.&lt;/p&gt;
&lt;p&gt;An obstacle that came up that I was not expecting was how to manage Terraform state across devices. I use a different computer at home than when I am at school, which quickly led to issues with tracking the state when switching devices.&lt;/p&gt;
&lt;p&gt;What I came up with was using an S3 bucket as a remote state. I also created a DynamoDB table that would track if someone was actively making changes and apply a state lock. It might not be the most relevant thing for a solo developer, but it was simple enough to implement and felt like the &amp;quot;correct&amp;quot; way to engineer it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;# terraform/backend.tf
# Note: Bucket name is an example - use your own unique bucket name

terraform {
  backend &amp;quot;s3&amp;quot; {
    bucket         = &amp;quot;your-terraform-state-bucket&amp;quot;
    key            = &amp;quot;chat-app/terraform.tfstate&amp;quot;
    region         = &amp;quot;us-east-1&amp;quot;
    dynamodb_table = &amp;quot;terraform-state-lock&amp;quot; # Handles the locking
    encrypt        = true
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;IAM Permissions: The Hidden Complexity&lt;/h3&gt;
&lt;p&gt;Getting components to talk to each other requires very specific IAM permissions. Here&#39;s a brief example of what the Lambda functions need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;# Example IAM policy for sendMessage Lambda
# Note: In production, use specific ARNs instead of wildcards where possible
{
  &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
  &amp;quot;Action&amp;quot;: [
    &amp;quot;dynamodb:PutItem&amp;quot;,
    &amp;quot;dynamodb:GetItem&amp;quot;,
    &amp;quot;dynamodb:DeleteItem&amp;quot;,
    &amp;quot;dynamodb:Scan&amp;quot;,
    &amp;quot;dynamodb:Query&amp;quot;,
    &amp;quot;execute-api:ManageConnections&amp;quot;
  ],
  &amp;quot;Resource&amp;quot;: [
    &amp;quot;arn:aws:dynamodb:us-east-1:123456789012:table/Connections&amp;quot;,
    &amp;quot;arn:aws:dynamodb:us-east-1:123456789012:table/Messages&amp;quot;,
    &amp;quot;arn:aws:execute-api:us-east-1:123456789012:*/@connections/*&amp;quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each Lambda needs explicit permissions for every AWS service it touches, and the resource ARNs must be precise (not wildcards where possible for security).&lt;/p&gt;
&lt;h2&gt;The Frontend: Balancing Design and Development Speed&lt;/h2&gt;
&lt;p&gt;I admit, I am a sucker for frontend design. Even though this was a cloud architecture project, I wanted it to look good—but I also didn&#39;t want to spend days tweaking CSS when the real learning was in the backend architecture.&lt;/p&gt;
&lt;p&gt;This is where AI tools like Cursor, V0, and Figma AI became force multipliers for a solo developer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Design exploration&lt;/strong&gt;: I used V0 to generate a &amp;quot;warm minimalism&amp;quot; UI based on Radix UI components. Instead of starting from a blank canvas, I could iterate on something that already looked professional.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure boilerplate&lt;/strong&gt;: Writing Terraform IAM policies is tedious and error-prone. I&#39;d prompt Cursor with &amp;quot;Give me a Terraform policy that allows this Lambda to write to this specific DynamoDB table,&amp;quot; turning 20 minutes of documentation hunting into 30 seconds of code review.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This experience taught me something valuable: AI tools are best used for generating boilerplate and exploring design options, not for understanding complex system interactions. I still needed to deeply understand WebSocket lifecycles, DynamoDB access patterns, and IAM permissions to debug when things went wrong.&lt;/p&gt;
&lt;h2&gt;Building a Robust WebSocket Connection: The Frontend&#39;s Role in a Serverless Architecture&lt;/h2&gt;
&lt;p&gt;While I spent most of my time architecting the backend, I quickly realized the frontend needed to be just as sophisticated. In a traditional server setup with Socket.io, the server handles much of the connection lifecycle. In a serverless world, the frontend carries more responsibility.&lt;/p&gt;
&lt;h3&gt;The Reconnection Challenge: When Lambda Functions Come and Go&lt;/h3&gt;
&lt;p&gt;WebSocket connections can drop—users close their laptops, switch networks, or Lambda cold starts cause temporary delays. Unlike a traditional Node.js server that maintains long-lived connections, API Gateway needs the client to be resilient.&lt;/p&gt;
&lt;p&gt;I implemented automatic reconnection with exponential backoff: wait 3 seconds after the first failure, then 6, then 12, up to a maximum of 5 attempts.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Exponential backoff: 3s, 6s, 12s, 24s, 48s
const delay = reconnectInterval * Math.pow(2, reconnectCountRef.current - 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But there&#39;s a React-specific gotcha: WebSocket event handlers can capture stale state if you&#39;re not careful. I used &lt;code&gt;useRef&lt;/code&gt; to store the WebSocket instance and callback functions, ensuring handlers always have fresh data—a pattern I had to learn the hard way after messages started duplicating.&lt;/p&gt;
&lt;h3&gt;State Management: The Frontend as the &amp;quot;Memory&amp;quot;&lt;/h3&gt;
&lt;p&gt;Remember how Lambda functions are stateless? The frontend needs to maintain its own state too. When a username changes, the WebSocket must reconnect with new credentials. But React&#39;s &lt;code&gt;useEffect&lt;/code&gt; can fire multiple times during renders, so I had to carefully clean up old connections before creating new ones.&lt;/p&gt;
&lt;p&gt;This was a good reminder: serverless doesn&#39;t mean stateless everywhere, you&#39;re just moving where that state lives.&lt;/p&gt;
&lt;h3&gt;Message Validation &amp;amp; Error Handling&lt;/h3&gt;
&lt;p&gt;The frontend validates every message it receives. Just because it came over the WebSocket doesn&#39;t mean it&#39;s trustworthy. Each message must have a valid timestamp, username, and message content. Invalid messages are silently filtered out rather than crashing the UI.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Validate message before adding to state
if (!message.timestamp || !message.username || !message.message) {
  console.warn(&#39;Received invalid message format:&#39;, message)
  return // Skip this message
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Automatic History Loading&lt;/h3&gt;
&lt;p&gt;When a user connects, the frontend automatically requests the last 50 messages from the server. This happens immediately after the WebSocket connection opens, before the user even types anything. The Lambda function uses that DynamoDB GSI we discussed earlier to efficiently fetch recent messages sorted by timestamp.&lt;/p&gt;
&lt;h3&gt;Connection Status UX&lt;/h3&gt;
&lt;p&gt;The UI shows real-time connection status: connecting, connected, disconnected, or error. This transparency was crucial during development—seeing &amp;quot;error&amp;quot; immediately told me if my backend deployment failed. In production, it helps users understand when they&#39;ve lost connectivity.&lt;/p&gt;
&lt;p&gt;The frontend turned out to be more complex than I expected. Managing WebSocket lifecycle, handling reconnections, validating data, and providing good UX feedback required more state management logic than a simple chat interface might suggest.&lt;/p&gt;
&lt;h2&gt;The Missing Features You Don&#39;t Think About&lt;/h2&gt;
&lt;p&gt;When building a real-time chat app, there are several features that seem obvious in hindsight but aren&#39;t immediately apparent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Message history loading&lt;/strong&gt;: Users expect to see previous messages when they join. The &lt;code&gt;getHistory&lt;/code&gt; route and GSI pattern make this efficient.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stale connection cleanup&lt;/strong&gt;: Users can disconnect without properly closing the WebSocket (closed browser, network issues). Handling 410 Gone errors and cleaning up the database prevents memory leaks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-side username validation&lt;/strong&gt;: Never trust the client. The authorizer validates usernames before connections are established, preventing XSS and injection attacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Route-based message routing&lt;/strong&gt;: Using the &lt;code&gt;action&lt;/code&gt; field in the message body allows a single WebSocket connection to handle multiple types of operations (sendMessage, getHistory, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What I Learned&lt;/h2&gt;
&lt;p&gt;Building this app bridged the gap between the &amp;quot;million acronyms&amp;quot; I learned in class and actual engineering.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;State is hard in Serverless&lt;/strong&gt;: You take for granted that a server knows who is connected. In Lambda, you have to build that &amp;quot;memory&amp;quot; yourself using a database.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing WebSocket Lambda functions is challenging&lt;/strong&gt;: You can&#39;t easily test WebSocket connections locally. I had to deploy to AWS to test, which slowed down the development cycle. I created test scripts using event payloads to simulate API Gateway invocations, but they still required the full infrastructure to be running.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modern Dev Tools&lt;/strong&gt;: Combining Terraform for the backend and AI-assisted coding for the frontend allowed me to build a project that would have taken a team of three a few weeks to build in just a fraction of the time.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>The Art of Triage</title>
    <link href="https://tatemccauley.com/posts/the-art-of-triage/"/>
    <published>2025-10-16T00:00:00Z</published>
    <updated>2025-10-16T00:00:00Z</updated>
    <id>https://tatemccauley.com/posts/the-art-of-triage/</id>
    <summary>When every alert looks urgent, how do you decide what&#39;s truly dangerous? A look at the most critical skill for a SOC analyst: triage.</summary>
    <content type="html">&lt;h1&gt;The Art of Triage: Prioritizing Threats in the SOC&lt;/h1&gt;
&lt;p&gt;In a busy Security Operations Center (SOC), the biggest challenge isn&#39;t just investigating alerts—it&#39;s knowing &lt;em&gt;which&lt;/em&gt; ones to investigate first. When I started at Big West Oil, my Taegis XDR dashboard was a constant flood of information. If you try to treat every &amp;quot;High&amp;quot; severity alert with the same level of panic, you&#39;ll burn out in an hour and, worse, you&#39;ll miss the &lt;em&gt;truly&lt;/em&gt; critical threat hiding in the noise.&lt;/p&gt;
&lt;p&gt;This is the most essential and stressful skill of a SOC analyst: &lt;strong&gt;the art of triage&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It&#39;s not a simple checklist. It&#39;s a rapid-fire decision-making process that balances a few key questions to determine the answer to the most important one: &amp;quot;What do I have to work on &lt;em&gt;right now&lt;/em&gt;?&amp;quot;&lt;/p&gt;
&lt;h2&gt;1. How Bad is the Threat? (Severity)&lt;/h2&gt;
&lt;p&gt;This is the most obvious starting point. My tools did a lot of the initial work for me. Taegis XDR would flag an alert as &amp;quot;High,&amp;quot; &amp;quot;Medium,&amp;quot; or &amp;quot;Low&amp;quot; based on the nature of the detection.&lt;/p&gt;
&lt;p&gt;A &amp;quot;High&amp;quot; alert, like a potential ransomware signature or a known command-and-control (C2) callback, is almost always going to jump to the top of the list. A &amp;quot;Low&amp;quot; alert, like a single failed login, is less concerning on its own. But this is just the first piece of the puzzle.&lt;/p&gt;
&lt;h2&gt;2. Where is it Happening? (Asset Criticality)&lt;/h2&gt;
&lt;p&gt;This, I learned, is the most important question. An alert&#39;s true priority is a combination of its &lt;strong&gt;Severity&lt;/strong&gt; and the &lt;strong&gt;Criticality of the asset&lt;/strong&gt; it&#39;s on.&lt;/p&gt;
&lt;p&gt;To be an effective analyst, you &lt;em&gt;must&lt;/em&gt; understand the business. Is the alert on a critical domain controller or a server hosting the company&#39;s financial data? Or is it on a guest laptop on the public Wi-Fi?&lt;/p&gt;
&lt;p&gt;A &amp;quot;Medium&amp;quot; severity alert on a domain controller is &lt;strong&gt;infinitely more urgent&lt;/strong&gt; than a &amp;quot;High&amp;quot; severity alert on an intern&#39;s old laptop. This is where my GRC background from FJ Management gave me a massive head start. I already understood the &amp;quot;crown jewels&amp;quot; of the business. This let me see an asset as more than just an IP.&lt;/p&gt;
&lt;h2&gt;3. What&#39;s the Context? (Correlation)&lt;/h2&gt;
&lt;p&gt;No alert exists in a vacuum. A single, isolated alert is rarely a five-alarm fire. The real danger is in the &lt;em&gt;pattern&lt;/em&gt;. This is where my investigative skills from the previous week came into play.&lt;/p&gt;
&lt;p&gt;Is this &amp;quot;Unusual Login&amp;quot; alert an isolated event? Or was it preceded by a &amp;quot;Phishing Email Clicked&amp;quot; alert and followed by a &amp;quot;Malicious PowerShell Command&amp;quot; alert, all from the same user?&lt;/p&gt;
&lt;p&gt;That chain of events tells a story. My job in triage was to quickly see if an alert was a standalone event or one chapter in a much scarier story. This is what triage is all about: using my tools and business knowledge to separate the signal from the noise, fast.&lt;/p&gt;
&lt;h2&gt;The Final Decision&lt;/h2&gt;
&lt;p&gt;In just a few minutes, I had to make a call. Was this a false positive I could close? Was it a minor issue I could investigate when I had time? Or was this a true positive on a critical asset—a &amp;quot;drop everything and call my manager&amp;quot; moment?&lt;/p&gt;
&lt;p&gt;Getting this right is the core of the job. It&#39;s what stops a small intrusion from becoming a front-page data breach.&lt;/p&gt;
&lt;h2&gt;What&#39;s Coming Next&lt;/h2&gt;
&lt;p&gt;This daily act of balancing GRC context with SOC threats led to the biggest insight of my entire internship. &lt;strong&gt;Next week&lt;/strong&gt;, I&#39;ll share that &amp;quot;Aha!&amp;quot; moment—how my audit experience made me a better analyst, and how my time in the SOC made me a better auditor.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series about my cybersecurity internship experiences. Read the previous posts to catch up on the journey so far.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The Digital Detective: A Day in the Life of a SOC Analyst</title>
    <link href="https://tatemccauley.com/posts/SOC-day-in-the-life/"/>
    <published>2025-09-19T00:00:00Z</published>
    <updated>2025-09-19T00:00:00Z</updated>
    <id>https://tatemccauley.com/posts/SOC-day-in-the-life/</id>
    <summary>From the initial alert to the final verdict, a look at the investigative process of a SOC analyst hunting for threats in a sea of data.</summary>
    <content type="html">&lt;h1&gt;The Digital Detective: Investigating with Darktrace and Taegis XDR&lt;/h1&gt;
&lt;p&gt;Moving into the Security Operations Center (SOC) at Big West Oil meant shifting from theory to practice. My daily work revolved around two core platforms: &lt;strong&gt;Taegis XDR&lt;/strong&gt; for alerts and endpoint data, and &lt;strong&gt;Darktrace&lt;/strong&gt; for network visibility. My job was to be a digital detective, using these tools to investigate the constant stream of potential threats.&lt;/p&gt;
&lt;p&gt;Every day brought a new set of puzzles. While each case was unique, the investigative process followed a clear, structured path.&lt;/p&gt;
&lt;h2&gt;The First Clue: The Alert&lt;/h2&gt;
&lt;p&gt;An investigation always started with a single alert in Taegis XDR. I saw a wide variety of them, but the most common were related to user activity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Potential Phishing Attempts:&lt;/strong&gt; A user account suddenly trying to access a known malicious domain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password Spraying:&lt;/strong&gt; A series of failed login attempts against multiple accounts from a single IP address.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unusual Login Attempts:&lt;/strong&gt; A successful login from a geographic location where we have no employees.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These alerts were the first clue—the &amp;quot;tip-off&amp;quot;—that something was wrong. My job was to figure out if it was a real threat or just a false alarm.&lt;/p&gt;
&lt;h2&gt;Building the Case File&lt;/h2&gt;
&lt;p&gt;Once an alert came in, I would immediately begin to build context around it. My investigation process involved three key questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Who and What is Involved?&lt;/strong&gt; Taegis XDR would show me the associated user and their device. The first step was understanding the context. Is this a server that should have very predictable behavior, or is it a developer&#39;s laptop with more varied activity?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are There Other Clues?&lt;/strong&gt; I would then pivot to look for other alerts or events associated with that same user or device. A single unusual login might be a false positive. But an unusual login, followed by a &amp;quot;Potential Phishing&amp;quot; alert, followed by a &amp;quot;Malicious Process Detected&amp;quot; alert starts to paint a very clear picture of a compromise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Is This Behavior Expected?&lt;/strong&gt; By combining these pieces of evidence, I could usually determine if the activity was expected or not. An engineer on vacation in Europe might trigger an unusual login alert, which is expected behavior. That same alert on a random Tuesday from an accountant&#39;s account is a major red flag.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Delivering the Verdict&lt;/h2&gt;
&lt;p&gt;After gathering the evidence, I had to deliver a verdict. If the activity was deemed legitimate, I would document my findings and close the alert, sometimes tuning the rule to reduce future noise.&lt;/p&gt;
&lt;p&gt;If it was a real threat, I would immediately escalate it to my manager. From there, we would work together to determine the next steps for containment and remediation. This hands-on process of finding the signal in the noise was the core of my role as an analyst. It was a thrilling challenge to piece together disparate logs and alerts to uncover the full story.&lt;/p&gt;
&lt;h2&gt;What&#39;s Coming Next&lt;/h2&gt;
&lt;p&gt;Investigating alerts is one thing, but knowing which ones to investigate &lt;em&gt;first&lt;/em&gt; is a completely different skill. &lt;strong&gt;Next week&lt;/strong&gt;, I&#39;ll cover &amp;quot;The Art of Triage&amp;quot; and how a SOC analyst prioritizes threats when everything seems urgent.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series about my cybersecurity internship experiences. Read the &lt;a href=&quot;/posts/the-two-sides-of-the-security-coin/&quot;&gt;first post&lt;/a&gt; to understand the context of my journey through both GRC and Security Operations roles.&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>From Auditing Firewalls to Defending Them</title>
    <link href="https://tatemccauley.com/posts/audit-to-defense/"/>
    <published>2025-09-11T00:00:00Z</published>
    <updated>2025-09-11T00:00:00Z</updated>
    <id>https://tatemccauley.com/posts/audit-to-defense/</id>
    <summary>The journey from a GRC auditor to a SOC analyst, and how seeing security from both sides provides a unique and powerful perspective.</summary>
    <content type="html">&lt;h1&gt;From Auditing Firewalls to Defending Them&lt;/h1&gt;
&lt;p&gt;For the first part of my internship, I was the building inspector. I reviewed blueprints (policies), checked the structural integrity of systems, and even tested the emergency response plans by running a phishing simulation. My work was in the world of Governance, Risk, and Compliance, the strategic &amp;quot;why&amp;quot; behind security.&lt;/p&gt;
&lt;p&gt;Now, it was time to wee the other side of the coin.&lt;/p&gt;
&lt;p&gt;I transitioned from my role at the parent company, FJ Management, to a more direct, hands-on position as a &lt;strong&gt;Cybersecurity Analyst&lt;/strong&gt; at one of its subsidiaries, &lt;strong&gt;Big West Oil&lt;/strong&gt;. I was moving from planning and assessing to defending in real-time.&lt;/p&gt;
&lt;h2&gt;A Familiar Map, A New Battlefield&lt;/h2&gt;
&lt;p&gt;What made this transition so unique was that I wasn&#39;t walking in blind. During my time as an auditor, one of my first projects involved reviewing the firewall rules and security posture of the very company I was now joining. I had already seen the blueprints.&lt;/p&gt;
&lt;p&gt;As an auditor, my questions were strategic: Does this policy effectively reduce risk? Is it documented correctly? Is it essential for the business?&lt;/p&gt;
&lt;p&gt;Now, as an analyst staring at an XDR (Extended Detection and Response) platform, I was watching the live traffic flowing through those same firewalls. My questions became immediate and tactical: Is that packet an attack?&lt;/p&gt;
&lt;h2&gt;Connecting the &amp;quot;Why&amp;quot; to the &amp;quot;What&amp;quot;&lt;/h2&gt;
&lt;p&gt;This immediate connection between my two roles was a game-changer. When I saw an alert for suspicious traffic coming from a specific network segment, I didn&#39;t just see an IP address. I remembered the conversations from my audit about &lt;em&gt;why&lt;/em&gt; that segment was configured the way it was and what critical business assets it housed.&lt;/p&gt;
&lt;p&gt;This experience provided the context, the &amp;quot;why&amp;quot; that made the real-time data, the &amp;quot;what&amp;quot; more meaningful. I understood that the alerts weren&#39;t just random noise they were potential threats to the very systems whose defenses I had previously been tasked with inspecting.&lt;/p&gt;
&lt;p&gt;This experience immediately began to bridge the gap between the two sides of the cybersecurity coin. The rules and policies I had been auditing were no longer abstract concepts. They were the digital barriers I was now responsible for defending on the front lines.&lt;/p&gt;
&lt;h2&gt;What&#39;s Coming Next&lt;/h2&gt;
&lt;p&gt;But make no mistake, the transition wasn&#39;t seamless. Knowing the &#39;why&#39; didn&#39;t automatically teach me the &#39;how.&#39; Moving into an analyst role was like learning a new language, one filled with a new vocabulary of tools, queries, and investigative techniques.&lt;/p&gt;
&lt;p&gt;Next week, I’ll take you inside my day-to-day as a &amp;quot;Digital Detective,&amp;quot; sharing what it was like to grapple with that learning curve, investigate real alerts, and hunt for threats in a sea of data.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series about my cybersecurity internship experiences. Read the &lt;a href=&quot;/posts/the-two-sides-of-the-security-coin/&quot;&gt;first post&lt;/a&gt; to understand the context of my journey through both GRC and Security Operations roles.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The Human Element: I Phished My Own Company (For Science!)</title>
    <link href="https://tatemccauley.com/posts/the-human-element/"/>
    <published>2025-09-03T00:00:00Z</published>
    <updated>2025-09-03T00:00:00Z</updated>
    <id>https://tatemccauley.com/posts/the-human-element/</id>
    <summary>How I used OSINT, AI, and a little social engineering to build a realistic spearphishing campaign to test the human firewall at my own company.</summary>
    <content type="html">&lt;h1&gt;The Human Element: I Phished My Own Company (For Science!)&lt;/h1&gt;
&lt;p&gt;After auditing the machines and software pipelines, my focus shifted to the most complex and unpredictable part of any security system: people. Upper management at FJ Management wanted to know how our teams would hold up against a sophisticated, targeted spearphishing attack. My manager gave me the project, and I was given permission to (ethically) attack my own companies.&lt;/p&gt;
&lt;p&gt;The goal wasn&#39;t to play &amp;quot;gotcha&amp;quot; or to get anyone in trouble. It was to gather data and see how we could build a stronger, more resilient security awareness culture.&lt;/p&gt;
&lt;h2&gt;Step 1: Building the Attack Plan with OSINT&lt;/h2&gt;
&lt;p&gt;To make this a true test, we decided to use as little inside information as possible. The goal was to simulate what a real attacker would do. That meant my first step was &lt;strong&gt;Open-Source Intelligence (OSINT)&lt;/strong&gt; gathering.&lt;/p&gt;
&lt;p&gt;Using LinkedIn profiles, I pieced together an organizational chart for our target groups at FJ Management and its subsidiaries. I identified key individuals, their job titles, and their roles within the company. This public information would become the foundation for creating highly specific and believable phishing lures.&lt;/p&gt;
&lt;h2&gt;Step 2: Crafting the Bait with AI&lt;/h2&gt;
&lt;p&gt;With our targets identified, it was time to craft the bait. We used the open-source tool &lt;strong&gt;Gophish&lt;/strong&gt; to manage the campaigns. For the emails and landing pages, I turned to AI.&lt;/p&gt;
&lt;p&gt;Based on the job titles I found, I created &lt;strong&gt;15 bespoke and targeted emails&lt;/strong&gt;. For example, an email to someone in finance might reference a fake update for an invoice, while a message to an IT-adjacent role might be about another user who is having issues. The AI helped me write convincing copy for each scenario.&lt;/p&gt;
&lt;p&gt;To make the attacks even more realistic, we registered several &lt;strong&gt;typosquatted domains&lt;/strong&gt; that looked nearly identical to our real ones—a classic trick where a capital &#39;I&#39; can look just like a lowercase &#39;l&#39;, making the fake domain almost impossible to spot at a glance. These domains hosted AI-designed fake landing pages that were nearly identical clones of a SharePoint or Microsoft 365 login portal.&lt;/p&gt;
&lt;h2&gt;Step 3: The Launch&lt;/h2&gt;
&lt;p&gt;I’ll admit, it was nerve-wracking to hit &amp;quot;send.&amp;quot; These weren&#39;t generic, typo-filled phishing emails. They were carefully crafted attacks aimed at my own colleagues. But I reminded myself that we were doing this to help protect the company in the long run we were a sparring partner, not a real opponent.&lt;/p&gt;
&lt;p&gt;We started seeing results almost immediately. People clicked.&lt;/p&gt;
&lt;p&gt;Even many who didn&#39;t click reached out to our security team to comment that it was the most convincing phishing attempt they&#39;d ever encountered. That feedback alone was a huge win—it meant our simulation was realistic.&lt;/p&gt;
&lt;h2&gt;The Surprising Results&lt;/h2&gt;
&lt;p&gt;Here’s where our test differed from a standard phishing campaign. When an employee clicked the link and landed on our fake page, there was no obvious &amp;quot;You&#39;ve Been Phished!&amp;quot; message. We tracked the click, but we didn&#39;t harvest credentials. Our primary goal was to see what happened &lt;em&gt;next&lt;/em&gt;. &lt;strong&gt;Would they report it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We found that while employee training was generally effective, the combination of specific details gathered from OSINT and the polished, AI-generated landing pages gave us a significant degree of success. Some people who clicked never realized that it was a spearphishing attack. Others did realize after they entered their credentials but were hesitant to report their mistake to the security team.&lt;/p&gt;
&lt;p&gt;My key takeaway was that the &amp;quot;human firewall&amp;quot; is not a pass-fail system. It&#39;s a dynamic defense that needs to be tested with realistic, modern threats. By acting like a real attacker, we gained invaluable data on how to better train and equip our team for the real thing.&lt;/p&gt;
&lt;h2&gt;What&#39;s Coming Next&lt;/h2&gt;
&lt;p&gt;My time in the GRC world, from auditing mergers to testing our human defenses, gave me a strategic view of security. &lt;strong&gt;Next week&lt;/strong&gt;, I&#39;ll describe my transition to the other side of the coin: moving to the front lines as a Cybersecurity Analyst at Big West Oil.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series about my cybersecurity internship experiences. Read the &lt;a href=&quot;/posts/the-two-sides-of-the-security-coin/&quot;&gt;first post&lt;/a&gt; to understand the context of my journey through both GRC and Security Operations roles.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>From Pipelines to Policies: Auditing Modern Application Development</title>
    <link href="https://tatemccauley.com/posts/pipelines-to-policies/"/>
    <published>2025-08-27T00:00:00Z</published>
    <updated>2025-08-27T00:00:00Z</updated>
    <id>https://tatemccauley.com/posts/pipelines-to-policies/</id>
    <summary>My deep dive into auditing a modern CI/CD pipeline, connecting high-level security policies to the technical reality of developer workflows and tools like Snyk and SonarQube.</summary>
    <content type="html">&lt;h1&gt;From Pipelines to Policies: Auditing Modern Application Development&lt;/h1&gt;
&lt;p&gt;When I was assigned to the Maverik application change management audit, I’ll be honest, I had no idea what a &amp;quot;CI/CD pipeline&amp;quot; was.&lt;/p&gt;
&lt;p&gt;Over the course of two months, my task was to answer the question: &amp;quot;How do we make sure that new code, from a developer&#39;s keyboard to our live applications, is built and deployed securely?&amp;quot;&lt;/p&gt;
&lt;p&gt;To find the answer, I couldn&#39;t just read a policy document. I had to learn what was actually happening in the development process, starting by interviewing the development teams themselves.&lt;/p&gt;
&lt;h2&gt;The Digital Assembly Line&lt;/h2&gt;
&lt;p&gt;Through my conversations with developers, I learned that the CI/CD (Continuous Integration/Continuous Deployment) pipeline is like an automated assembly line for software. At Maverik, this assembly line was powered by a tool called &lt;strong&gt;Jenkins&lt;/strong&gt;. A developer commits code, and Jenkins takes over, automatically building, testing, and preparing it for release.&lt;/p&gt;
&lt;p&gt;My role was to walk the entire length of this assembly line. Each step of the process provided a level of assurance that the code was secure, had been reviewed by QA, and was ready to end up in production. I had to learn the entire application change management process from start to finish and see if each step was being followed correctly.&lt;/p&gt;
&lt;h2&gt;From Conversation to Configuration Files&lt;/h2&gt;
&lt;p&gt;The most crucial part of the audit was verifying that security was truly built into the process, not just an afterthought. The developers told me they used a suite of tools like &lt;strong&gt;Snyk&lt;/strong&gt; to scan for vulnerable dependencies and &lt;strong&gt;SonarQube&lt;/strong&gt; to check for bugs and security issues in their own code.&lt;/p&gt;
&lt;p&gt;But in an audit, &amp;quot;trust but verify&amp;quot; is the rule. It wasn&#39;t enough to hear that they were using these tools. I had to prove it.&lt;/p&gt;
&lt;p&gt;This is where I had to roll up my sleeves and learn to read Jenkins configuration files. Sifting through code, I could directly verify the claims from my interviews. I looked for the specific lines that initiated the Snyk and SonarQube scans and checked the settings to confirm the pipeline would actually &lt;em&gt;fail&lt;/em&gt; if a high-severity vulnerability was found. I was connecting the dots between a conversation with a developer and the technical enforcement in a config file.&lt;/p&gt;
&lt;h2&gt;Connecting Code to the Committee&lt;/h2&gt;
&lt;p&gt;This technical deep dive was only one piece of the puzzle. I also learned how this automated process fed into the larger governance structure. Every significant change had to be approved by a &lt;strong&gt;Change Advisory Board (CAB)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The logs and reports from the Jenkins pipeline—proof that the code was tested and passed all its security scans—served as the evidence presented to the CAB. I saw how a technical control, like a SonarQube scan configured in a Jenkins file, provided the assurance a high-level governance body like the CAB needed to make a risk-informed decision. I even got to see how this process was adapted for emergencies, ensuring security wasn&#39;t abandoned when things needed to move fast.&lt;/p&gt;
&lt;h2&gt;My Key Takeaway&lt;/h2&gt;
&lt;p&gt;Auditing a CI/CD pipeline taught me that a modern auditor is a translator. You have to speak the language of developers and understand their tools, then translate that technical evidence into the language of risk and compliance for management and governance bodies. You are the bridge between the configuration file and the committee meeting. This experience proved that GRC isn&#39;t a separate world; it&#39;s a critical lens through which to view and validate the technology itself.&lt;/p&gt;
&lt;h2&gt;What&#39;s Coming Next&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Next week&lt;/strong&gt;, I&#39;ll shift my focus from auditing the machines to testing the people. I&#39;ll share the story of how I was given permission to (ethically) phish my own company to test our human defenses.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series about my cybersecurity internship experiences. Read the &lt;a href=&quot;/posts/the-two-sides-of-the-security-coin/&quot;&gt;first post&lt;/a&gt; to understand the context of my journey through both GRC and Security Operations roles.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>What I Learned Auditing a Corporate Merger</title>
    <link href="https://tatemccauley.com/posts/what-I-learned-auditing-a-merger/"/>
    <published>2025-08-20T00:00:00Z</published>
    <updated>2025-08-20T00:00:00Z</updated>
    <id>https://tatemccauley.com/posts/what-I-learned-auditing-a-merger/</id>
    <summary>A cybersecurity intern&#39;s perspective on the complex process of securely integrating two companies&#39; digital infrastructure during a major corporate merger.</summary>
    <content type="html">&lt;h1&gt;What I Learned Auditing a Corporate Merger&lt;/h1&gt;
&lt;p&gt;What really happens to the digital infrastructure when two giant companies merge? Even though Maverik&#39;s acquisition of Kum &amp;amp; Go happened in 2023, the process of securely integrating their technologies is a marathon, not a sprint. I joined the ITGC internal audit team at FJ Management (Maverik&#39;s parent company) in 2025, stepping right into the middle of this challenge.&lt;/p&gt;
&lt;h2&gt;The Great Tool Inventory&lt;/h2&gt;
&lt;p&gt;My first task was to be a fly on the wall in the discussions about the great &amp;quot;tool inventory.&amp;quot; It was my job to listen and learn as senior members of the team analyzed the massive ecosystem of security tools each company used. It was like looking at two different toolboxes, both filled with wrenches and hammers, but from different brands and with different features.&lt;/p&gt;
&lt;p&gt;I was amazed to learn just how many different SaaS tools can be pieced together to protect every little aspect of a company&#39;s security posture.&lt;/p&gt;
&lt;h2&gt;The Firewall Dilemma: A Lesson in Business vs. Security&lt;/h2&gt;
&lt;p&gt;The most interesting part of the audit was witnessing a classic debate unfold in real time. The situation was simple: &lt;strong&gt;Kum &amp;amp; Go used a different brand of firewalls than Maverik&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;The Security Problem&lt;/h3&gt;
&lt;p&gt;For the security team, managing two different firewall systems was a major headache. It meant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Double the training&lt;/li&gt;
&lt;li&gt;Slower response time&lt;/li&gt;
&lt;li&gt;Higher chance of misconfigurations that could leave a gap for attackers&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Business Problem&lt;/h3&gt;
&lt;p&gt;From the business side, the firewalls they had were working just fine. The idea of spending a significant amount of money to replace perfectly functional equipment was, to put it mildly, not popular.&lt;/p&gt;
&lt;p&gt;As an intern, I had a front-row seat to one of the most fundamental challenges in our field: &lt;strong&gt;showing the business why a necessary security change is worth the cost&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;My Key Takeaway&lt;/h2&gt;
&lt;p&gt;My biggest lesson from this project wasn&#39;t about which firewall brand was better. It was that &lt;strong&gt;cybersecurity is as much about communication and business acumen as it is about technology&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The best security tool in the world is useless if you can&#39;t articulate its value and get the business to support it. You have to learn to speak the language of risk and efficiency, not just the language of tech.&lt;/p&gt;
&lt;p&gt;This experience was a great introduction to the world of GRC, and it helped shape how I view the role of a security professional.&lt;/p&gt;
&lt;h2&gt;What&#39;s Coming Next&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Next week&lt;/strong&gt;, I&#39;ll shift from high-level strategy to the nuts and bolts of modern development as I share my experience auditing a CI/CD pipeline.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series about my cybersecurity internship experiences. Read the &lt;a href=&quot;/posts/the-two-sides-of-the-security-coin/&quot;&gt;first post&lt;/a&gt; to understand the context of my journey through both GRC and Security Operations roles.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The Two Sides of the Cybersecurity Coin</title>
    <link href="https://tatemccauley.com/posts/the-two-sides-of-the-security-coin/"/>
    <published>2025-08-13T00:00:00Z</published>
    <updated>2025-08-13T00:00:00Z</updated>
    <id>https://tatemccauley.com/posts/the-two-sides-of-the-security-coin/</id>
    <summary>A cybersecurity student shares insights from experiencing both Governance, Risk &amp; Compliance (GRC) and Security Operations roles through two different internships.</summary>
    <content type="html">&lt;h1&gt;The Two Sides of the Cybersecurity Coin&lt;/h1&gt;
&lt;p&gt;If you ask the average person what they think about cybersecurity, they&#39;ll probably imagine a hacker in a hoodie, scrolling walls of green text right out of &lt;em&gt;The Matrix&lt;/em&gt;, or a news story about a data breach. While these things might be part of cybersecurity, they only focus on one side of the coin.&lt;/p&gt;
&lt;p&gt;Over the past eight months, I had the opportunity to see the other side of cybersecurity. Through two very different internships, I experienced two distinct perspectives on security: first as an Internal ITGC Auditor for FJ Management, and then as a Cybersecurity Analyst for Big West Oil.&lt;/p&gt;
&lt;p&gt;Over the next 10 weeks, I&#39;m going to share experiences and lessons that I learned.&lt;/p&gt;
&lt;h2&gt;Side A: The Building Inspector (Governance, Risk, and Compliance)&lt;/h2&gt;
&lt;p&gt;My journey began in the world of &lt;strong&gt;Governance, Risk, and Compliance (GRC)&lt;/strong&gt;. A good way to think of this role is like being a building inspector for a bustling city. The buildings are already standing and operational, and my job was to come in and ensure everything was up to code.&lt;/p&gt;
&lt;p&gt;I wasn&#39;t designing from scratch; I was examining ongoing business and technical processes—reviewing the &#39;structural integrity&#39; of a firewall&#39;s rules, checking the &#39;fire exits&#39; of a CI/CD pipeline, and ensuring the &#39;safety plans&#39; (policies) were being followed in real-time. It&#39;s the critical work of finding hidden risks in active systems before they lead to a collapse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is the &amp;quot;why&amp;quot; behind the rules.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Side B: The Firefighter (Security Operations)&lt;/h2&gt;
&lt;p&gt;Then, I flipped the coin and moved to the front lines as a &lt;strong&gt;Cybersecurity Analyst&lt;/strong&gt;. If GRC is the building inspector, then Security Operations (SecOps) is the city&#39;s firefighter. They are the ones watching for the first sign of smoke, responding to alarms in real-time, and rushing to the scene of a crisis.&lt;/p&gt;
&lt;p&gt;This is the world of XDR platforms, threat monitoring, and incident response. It&#39;s about asking &amp;quot;Is there a fire right now?&amp;quot; and &amp;quot;How do we put it out?&amp;quot; This is the tactical, reactive work that contains damage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is the &amp;quot;how&amp;quot; of cybersecurity.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Why Seeing Both Sides Matters&lt;/h2&gt;
&lt;p&gt;So, why does seeing both matter? A building inspector who has never been to a fire doesn&#39;t fully grasp the real-world consequences of a failed inspection. A firefighter who doesn&#39;t understand the building&#39;s layout or its hidden safety flaws might struggle to respond effectively.&lt;/p&gt;
&lt;p&gt;My time in auditing gave me a deep appreciation for why the rules existed for our ongoing processes, which made me far more effective when I was on the front lines, tasked with defending those same processes.&lt;/p&gt;
&lt;h2&gt;What&#39;s Coming Next&lt;/h2&gt;
&lt;p&gt;I hope you&#39;ll follow along as I share specific stories and insights from these roles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next week&lt;/strong&gt;, I&#39;ll dive into my first major project: auditing the security integration of a corporate merger.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is the first post in a series about my cybersecurity internship experiences. Stay tuned for more insights from both the GRC and Security Operations perspectives.&lt;/em&gt;&lt;/p&gt;
</content>
  </entry>
</feed>
