<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Security on ferkakta.dev</title><link>https://ferkakta.dev/tags/security/</link><description>Recent content in Security on ferkakta.dev</description><generator>Hugo</generator><language>en-US</language><copyright>Copyright fizz.</copyright><lastBuildDate>Fri, 27 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://ferkakta.dev/tags/security/index.xml" rel="self" type="application/rss+xml"/><item><title>Stop copying AWS managed policies — deny what you don't want instead</title><link>https://ferkakta.dev/iam-deny-overlay-managed-policies/</link><pubDate>Fri, 27 Feb 2026 00:00:00 +0000</pubDate><guid>https://ferkakta.dev/iam-deny-overlay-managed-policies/</guid><description>&lt;p&gt;I needed to give a developer full CloudWatch read access — metrics, alarms, dashboards, log groups — but deny access to three categories of log groups containing security-sensitive data: WorkSpaces OS event logs, VPC flow logs, and WAF request logs.&lt;/p&gt;
&lt;p&gt;The reflex is to copy &lt;code&gt;CloudWatchReadOnlyAccess&lt;/code&gt; into a custom policy and delete the parts you don&amp;rsquo;t want. I&amp;rsquo;ve seen this in every organization I&amp;rsquo;ve worked in. It produces a policy with 50+ actions that you now own. Every time AWS ships a new CloudWatch feature, your policy is stale. You won&amp;rsquo;t update it. It&amp;rsquo;ll rot.&lt;/p&gt;</description></item><item><title>The IAM policy controls access — the document controls how people feel about it</title><link>https://ferkakta.dev/access-control-docs-as-respect/</link><pubDate>Fri, 27 Feb 2026 00:00:00 +0000</pubDate><guid>https://ferkakta.dev/access-control-docs-as-respect/</guid><description>&lt;p&gt;I tightened a teammate&amp;rsquo;s AWS permissions last night. Added an inline deny policy to block three categories of CloudWatch log groups — WorkSpaces OS logs, VPC flow logs, WAF request data. Five minutes of IAM work. Then I spent twenty minutes writing a document explaining every boundary, what&amp;rsquo;s accessible, what&amp;rsquo;s denied, what&amp;rsquo;s coming next, and what I haven&amp;rsquo;t designed yet.&lt;/p&gt;
&lt;p&gt;The document mattered more than the policy.&lt;/p&gt;
&lt;h2 id="the-default-is-silence"&gt;The default is silence&lt;/h2&gt;
&lt;p&gt;Most companies handle access control the same way. Someone asks for access. An admin creates a policy. The requester gets a login link. Nobody explains what they can and can&amp;rsquo;t do, or why.&lt;/p&gt;</description></item><item><title>IAM trust policies silently accept wildcards in principals — and silently deny everything</title><link>https://ferkakta.dev/iam-trust-policy-wildcards/</link><pubDate>Thu, 26 Feb 2026 10:00:00 -0600</pubDate><guid>https://ferkakta.dev/iam-trust-policy-wildcards/</guid><description>&lt;p&gt;I needed a cross-account IAM role in a management account that workloads in a separate devops account could assume to send email via SES. Two types of callers: one shared service with a stable role name, and N dynamically-created per-tenant roles following a naming convention like &lt;code&gt;myapp-apiserver-*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The shared service was straightforward — exact ARN in the trust policy principal. For the per-tenant roles, I wrote what looked correct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Principal&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; { &lt;span style="color:#f92672"&gt;&amp;#34;AWS&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;arn:aws:iam::111111111111:role/myapp-apiserver-*&amp;#34;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;terraform apply&lt;/code&gt; succeeded. The role was created. Every assume-role call was denied.&lt;/p&gt;</description></item><item><title>The Over-Mighty Subject: why your site repos have too much power</title><link>https://ferkakta.dev/over-mighty-subjects-terraform-credential-scope/</link><pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate><guid>https://ferkakta.dev/over-mighty-subjects-terraform-credential-scope/</guid><description>&lt;p&gt;Josh Marshall &lt;a href="https://talkingpointsmemo.com/edblog/elon-musk-and-the-the-threat-of-the-over-mighty-subject-part-i/sharetoken/21fb0dac-112d-4a9d-bcc0-f5bf844b16bb"&gt;borrows a phrase from medieval history&lt;/a&gt; to describe a modern political problem: the Over-Mighty Subject. A feudal lord whose personal wealth, private army, and territorial control grew so large that he rivaled the crown itself. Not a rebel — still nominally a subject — but operating with enough independent power that the sovereign&amp;rsquo;s authority became theoretical.&lt;/p&gt;
&lt;p&gt;I had three of them in my infrastructure. They were Terraform roots for static sites.&lt;/p&gt;</description></item><item><title>Expression injection in GitHub Actions repository_dispatch — and the one-line fix</title><link>https://ferkakta.dev/github-actions-expression-injection-repository-dispatch/</link><pubDate>Fri, 20 Feb 2026 10:00:00 -0600</pubDate><guid>https://ferkakta.dev/github-actions-expression-injection-repository-dispatch/</guid><description>&lt;p&gt;I was hardening a cross-repo deploy pipeline built on &lt;code&gt;repository_dispatch&lt;/code&gt; when I found a textbook expression injection sitting in plain sight. The trigger workflow accepted a &lt;code&gt;client_payload&lt;/code&gt; JSON object from the caller and dropped its fields straight into a &lt;code&gt;run:&lt;/code&gt; block.&lt;/p&gt;
&lt;h2 id="how-repository_dispatch-works"&gt;How repository_dispatch works&lt;/h2&gt;
&lt;p&gt;When you call the &lt;code&gt;repository_dispatch&lt;/code&gt; API, you send a JSON body with an &lt;code&gt;event_type&lt;/code&gt; and an optional &lt;code&gt;client_payload&lt;/code&gt; — an arbitrary JSON object the caller defines. Your workflow reads it via &lt;code&gt;github.event.client_payload.*&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Your CI/CD dispatch token can rewrite your infrastructure code</title><link>https://ferkakta.dev/github-actions-repository-dispatch-contents-write-permission/</link><pubDate>Fri, 20 Feb 2026 09:00:00 -0600</pubDate><guid>https://ferkakta.dev/github-actions-repository-dispatch-contents-write-permission/</guid><description>&lt;p&gt;I built a cross-repo auto-deploy pipeline this week. Three app repos push Docker images to ECR, then dispatch a deploy event to the infra repo&amp;rsquo;s orchestrator workflow via &lt;code&gt;repository_dispatch&lt;/code&gt;. Standard pattern.&lt;/p&gt;
&lt;p&gt;The gotcha: fine-grained PATs need &lt;code&gt;contents:write&lt;/code&gt; to call the &lt;code&gt;repository_dispatch&lt;/code&gt; API. Not &lt;code&gt;actions:write&lt;/code&gt; — &lt;code&gt;contents:write&lt;/code&gt;. The permission that also lets you push code, create branches, and delete files.&lt;/p&gt;
&lt;p&gt;My service token that should only be able to say &amp;ldquo;hey, deploy this&amp;rdquo; can also rewrite the deployment workflow it&amp;rsquo;s triggering. That&amp;rsquo;s not least privilege. That&amp;rsquo;s a door that&amp;rsquo;s three sizes too wide.&lt;/p&gt;</description></item><item><title>What building infrastructure for a startup actually looks like</title><link>https://ferkakta.dev/startup-infra-unglamorous-work/</link><pubDate>Wed, 11 Feb 2026 09:00:00 -0600</pubDate><guid>https://ferkakta.dev/startup-infra-unglamorous-work/</guid><description>&lt;p&gt;I spent a day doing the unglamorous infrastructure work that keeps a startup alive. Here&amp;rsquo;s everything that happened.&lt;/p&gt;
&lt;h2 id="morning-security-audit"&gt;Morning: security audit&lt;/h2&gt;
&lt;p&gt;Audited two EKS clusters for a K8s privilege escalation vulnerability. Found 9 service accounts with &lt;code&gt;cluster-admin&lt;/code&gt; that didn&amp;rsquo;t need it. Deleted two dead deployments — ArgoCD and Velero, both mine, both abandoned months ago. The rest are kubeflow components we can&amp;rsquo;t touch until 1.36 ships the fix in April.&lt;/p&gt;</description></item><item><title>Your ACM certificate request is a beacon — scanners are watching Certificate Transparency logs</title><link>https://ferkakta.dev/acm-certificate-transparency-scanners/</link><pubDate>Mon, 09 Feb 2026 09:00:00 -0600</pubDate><guid>https://ferkakta.dev/acm-certificate-transparency-scanners/</guid><description>&lt;p&gt;I accidentally exposed production secrets on a public endpoint. Here&amp;rsquo;s what happened and what I learned about Certificate Transparency.&lt;/p&gt;
&lt;h2 id="the-setup"&gt;The setup&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re building a multi-tenant SaaS platform on EKS. During development, our Terraform module defaulted to &lt;code&gt;ealen/echo-server&lt;/code&gt; for three microservices — a lightweight HTTP server that echoes back request info. Seemed harmless.&lt;/p&gt;
&lt;p&gt;What I missed: echo-server echoes EVERYTHING. Every environment variable in the container, including ones injected from AWS SSM via External Secrets Operator. Database connection strings. Redis auth tokens. OAuth client secrets. Signing keys. A single unauthenticated &lt;code&gt;GET /&lt;/code&gt; returns it all as JSON.&lt;/p&gt;</description></item></channel></rss>