<?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: suhteevah</title>
    <description>The latest articles on DEV Community by suhteevah (@suhteevah).</description>
    <link>https://dev.to/suhteevah</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%2F3773643%2F0d01ce69-546a-4798-9625-e73f62a7addb.jpeg</url>
      <title>DEV Community: suhteevah</title>
      <link>https://dev.to/suhteevah</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/suhteevah"/>
    <language>en</language>
    <item>
      <title>Your Terraform Is Probably Insecure — Here Are 90 Patterns to Check</title>
      <dc:creator>suhteevah</dc:creator>
      <pubDate>Fri, 20 Feb 2026 17:18:19 +0000</pubDate>
      <link>https://dev.to/suhteevah/your-terraform-is-probably-insecure-here-are-90-patterns-to-check-1bci</link>
      <guid>https://dev.to/suhteevah/your-terraform-is-probably-insecure-here-are-90-patterns-to-check-1bci</guid>
      <description>&lt;p&gt;Cloud misconfigurations were responsible for 15% of all initial attack vectors in data breaches last year. Not zero-days. Not sophisticated exploits. Misconfigurations. Public S3 buckets, overprivileged IAM roles, security groups that allow the entire internet to SSH in.&lt;/p&gt;

&lt;p&gt;The infrastructure-as-code revolution was supposed to fix this — codify your infrastructure, review it like application code, catch mistakes in PRs. But &lt;code&gt;terraform plan&lt;/code&gt; tells you what will change. It does not tell you if what you're deploying is secure.&lt;/p&gt;

&lt;p&gt;I built CloudGuard to close that gap. 90 security patterns for Terraform and CloudFormation files. Here's what it checks and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Public S3 Buckets and Storage Access
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The pattern — public-read ACL with no block&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"assets"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"company-assets"&lt;/span&gt;
  &lt;span class="nx"&gt;acl&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"public-read"&lt;/span&gt;  &lt;span class="c1"&gt;# Anyone on the internet can read this&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# The fix — block public access explicitly&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"assets"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"company-assets"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_public_access_block"&lt;/span&gt; &lt;span class="s2"&gt;"assets"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assets&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;block_public_acls&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_policy&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ignore_public_acls&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;restrict_public_buckets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it still happens: marketing wants a public assets bucket, someone copies a Terraform example that defaults to public, the bucket policy is complex and nobody reads it carefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudGuard rule S3-001&lt;/strong&gt; catches public-read ACL on buckets. &lt;strong&gt;S3-004&lt;/strong&gt; flags missing S3 Block Public Access configuration. &lt;strong&gt;S3-008&lt;/strong&gt; detects bucket policies allowing wildcard principals.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Wildcard IAM Policies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The pattern — full admin access to everything&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"admin"&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;"ci-pipeline-policy"&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&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;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;       &lt;span class="c1"&gt;# Every AWS action&lt;/span&gt;
      &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;       &lt;span class="c1"&gt;# On every resource&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="c1"&gt;# The fix — scope to specific actions and resources&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"ci_deploy"&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;"ci-pipeline-policy"&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&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;"2012-10-17"&lt;/span&gt;
    &lt;span class="nx"&gt;Statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="nx"&gt;Effect&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
      &lt;span class="nx"&gt;Action&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:s3:::my-deploy-bucket/*"&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"I'll scope it down later." You won't. Or: "It's just for the CI pipeline." Until the CI credentials leak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudGuard rule IM-001&lt;/strong&gt; catches wildcard Action in IAM policies. &lt;strong&gt;IM-003&lt;/strong&gt; flags wildcard Resource. &lt;strong&gt;IM-007&lt;/strong&gt; detects IAM roles with wildcard trust policies.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Open Security Groups
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The pattern — SSH open to the entire internet&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-server"&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="c1"&gt;# The entire internet can SSH in&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# The fix — restrict to specific CIDR blocks&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-server"&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.0/8"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# VPN/internal only&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;"I need to SSH in from home." Use a bastion host or AWS SSM Session Manager. Never expose SSH directly to the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudGuard rule SG-001&lt;/strong&gt; catches security groups allowing 0.0.0.0/0 ingress. &lt;strong&gt;SG-004&lt;/strong&gt; flags database ports open to the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Unencrypted Storage and Databases
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The pattern — RDS without encryption (Terraform default)&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&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;engine&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgres"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
  &lt;span class="c1"&gt;# storage_encrypted is false by default&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# The fix — always encrypt&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_db_instance"&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;engine&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgres"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_class&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db.t3.micro"&lt;/span&gt;
  &lt;span class="nx"&gt;storage_encrypted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;kms_key_id&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_kms_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most Terraform resources don't enable encryption by default. If you don't explicitly set it, it's off. There's almost no performance penalty for encryption at rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudGuard rule EC-001&lt;/strong&gt; catches RDS without encryption at rest. &lt;strong&gt;EC-004&lt;/strong&gt; flags unencrypted EBS volumes. &lt;strong&gt;EC-012&lt;/strong&gt; detects missing KMS key rotation.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Missing Logging and Audit Trails
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What's missing — no CloudTrail, no VPC flow logs&lt;/span&gt;
&lt;span class="c1"&gt;# If you don't have these resources, you're flying blind&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_cloudtrail"&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;name&lt;/span&gt;                          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main-trail"&lt;/span&gt;
  &lt;span class="nx"&gt;s3_bucket_name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trail&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;include_global_service_events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;is_multi_region_trail&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_log_file_validation&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_flow_log"&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&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;traffic_type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ALL"&lt;/span&gt;
  &lt;span class="nx"&gt;log_destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudwatch_log_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CloudTrail is non-negotiable. Without it, you have no audit trail of who did what in your AWS account. VPC Flow Logs on production VPCs. S3 access logging on buckets with sensitive data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudGuard rule LG-001&lt;/strong&gt; catches missing CloudTrail configuration. &lt;strong&gt;LG-004&lt;/strong&gt; flags VPCs without flow logs. &lt;strong&gt;LG-007&lt;/strong&gt; detects S3 buckets without access logging.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Compliance Mapping
&lt;/h2&gt;

&lt;p&gt;Every CloudGuard finding is tagged with relevant compliance framework sections:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;SOC2&lt;/th&gt;
&lt;th&gt;HIPAA&lt;/th&gt;
&lt;th&gt;PCI-DSS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public S3 bucket&lt;/td&gt;
&lt;td&gt;CC6.1&lt;/td&gt;
&lt;td&gt;164.312(a)(1)&lt;/td&gt;
&lt;td&gt;Req 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wildcard IAM&lt;/td&gt;
&lt;td&gt;CC6.3&lt;/td&gt;
&lt;td&gt;164.312(a)(1)&lt;/td&gt;
&lt;td&gt;Req 7.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open security group&lt;/td&gt;
&lt;td&gt;CC6.6&lt;/td&gt;
&lt;td&gt;164.312(e)(1)&lt;/td&gt;
&lt;td&gt;Req 1.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unencrypted RDS&lt;/td&gt;
&lt;td&gt;CC6.1&lt;/td&gt;
&lt;td&gt;164.312(a)(2)(iv)&lt;/td&gt;
&lt;td&gt;Req 3.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No CloudTrail&lt;/td&gt;
&lt;td&gt;CC7.2&lt;/td&gt;
&lt;td&gt;164.312(b)&lt;/td&gt;
&lt;td&gt;Req 10.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run a scan, get a compliance report. No manual mapping required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It On Your Infrastructure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clawhub &lt;span class="nb"&gt;install &lt;/span&gt;cloudguard
cloudguard scan infra/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cloudguard scan infra/

[CRITICAL] S3-001 Bucket with public-read ACL — storage.tf:15
[CRITICAL] IM-003 IAM policy: Action * on Resource * — iam/admin-role.tf:22
[HIGH]     SG-001 Security group allows 0.0.0.0/0 on port 22 — network/security.tf:34
[HIGH]     EC-005 RDS without encryption at rest — database/main.tf:18

Score: 38/100 (Grade: F)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most Terraform codebases I've scanned score below 50.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Compares to tfsec and checkov
&lt;/h2&gt;

&lt;p&gt;Both are solid tools. CloudGuard differs in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scoring&lt;/strong&gt; — 0-100 grade per scan, not just pass/fail&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance mapping&lt;/strong&gt; — SOC2/HIPAA/PCI in the report&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-commit hooks&lt;/strong&gt; — bad IaC never merges (Pro tier)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem&lt;/strong&gt; — part of 26 tools, same installer, same philosophy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If tfsec or checkov is working for your team, you probably don't need to switch. CloudGuard is for teams that want scoring + compliance mapping + the broader ecosystem.&lt;/p&gt;

&lt;p&gt;Free to scan. Pro ($19/mo) adds pre-commit hooks + compliance reports. 100% local — your infrastructure code is literally a map of your cloud. It should never leave your machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloudguard.pages.dev" rel="noopener noreferrer"&gt;CloudGuard&lt;/a&gt; | &lt;a href="https://github.com/suhteevah/cloudguard" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>security</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Every Input Is an Attack Vector: A Developer's Guide to Input Validation</title>
      <dc:creator>suhteevah</dc:creator>
      <pubDate>Fri, 20 Feb 2026 17:10:28 +0000</pubDate>
      <link>https://dev.to/suhteevah/every-input-is-an-attack-vector-a-developers-guide-to-input-validation-4dmg</link>
      <guid>https://dev.to/suhteevah/every-input-is-an-attack-vector-a-developers-guide-to-input-validation-4dmg</guid>
      <description>&lt;p&gt;Every form field, query parameter, URL slug, file upload, and HTTP header your application accepts is an attack surface. If you're not validating and sanitizing all of them, you have vulnerabilities. This isn't a question of "if" — it's a question of how many.&lt;/p&gt;

&lt;p&gt;We have linters for code style, type checkers for safety, test frameworks for correctness. But input validation? Most teams rely on frameworks to handle it, and frameworks only cover the happy path.&lt;/p&gt;

&lt;p&gt;I built InputShield to scan for input validation failures that standard linting tools miss. Here are the 6 most dangerous patterns it catches.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. SQL Injection — Still Alive in 2026
&lt;/h2&gt;

&lt;p&gt;ORMs handle most queries. But there's always that one raw query for a complex join or a search feature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The pattern — string concatenation in SQL&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;`SELECT * FROM products WHERE name LIKE '%&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%'&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;;
  db.query(query); // SQL injection
});

// The fix — parameterized queries, always
app.get('/search', (req, res) =&amp;gt; {
  db.query('SELECT * FROM products WHERE name LIKE $1', [&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;%&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;]);
});
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;InputShield rule SQ-001&lt;/strong&gt; catches string concatenation in SQL queries. &lt;strong&gt;SQ-004&lt;/strong&gt; flags template literals in raw SQL calls. &lt;strong&gt;SQ-007&lt;/strong&gt; finds ORM raw queries with unparameterized input.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Cross-Site Scripting (XSS) — More Vectors Than You Think
&lt;/h2&gt;

&lt;p&gt;It's not just innerHTML. There's dangerouslySetInnerHTML in React, v-html in Vue, [innerHTML] in Angular, document.write, and href attributes with &lt;code&gt;javascript:\&lt;/code&gt; protocol.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A comment component that renders "processed" markdown&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;markdownToHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Does this sanitize? Usually no.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// The fix — sanitize before rendering&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;DOMPurify&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dompurify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DOMPurify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sanitize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;markdownToHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;InputShield rule XS-001&lt;/strong&gt; flags innerHTML from untrusted sources. &lt;strong&gt;XS-003&lt;/strong&gt; catches dangerouslySetInnerHTML with unsanitized data. &lt;strong&gt;XS-007&lt;/strong&gt; detects href attributes with user-controlled values.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Command Injection — The exec() Problem
&lt;/h2&gt;

&lt;p&gt;File conversion, image processing, PDF generation — anything that shells out to a system command is a risk if user input reaches it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A file conversion endpoint&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/convert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;filename&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;`convert &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; output.pdf&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;); // Command injection
});

// The fix — use execFile with explicit arguments
const { execFile } = require('child_process');
app.post('/convert', (req, res) =&amp;gt; {
  const safeName = path.basename(filename); // Strip path traversal
  execFile('convert', [safeName, 'output.pdf']); // No shell interpolation
});
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;InputShield rule CI-001&lt;/strong&gt; catches user input in exec() calls. &lt;strong&gt;CI-004&lt;/strong&gt; flags unsanitized strings in child_process.spawn. &lt;strong&gt;CI-008&lt;/strong&gt; detects commands built from request parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Path Traversal — When Users Control File Paths
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A file download endpoint&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/download&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="c1"&gt;// GET /download?file=../../etc/passwd — game over&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// The fix — resolve and verify&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/download&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Strip directory traversal&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/uploads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/uploads/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Forbidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;InputShield rule PT-001&lt;/strong&gt; catches user input in fs.readFile paths. &lt;strong&gt;PT-004&lt;/strong&gt; flags path.join with user-controlled segments. &lt;strong&gt;PT-007&lt;/strong&gt; detects directory traversal sequences in file operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Server-Side Request Forgery (SSRF)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A URL preview feature&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/preview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// SSRF — user controls the URL&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;extractMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An attacker sends &lt;code&gt;url=http://169.254.169.254/latest/meta-data/\&lt;/code&gt; and reads your AWS instance metadata, including IAM credentials.&lt;/p&gt;

&lt;p&gt;The fix: validate the URL against an allowlist of domains, block internal IP ranges, and use a DNS resolver that prevents rebinding attacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InputShield rule SS-001&lt;/strong&gt; catches user-controlled URLs in server-side fetch calls. &lt;strong&gt;SS-004&lt;/strong&gt; flags missing URL validation before HTTP requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. ReDoS — Regular Expressions as Attack Vectors
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A search endpoint that builds regex from user input&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ReDoS risk&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&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 crafted input like &lt;code&gt;(a+)+$\&lt;/code&gt; causes catastrophic backtracking. Your server hangs on a single regex evaluation.&lt;/p&gt;

&lt;p&gt;The fix: never build regex from user input. Use string methods (includes, indexOf) or a safe regex library with timeouts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InputShield rule RE-001&lt;/strong&gt; catches regex constructed from user input. &lt;strong&gt;RE-004&lt;/strong&gt; flags patterns with potential catastrophic backtracking applied to user data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It On Your Codebase
&lt;/h2&gt;

&lt;p&gt;InputShield scans for all 6 of these categories (90 patterns total) in one pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clawhub &lt;span class="nb"&gt;install &lt;/span&gt;inputshield
inputshield scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ inputshield scan src/

[CRITICAL] CI-002 Unsanitized exec() with user input — api/convert.js:31
[CRITICAL] SQ-001 SQL query with string concatenation — db/users.js:55
[HIGH]     XS-001 innerHTML from untrusted source — components/comment.tsx:18
[HIGH]     PT-004 path.join with user-controlled segment — api/files.js:42

Score: 38/100 (Grade: F)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The average web application I've scanned scores 38/100. Run it on your codebase and see where you land.&lt;/p&gt;

&lt;p&gt;Free to scan. Pro ($19/mo) adds pre-commit hooks so injection vectors can't merge. Runs 100% locally — your source code stays on your machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://inputshield.pages.dev" rel="noopener noreferrer"&gt;InputShield&lt;/a&gt; | &lt;a href="https://github.com/suhteevah/inputshield" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Automated OWASP Top 10 Checks With a Pre-Commit Hook</title>
      <dc:creator>suhteevah</dc:creator>
      <pubDate>Fri, 20 Feb 2026 17:02:52 +0000</pubDate>
      <link>https://dev.to/suhteevah/i-automated-owasp-top-10-checks-with-a-pre-commit-hook-3b97</link>
      <guid>https://dev.to/suhteevah/i-automated-owasp-top-10-checks-with-a-pre-commit-hook-3b97</guid>
      <description>&lt;p&gt;Broken Access Control has been OWASP #1 since 2021. Not because developers don't understand authentication — but because auth is a consistency problem.&lt;/p&gt;

&lt;p&gt;Your auth can be perfect on 99 endpoints. Endpoint 100 ships without middleware because someone forgot, copied a route template that didn't include it, or added an "admin-only" page during a hackathon and never locked it down.&lt;/p&gt;

&lt;p&gt;Most auth tooling is runtime: pentest frameworks, DAST scanners, bug bounties. By the time they find something, the code is deployed and the vulnerability is live. I wanted a pre-commit hook that catches the common stuff before it leaves the developer's machine. So I built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How AuthAudit Maps to OWASP
&lt;/h2&gt;

&lt;p&gt;AuthAudit scans for 90 authentication and authorization anti-patterns across 6 categories. Every finding maps to an OWASP Top 10 2021 entry:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OWASP Category&lt;/th&gt;
&lt;th&gt;AuthAudit Coverage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A01:2021 Broken Access Control&lt;/td&gt;
&lt;td&gt;Missing auth middleware, IDOR patterns, frontend-only role checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A02:2021 Cryptographic Failures&lt;/td&gt;
&lt;td&gt;Weak hashing, missing encryption, token mismanagement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A04:2021 Insecure Design&lt;/td&gt;
&lt;td&gt;Missing rate limiting, authorization logic flaws&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A07:2021 Identification &amp;amp; Auth Failures&lt;/td&gt;
&lt;td&gt;Session management, password handling, CSRF gaps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Pattern 1: Unprotected Routes
&lt;/h2&gt;

&lt;p&gt;This is the most common finding. A route definition with no authentication middleware between the path and the handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Express — no auth middleware&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// What it should look like&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;requireRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it happens: route templates without auth, copy-paste from tutorials, "I'll add auth later" that never happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AuthAudit rule AC-001&lt;/strong&gt; catches route definitions with no auth middleware. &lt;strong&gt;AC-003&lt;/strong&gt; specifically flags admin paths without elevated auth checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: JWT Stored in localStorage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// After successful login&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// XSS = full account takeover&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any XSS vulnerability gives the attacker the full JWT. Game over. Full account takeover.&lt;/p&gt;

&lt;p&gt;The alternative is HttpOnly cookies — they're not accessible to JavaScript. An XSS attack can still make requests (CSRF), but it can't steal the session token and use it from another machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AuthAudit rule TK-003&lt;/strong&gt; flags JWT/token storage in localStorage. &lt;strong&gt;TK-005&lt;/strong&gt; catches sensitive tokens accessible to client-side JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: Missing CSRF Protection
&lt;/h2&gt;

&lt;p&gt;State-changing endpoints (POST, PUT, DELETE) without CSRF token validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// No CSRF middleware on a state-changing route&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/transfer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;transferFunds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&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;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SPA frameworks handle CSRF differently than server-rendered apps. Developers assume "the frontend handles it" — often nobody handles it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AuthAudit rule CS-004&lt;/strong&gt; catches state-changing endpoints without CSRF protection. &lt;strong&gt;CS-006&lt;/strong&gt; flags missing SameSite cookie attribute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: Session Mismanagement
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Express session config — missing security flags&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keyboard cat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Missing: httpOnly, secure, sameSite&lt;/span&gt;
    &lt;span class="c1"&gt;// Missing: maxAge (no session expiry)&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;Three things that should always be set on session cookies: HttpOnly (not accessible to JS), Secure (HTTPS only), and SameSite (prevents cross-origin requests from sending the cookie). Plus: rotate the session ID after login to prevent session fixation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AuthAudit rules CS-001&lt;/strong&gt; (missing HttpOnly), &lt;strong&gt;CS-002&lt;/strong&gt; (missing Secure), &lt;strong&gt;CS-007&lt;/strong&gt; (no session rotation after authentication).&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 5: Insecure Password Handling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Plaintext comparison — never do this&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// MD5/SHA1 — not designed for passwords&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;md5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// What it should look like&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;passwordHash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plaintext comparison means passwords are stored in plaintext. MD5 and SHA1 are fast hashes — designed for checksums, not passwords. Use bcrypt, scrypt, or argon2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AuthAudit rule PW-001&lt;/strong&gt; (plaintext password comparison), &lt;strong&gt;PW-003&lt;/strong&gt; (weak hashing algorithm), &lt;strong&gt;PW-006&lt;/strong&gt; (login endpoint without rate limiting).&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 6: Frontend-Only Role Checks
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// React component — frontend-only authorization&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AdminPanel&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserPanel&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// But the API endpoint serves admin data to ANY authenticated user&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/admin/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Missing: requireRole('admin')&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAdminDashboard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;Frontend checks are UX, not security. Anyone can call your API directly. The backend must enforce authorization independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AuthAudit rule AC-008&lt;/strong&gt; (role check in frontend only, no backend enforcement), &lt;strong&gt;AC-011&lt;/strong&gt; (API endpoint missing role-based authorization).&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Pre-Commit Hook
&lt;/h2&gt;

&lt;p&gt;Install AuthAudit and run an initial scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clawhub &lt;span class="nb"&gt;install &lt;/span&gt;authaudit
authaudit scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get a scored report (0-100) with findings mapped to OWASP categories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ authaudit scan src/

[CRITICAL] AC-001 Unprotected admin route — routes/admin.js:5
[CRITICAL] TK-003 JWT stored in localStorage — auth/client.js:22
[HIGH]     CS-002 Session cookie missing HttpOnly — config/session.js:8
[HIGH]     AC-008 Role check in frontend only — components/admin.tsx:45

Score: 41/100 (Grade: F)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the pre-commit hook (Pro tier), configure lefthook:&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;# .lefthook.yml&lt;/span&gt;
&lt;span class="na"&gt;pre-commit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;authaudit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authaudit scan --staged --min-score &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This blocks any commit that would drop the auth security score below 60. Start with a low threshold and raise it as you fix existing issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Doesn't Replace
&lt;/h2&gt;

&lt;p&gt;AuthAudit finds &lt;em&gt;known anti-patterns&lt;/em&gt;. It won't find a novel auth bypass in your custom OAuth implementation. For that, you still need manual security review or a dedicated pentest.&lt;/p&gt;

&lt;p&gt;This tool catches the stuff that shouldn't survive code review but consistently does — the missing middleware, the insecure cookie config, the localStorage JWT that someone copied from a tutorial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clawhub &lt;span class="nb"&gt;install &lt;/span&gt;authaudit
authaudit scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free to scan. Pro ($19/mo) adds the pre-commit hook + OWASP compliance reports. 100% local — your auth code never leaves your machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://authaudit.pages.dev" rel="noopener noreferrer"&gt;AuthAudit&lt;/a&gt; | &lt;a href="https://github.com/suhteevah/authaudit" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>owasp</category>
      <category>webdev</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Your Logs Are a Security Risk — 6 Patterns That Leak PII</title>
      <dc:creator>suhteevah</dc:creator>
      <pubDate>Fri, 20 Feb 2026 16:55:42 +0000</pubDate>
      <link>https://dev.to/suhteevah/your-logs-are-a-security-risk-6-patterns-that-leak-pii-5jd</link>
      <guid>https://dev.to/suhteevah/your-logs-are-a-security-risk-6-patterns-that-leak-pii-5jd</guid>
      <description>&lt;p&gt;Most teams treat logging as an afterthought. You add console.log during debugging, maybe upgrade to a structured logger eventually, and call it done. But here's the problem: &lt;strong&gt;logs are the largest unaudited data stream in most applications.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I spent months scanning codebases for logging anti-patterns. Here are the 6 most dangerous ones I keep finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Console.log With Full User Objects
&lt;/h2&gt;

&lt;p&gt;The pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Login attempt:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// user object contains: email, hashedPassword, phone, address, tokens...&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;Why it's dangerous:&lt;/strong&gt; User objects contain emails, hashed passwords, tokens, phone numbers, addresses. All of it ends up in your log aggregator where every developer and ops engineer can see it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Log only what you need — user ID, action, timestamp. Never log the full object.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Stack Traces With Sensitive Data
&lt;/h2&gt;

&lt;p&gt;The pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cardNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// stack trace can contain function arguments including card details&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;Why it's dangerous:&lt;/strong&gt; Stack traces can contain function arguments, local variable values, database connection strings, and API keys from environment variable interpolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Sanitize stack traces before logging. Strip arguments, redact known sensitive patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Missing Log Levels (Everything Is console.log)
&lt;/h2&gt;

&lt;p&gt;When your entire codebase uses &lt;code&gt;console.log&lt;/code&gt; for everything — errors, debug info, user actions, system events — you can't filter by severity, can't set up alerts on error-level events, and can't strip debug logs from production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Pick a structured logger (winston, pino, bunyan). Use appropriate levels. Strip debug in production builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. No Correlation IDs Across Services
&lt;/h2&gt;

&lt;p&gt;When a request touches 5 microservices and something breaks, you have no way to trace the path if your logs don't share a correlation ID. Debugging production issues goes from minutes to hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Generate a correlation ID at the entry point, pass it through context, include it in every log line.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Mixed Logging Formats
&lt;/h2&gt;

&lt;p&gt;Half the codebase uses winston with JSON output, the other half uses console.log with string interpolation. Your log aggregator can't parse inconsistent formats. Search and filtering break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Pick one logger. Enforce it project-wide.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Silent Error Paths (Catch and Swallow)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// TODO: handle this&lt;/span&gt;
  &lt;span class="k"&gt;return&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 error happened but you'll never know. The function returns an empty array, something downstream breaks in a confusing way, and you have zero trail to follow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Every catch block should log at minimum the error message, the function name, and relevant context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automate This
&lt;/h2&gt;

&lt;p&gt;Logging is infrastructure. It deserves the same automated standards we apply to code style and test coverage. None of these patterns are hard to fix individually — the problem is that nobody checks.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://logsentry.pages.dev" rel="noopener noreferrer"&gt;LogSentry&lt;/a&gt; to scan for all 6 of these patterns (and 84 more) in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clawhub &lt;span class="nb"&gt;install &lt;/span&gt;logsentry
logsentry scan &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free to scan. Pro ($19/mo) adds pre-commit hooks so these patterns can't merge. Runs 100% locally — your log analysis never leaves your machine.&lt;/p&gt;

&lt;p&gt;What logging anti-patterns have you run into? I'd love to hear what I'm missing.&lt;/p&gt;

</description>
      <category>security</category>
      <category>logging</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Best Snyk Alternatives in 2026: Open-Source Dependency Security Tools</title>
      <dc:creator>suhteevah</dc:creator>
      <pubDate>Sun, 15 Feb 2026 07:37:32 +0000</pubDate>
      <link>https://dev.to/suhteevah/best-snyk-alternatives-in-2026-open-source-dependency-security-tools-1gb4</link>
      <guid>https://dev.to/suhteevah/best-snyk-alternatives-in-2026-open-source-dependency-security-tools-1gb4</guid>
      <description>&lt;p&gt;Snyk is the default answer for dependency security, but it comes with trade-offs: cloud dependency, pricing complexity, and your code being analyzed on external servers. If you're looking for alternatives — especially ones that run locally — here's what's available in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Landscape
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Local?&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Languages&lt;/th&gt;
&lt;th&gt;SBOM&lt;/th&gt;
&lt;th&gt;License Audit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Snyk&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;$25+/dev/mo&lt;/td&gt;
&lt;td&gt;Many&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Socket&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;$20+/dev/mo&lt;/td&gt;
&lt;td&gt;JS, Python&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DepGuard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Free / $19/dev/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10 pkg managers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✓&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✓&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm audit&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;JS only&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pip-audit&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Python only&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cargo audit&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Rust only&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;td&gt;✗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trivy&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Containers&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why Local Matters
&lt;/h2&gt;

&lt;p&gt;Cloud-based tools like Snyk require your dependency manifests (and sometimes source code) to be sent to external servers. For many teams — especially in regulated industries — this is a non-starter.&lt;/p&gt;

&lt;p&gt;Tools that run locally analyze your code on your machine and never phone home.&lt;/p&gt;

&lt;h2&gt;
  
  
  DepGuard: The All-in-One Local Option
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://depguard.pages.dev" rel="noopener noreferrer"&gt;DepGuard&lt;/a&gt; is interesting because it wraps native audit tools (npm audit, pip-audit, cargo audit, govulncheck) into a single interface and adds license compliance on top.&lt;/p&gt;

&lt;p&gt;What it does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vulnerability scanning&lt;/strong&gt; using the audit tools your package managers already trust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License detection&lt;/strong&gt; categorizing every dependency as permissive, copyleft, or unknown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git hook enforcement&lt;/strong&gt; blocking commits that introduce vulnerable dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SBOM generation&lt;/strong&gt; in CycloneDX format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy enforcement&lt;/strong&gt; blocking specific licenses (e.g., GPL in proprietary projects)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free tier covers vulnerability scanning and license detection. Pro ($19/user/mo) adds git hooks and auto-fix. Team ($39/user/mo) adds SBOM and compliance reports.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clawhub &lt;span class="nb"&gt;install &lt;/span&gt;depguard
depguard scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The DIY Approach
&lt;/h2&gt;

&lt;p&gt;You can also build your own pipeline with individual tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# JavaScript&lt;/span&gt;
npm audit &lt;span class="nt"&gt;--json&lt;/span&gt;

&lt;span class="c"&gt;# Python&lt;/span&gt;
pip-audit

&lt;span class="c"&gt;# Rust&lt;/span&gt;
cargo audit

&lt;span class="c"&gt;# Go&lt;/span&gt;
govulncheck ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downside: you need to maintain scripts for each language, there's no unified reporting, and you're on your own for license compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snyk&lt;/strong&gt; if you need the deepest vulnerability database and don't mind cloud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DepGuard&lt;/strong&gt; if you want local-only, multi-language scanning with license compliance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Individual audit tools&lt;/strong&gt; if you only use one language and don't need license checks&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://depguard.pages.dev" rel="noopener noreferrer"&gt;DepGuard&lt;/a&gt; — dependency audit &amp;amp; license compliance. Install: &lt;code&gt;clawhub install depguard&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>devtools</category>
      <category>opensource</category>
      <category>node</category>
    </item>
    <item>
      <title>Why Your Documentation Is Always Stale (And How to Fix It With Git Hooks)</title>
      <dc:creator>suhteevah</dc:creator>
      <pubDate>Sun, 15 Feb 2026 07:34:07 +0000</pubDate>
      <link>https://dev.to/suhteevah/why-your-documentation-is-always-stale-and-how-to-fix-it-with-git-hooks-f7b</link>
      <guid>https://dev.to/suhteevah/why-your-documentation-is-always-stale-and-how-to-fix-it-with-git-hooks-f7b</guid>
      <description>&lt;p&gt;Every dev team has the same problem: documentation that was accurate three months ago but hasn't been updated since. It's not because developers are lazy — it's because there's no feedback loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Cause
&lt;/h2&gt;

&lt;p&gt;Code has CI/CD. Tests run on every commit. Linting catches style issues automatically. But documentation? It's a manual process that depends on someone remembering to update it.&lt;/p&gt;

&lt;p&gt;That's the fundamental problem: &lt;strong&gt;docs are disconnected from the code they describe.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What If Docs Had Their Own CI?
&lt;/h2&gt;

&lt;p&gt;Imagine if every commit was checked for documentation drift — the same way ESLint checks for code style or Jest checks for regressions.&lt;/p&gt;

&lt;p&gt;That's exactly what &lt;a href="https://docsync-1q4.pages.dev" rel="noopener noreferrer"&gt;DocSync&lt;/a&gt; does.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;DocSync uses &lt;a href="https://tree-sitter.github.io/" rel="noopener noreferrer"&gt;tree-sitter&lt;/a&gt; to parse your code's AST and extract symbols: functions, classes, types, interfaces. Then it compares those symbols against your existing documentation.&lt;/p&gt;

&lt;p&gt;On every commit, a pre-commit hook runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git commit -m "add payment handler"

━━━ DocSync: Documentation Drift Detected ━━━

✗ processPayment (function in src/payments.ts)
✗ PaymentResult (type in src/payments.ts)
⚠ validateCard (docs older than source)

Run docsync auto-fix to generate missing docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commit is blocked until the docs are updated. One command regenerates everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docsync auto-fix
✓ Regenerated docs/api/payments.md (3 symbols)
✓ All documentation is now in sync.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Key Insight: Make It Automatic
&lt;/h2&gt;

&lt;p&gt;The reason documentation rots isn't that devs don't care — it's that there's no enforcement mechanism. DocSync turns documentation into a commit-level concern, just like tests and linting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Free — no account needed&lt;/span&gt;
clawhub &lt;span class="nb"&gt;install &lt;/span&gt;docsync
docsync generate &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The free tier generates docs for any file or directory. &lt;a href="https://docsync-1q4.pages.dev/#pricing" rel="noopener noreferrer"&gt;DocSync Pro&lt;/a&gt; adds git hooks, drift detection, and auto-fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  40+ Languages Supported
&lt;/h2&gt;

&lt;p&gt;TypeScript, JavaScript, Python, Rust, Go, Java, C/C++, Ruby, PHP, C#, Swift, Kotlin — and any language with a tree-sitter grammar.&lt;/p&gt;

&lt;p&gt;Everything runs locally. No code leaves your machine.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://docsync-1q4.pages.dev" rel="noopener noreferrer"&gt;DocSync&lt;/a&gt; is an OpenClaw skill. Install free: &lt;code&gt;clawhub install docsync&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>devtools</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
