惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threat Research - Cisco Blogs
S
Securelist
H
Heimdal Security Blog
Scott Helme
Scott Helme
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Hacker News
The Hacker News
C
CXSECURITY Database RSS Feed - CXSecurity.com
Spread Privacy
Spread Privacy
Cyberwarzone
Cyberwarzone
V
Vulnerabilities – Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
C
CERT Recently Published Vulnerability Notes
P
Proofpoint News Feed
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
人人都是产品经理
人人都是产品经理
C
Cisco Blogs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Engineering at Meta
Engineering at Meta
Project Zero
Project Zero
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
有赞技术团队
有赞技术团队
T
Tailwind CSS Blog
Cisco Talos Blog
Cisco Talos Blog
Last Week in AI
Last Week in AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
O
OpenAI News
P
Proofpoint News Feed
Google Online Security Blog
Google Online Security Blog
Recent Announcements
Recent Announcements
Hacker News: Ask HN
Hacker News: Ask HN
美团技术团队
Stack Overflow Blog
Stack Overflow Blog
U
Unit 42
P
Privacy International News Feed
Google DeepMind News
Google DeepMind News
G
GRAHAM CLULEY
Apple Machine Learning Research
Apple Machine Learning Research
TaoSecurity Blog
TaoSecurity Blog
S
Security @ Cisco Blogs
C
Check Point Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Jina AI
Jina AI
S
Secure Thoughts
G
Google Developers Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 最新话题
T
Tenable Blog
Latest news
Latest news
I
InfoQ

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Writing Custom SAST Rules for Vulnerabilities Your Scanner Doesn't Cover
Patience Mpo · 2026-05-08 · via DEV Community

Every SAST tool ships with a default ruleset. And every default ruleset has gaps.

Sometimes the gap is a framework-specific vulnerability that the tool's authors didn't anticipate. Sometimes it's an internal pattern unique to your organisation — a custom authentication library, a legacy data access layer, a home-grown serialisation format that every engineer knows is sensitive but no off-the-shelf rule covers.

This is the article where I show you how to close those gaps using the YAML rule engine I built. No Python required. No rebuilding the scanner. Just a YAML file and an understanding of what you're trying to detect.

By the end, you'll have written three custom rules from scratch — a Java-specific one, a Node.js-specific one, and an organisation-level one that catches usage of a fictional internal library pattern. The process is the same for any vulnerability you want to target.


Before You Write a Rule: The Four Questions

Every good detection rule starts with the same four questions. Skip them and you end up with either a rule that fires on everything or a rule that fires on nothing.

1. What does the vulnerable code actually look like in text?
Not the conceptual vulnerability — the literal characters that appear on screen when a developer writes the bad pattern. Be specific. "SQL injection" is not an answer. "SELECT * FROM users WHERE id = " + userId is an answer.

2. What does safe code look like?
You need the counterexample. If your pattern would also match safe code, you have a false positive problem. If you can't articulate what safe code looks like, you don't understand the vulnerability well enough to write a rule yet.

3. Which languages does this apply to?
Some patterns are universal — hardcoded secrets look similar everywhere. Others are language or framework-specific. Writing a broad rule when a narrow one is appropriate generates noise and erodes trust in the scanner.

4. What's the right confidence level?
HIGH means "this is almost certainly a real vulnerability." MEDIUM means "this warrants human review." LOW means "this is suspicious but probably benign." If you're unsure, start at MEDIUM and tighten it after you see the results on real code.

Now let's write some rules.


The Rule Format (Quick Reference)

rules:
  - id: CUSTOM-001
    title: Short descriptive title
    description: >
      What the vulnerability is and why it matters.
    severity: CRITICAL | HIGH | MEDIUM | LOW
    category: Injection | Secrets | Cryptography | Authentication | Misconfiguration | Path Traversal
    cwe: CWE-XXX
    owasp: AXX:2021 - Category Name
    languages: ["python", "java", "javascript", "typescript", "csharp", "kotlin", "go", "ruby", "php"]
    remediation: >
      What the developer should do instead.
    patterns:
      - regex: 'your-pattern-here'
        confidence: HIGH | MEDIUM | LOW

Enter fullscreen mode Exit fullscreen mode

Save it anywhere — the scanner discovers all YAML files in the rules/ directory automatically. If you want to keep your custom rules separate from the core ruleset, create a rules/custom/ subdirectory and point the scanner at it:

python main.py ./src --rules ./rules/custom/

Enter fullscreen mode Exit fullscreen mode


Rule 1: Java — Spring @Transactional on Public Methods Exposing Sensitive Data

This one is Java-specific and framework-specific. It's the kind of vulnerability that no generic SAST tool covers because it requires understanding Spring's transaction management model.

The vulnerability: In Spring, @Transactional annotations on public methods in @Service or @Repository classes work as expected because Spring creates a proxy. But when @Transactional is placed on a private method, Spring's proxy-based AOP cannot intercept it — the transaction is silently ignored. This is especially dangerous when the private method performs database writes that need to be atomic.

This isn't a traditional security vulnerability in the CVE sense — it's a correctness issue that can become a security issue when the failed transaction silently corrupts data, leaves partial writes in the database, or bypasses audit logging that was supposed to be transactional.

What safe code looks like: @Transactional on public methods, or using TransactionTemplate for programmatic transaction management on private methods.

What vulnerable code looks like:

@Service
public class PaymentService {

    @Transactional  // silent no-op — Spring proxy can't intercept private methods
    private void processRefund(String accountId, BigDecimal amount) {
        ledgerRepo.debit(accountId, amount);
        auditRepo.log("REFUND", accountId, amount);  // may not be in same transaction
    }
}

Enter fullscreen mode Exit fullscreen mode

The rule:

rules:
  - id: JAVA-001
    title: "@Transactional on Private Method  Transaction Silently Ignored"
    description: >
      Spring's proxy-based AOP cannot intercept @Transactional annotations on
      private methods. The annotation is silently ignored, meaning the method
      executes without transaction management. This can cause partial writes,
      data corruption, and bypassed audit logging in database operations.
    severity: HIGH
    category: Misconfiguration
    cwe: CWE-362
    owasp: A05:2021 - Security Misconfiguration
    languages: ["java"]
    remediation: >
      Move @Transactional to public methods only. For private methods that
      require transaction management, either make them public, use
      TransactionTemplate for programmatic transactions, or restructure
      the code so the public caller method is annotated instead.
    patterns:
      - regex: '@Transactional[\s\S]{0,100}private\s+\w+\s+\w+\s*\('
        confidence: HIGH
      - regex: 'private\s+\w+\s+\w+\s*\([\s\S]{0,100}@Transactional'
        confidence: MEDIUM

Enter fullscreen mode Exit fullscreen mode

Testing your rule — create a test file test_java_transactional.java and verify it fires:

// Should fire — JAVA-001
@Transactional
private void updateBalance(String id, BigDecimal amount) { }

// Should NOT fire — public method is fine
@Transactional
public void processPayment(String id, BigDecimal amount) { }

Enter fullscreen mode Exit fullscreen mode

Run:

python main.py ./test_java_transactional.java --rules ./rules/custom/java-rules.yaml

Enter fullscreen mode Exit fullscreen mode


Rule 2: Node.js — child_process.exec with Template Literals

This one targets a Node.js-specific pattern that's extremely common in backend services written by developers who came from a systems programming background.

The vulnerability: child_process.exec() passes its argument to the shell for execution. If that argument contains user-controlled input — even through a template literal that looks clean — it enables OS command injection. The shell will happily interpret special characters like ;, &&, |, and backticks as command separators or subshell operators.

What safe code looks like: child_process.execFile() or child_process.spawn() with arguments as an array — these bypass the shell entirely and treat the command and arguments as separate values.

What vulnerable code looks like:

// Dangerous — shell injection possible
const filename = req.body.filename;
exec(`convert ${filename} -resize 800x600 output.jpg`, callback);

// Also dangerous — looks safer but isn't
exec("ffmpeg -i " + userInput + " output.mp4", callback);

Enter fullscreen mode Exit fullscreen mode

What safe code looks like:

// Safe — no shell involved
execFile('convert', [filename, '-resize', '800x600', 'output.jpg'], callback);

// Safe — spawn with args array
spawn('ffmpeg', ['-i', userInput, 'output.mp4']);

Enter fullscreen mode Exit fullscreen mode

The rule:

rules:
  - id: NODE-001
    title: "child_process.exec with Dynamic Input  OS Command Injection"
    description: >
      child_process.exec() passes its argument to the system shell, enabling
      OS command injection when the argument includes user-controlled input,
      template literals, or string concatenation. Attackers can inject shell
      metacharacters to execute arbitrary commands on the host system.
    severity: CRITICAL
    category: Injection
    cwe: CWE-78
    owasp: A03:2021 - Injection
    languages: ["javascript", "typescript"]
    remediation: >
      Replace exec() with execFile() or spawn() and pass command arguments
      as an array. These functions bypass the shell entirely and treat each
      argument as a literal string, preventing shell metacharacter injection.
      Never concatenate user input into exec() arguments.
    patterns:
      - regex: 'exec\s*\(\s*`[^`]*\$\{'
        confidence: HIGH
      - regex: 'exec\s*\(\s*["\'][^"\']*["\'\s]\+\s*\w'
        confidence: HIGH
      - regex: 'exec\s*\(\s*\w+\s*\+'
        confidence: MEDIUM

Enter fullscreen mode Exit fullscreen mode

The three patterns cover the three common forms: template literals with interpolation, concatenation with a string prefix, and concatenation with a variable. The last one is MEDIUM because exec("mycommand" + options) where options is a static config value is less dangerous — but still warrants review.


Rule 3: Organisation-Level — Internal Audit Logger Bypass

This is the most interesting type of custom rule: one that only makes sense for your specific codebase.

Imagine your organisation has an internal library called AuditLogger that must be called for any database mutation. The security policy is clear: every write operation must produce an audit event. But the library has a skipAudit() method that was added for performance testing and was never supposed to reach production code.

This isn't in any public CVE database. No off-the-shelf SAST tool would ever flag it. But it's a real security control bypass in your organisation's context.

The rule:

rules:
  - id: ORG-001
    title: "AuditLogger.skipAudit()  Security Control Bypass"
    description: >
      The skipAudit() method on AuditLogger disables audit event generation
      for database mutations. This method was introduced for load testing
      only and must never appear in production code. Its presence bypasses
      the organisation's regulatory audit trail requirement and may
      constitute a compliance violation.
    severity: CRITICAL
    category: Misconfiguration
    cwe: CWE-778
    owasp: A09:2021 - Security Logging and Monitoring Failures
    languages: ["java", "kotlin", "csharp"]
    remediation: >
      Remove skipAudit() immediately. All database mutations must generate
      audit events via AuditLogger. If performance is a concern, use
      AuditLogger.asyncLog() instead, which queues events without blocking
      the main thread. Contact the security team if an exemption is required.
    patterns:
      - regex: '\.skipAudit\s*\('
        confidence: HIGH
      - regex: 'AuditLogger\s*\.\s*skip'
        confidence: HIGH

Enter fullscreen mode Exit fullscreen mode

Notice what this rule does that a generic tool can't: it encodes your organisation's security policy directly into the scanner. The remediation text names the correct alternative (asyncLog()). The description mentions the regulatory context. The severity is CRITICAL because in this fictional organisation, bypassing audit logging is a compliance issue, not just a best practice.

This is the highest-value type of custom rule because it's completely unavailable from any third-party source.


Multi-Pattern Rules: Increasing Coverage Without Losing Precision

One pattern rarely catches all instances of a vulnerability. The best rules use multiple patterns with appropriate confidence levels to maximise coverage while communicating certainty to the reviewer.

Here's a well-structured multi-pattern rule for detecting hardcoded database credentials in connection strings — a pattern that appears differently across languages and frameworks:

rules:
  - id: CUSTOM-DB-001
    title: "Hardcoded Database Credentials in Connection String"
    description: >
      Database connection strings with embedded credentials expose sensitive
      authentication material in source code, version control history, and
      build artifacts.
    severity: HIGH
    category: Secrets
    cwe: CWE-798
    owasp: A07:2021 - Identification and Authentication Failures
    languages: ["java", "csharp", "python", "javascript", "typescript", "kotlin"]
    remediation: >
      Move credentials to environment variables or a secrets manager such as
      AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault. Never commit
      credentials to version control.
    patterns:
      # JDBC connection strings
      - regex: 'jdbc:[a-z]+://[^/]+/[^?]+\?.*password=[^&\s"'']{3,}'
        confidence: HIGH
      # .NET connection strings
      - regex: 'Password\s*=\s*[^;"\s]{4,}\s*;'
        confidence: HIGH
      # Generic password assignment near connection context
      - regex: '(conn|connection|db).*password\s*=\s*["\'][^"'']{4,}["\']'
        confidence: MEDIUM
      # SQLAlchemy / Django database URLs
      - regex: '(postgresql|mysql|sqlite|mongodb)://\w+:[^@\s"'']{4,}@'
        confidence: HIGH

Enter fullscreen mode Exit fullscreen mode

Each pattern has a different confidence because each has a different false positive profile. JDBC connection strings with password parameters are nearly always real findings. The generic connection.password = pattern might match configuration loading code where the value comes from an environment variable on the right-hand side.


Testing Your Custom Rules

Before you add a rule to your pipeline, test it against both positive and negative cases.

Create a dedicated test file with clearly labelled sections:

# test_custom_rules.py

# --- SHOULD FIRE ---
# NODE-001: exec with template literal
exec(`convert ${userInput} output.jpg`)

# CUSTOM-DB-001: hardcoded JDBC credentials
conn = "jdbc:postgresql://localhost/mydb?user=admin&password=supersecret123"

# --- SHOULD NOT FIRE ---
# Safe: spawn with args array
spawn('convert', [userInput, 'output.jpg'])

# Safe: password from environment
conn = f"jdbc:postgresql://localhost/mydb?user=admin&password={os.getenv('DB_PASS')}"

Enter fullscreen mode Exit fullscreen mode

Then run the scanner and verify the output matches your expectations:

python main.py ./test_custom_rules.py --rules ./rules/custom/ --format json

Enter fullscreen mode Exit fullscreen mode

Check that:

  • Every SHOULD FIRE comment corresponds to a finding in the output
  • Every SHOULD NOT FIRE comment has no corresponding finding
  • The confidence and severity levels match what you intended If a false positive appears, either tighten the regex or downgrade the confidence level. If a true positive is missed, your pattern isn't covering that form of the vulnerability.

The Broader Point: Rules as Institutional Knowledge

The most valuable thing about a YAML-driven rule engine isn't the rules it ships with. It's the rules your team writes over time.

Every time a security engineer finds a vulnerability in a code review, there's a question worth asking: could this have been caught by a scanner rule? If the answer is yes, write the rule. Now the scanner catches that pattern forever, across every future PR, without anyone needing to remember it.

Rules become institutional knowledge. They encode the hard-won understanding of what goes wrong in your specific codebase, your specific frameworks, your specific compliance requirements. That's something no off-the-shelf tool can give you — and it compounds over time.


The full scanner and core ruleset are at github.com/pgmpofu/sast-tool. Drop your custom rules in rules/ and they're picked up automatically on the next scan.

Next up: embedding the scanner in a CI/CD pipeline with configurable severity thresholds — how to go from zero security gates to blocking builds on critical findings without breaking your team's deployment workflow.