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

推荐订阅源

L
LangChain Blog
博客园 - 司徒正美
美团技术团队
WordPress大学
WordPress大学
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
人人都是产品经理
人人都是产品经理
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Troy Hunt's Blog
S
Schneier on Security
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
Engineering at Meta
Engineering at Meta
Cisco Talos Blog
Cisco Talos Blog
T
Tor Project blog
B
Blog
NISL@THU
NISL@THU
月光博客
月光博客
博客园 - 【当耐特】
AWS News Blog
AWS News Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
腾讯CDC
L
Lohrmann on Cybersecurity
The Cloudflare Blog
L
LINUX DO - 最新话题
S
Security @ Cisco Blogs
S
Secure Thoughts
Spread Privacy
Spread Privacy
有赞技术团队
有赞技术团队
The Last Watchdog
The Last Watchdog
Project Zero
Project Zero
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
H
Hacker News: Front Page
S
SegmentFault 最新的问题
Schneier on Security
Schneier on Security
aimingoo的专栏
aimingoo的专栏
P
Privacy & Cybersecurity Law Blog
博客园 - 三生石上(FineUI控件)
Forbes - Security
Forbes - Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
I
InfoQ
T
Tailwind CSS Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
G
GRAHAM CLULEY
W
WeLiveSecurity
小众软件
小众软件
Recorded Future
Recorded Future
Cyberwarzone
Cyberwarzone
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

Aikido Security's Blog

Axios CVE-2026-40175: a critical bug that’s… not exploitable GlassWorm goes native: New Zig dropper infects every IDE on your machine Aikido Attack finds multiple 0-days in Hoppscotch The cybersecurity doomerism around Mythos doesn't match what we see on the ground axios compromised on npm: maintainer account hijacked, RAT deployed Popular telnyx package compromised on PyPI by TeamPCP Aikido × Lovable: Vibe, Fix, Ship CanisterWorm Gets Teeth: TeamPCP's Kubernetes Wiper Targets Iran TeamPCP deploys CanisterWorm on NPM following Trivy compromise Security testing is validating software that no longer exists Aikido Recognized by Frost & Sullivan with the 2026 Customer Value Leadership Award in ASPM GlassWorm Hides a RAT Inside a Malicious Chrome Extension fast-draft Open VSX Extension Compromised by BlokTrooper Glassworm Strikes Popular React Native Phone Number Packages Glassworm Is Back: A New Wave of Invisible Unicode Attacks Hits Hundreds of Repositories How Security Teams Fight Back Against AI-Powered Hackers Introducing Betterleaks, an open source secrets scanner by the author of Gitleaks Trump’s 2026 cybersecurity strategy: From compliance to consequence How does AI pentesting work with compliance? What continuous pentesting actually requires Rare Not Random: Using Token Efficiency for Secrets Scanning Persistent XSS/RCE using WebSockets in Storybook’s dev server Why Determinism Is Still a Necessity in Security WAF vs. RASP vs. ADR Introducing Aikido Infinite: A new model of self-securing software How Aikido secures AI pentesting agents by design Astro Full-Read SSRF via Host Header Injection How to Get Your Board to Care About Security (Before a Breach Forces the Issue) What is Slopsquatting? The AI Package Hallucination Attack Already Happening SvelteSpill: A Cache Deception Bug in SvelteKit + Vercel Top 6 Wiz Code Alternatives Aikido recognized as Platform Leader in Latio Tech's 2026 Application Security Report npm backdoor lets hackers hijack gambling outcomes Introducing Upgrade Impact Analysis: When breaking changes actually matter to your code Why Trying to Secure OpenClaw is Ridiculous Claude Opus 4.6 found 500 vulnerabilities. What does this change for software security? Introducing Aikido Expansion Packs: Safer defaults inside the IDE International AI Safety Report 2026: What It Means for Autonomous AI Systems Self-Securing Software: What It Is, Why It Matters, and How It Works npx Confusion: Packages That Forgot to Claim Their Own Name What Is Continuous Pentesting? Introducing Aikido Package Health: a Better Way to Trust Your Dependencies AI Pentesting: Minimum Safety Requirements for Security Testing Secure SDLC for Engineering Teams (+ Checklist) Fake Clawdbot VS Code Extension Installs ScreenConnect RAT G_Wagon: npm Package Deploys Python Stealer Targeting 100+ Crypto Wallets Gone Phishin': npm Packages Serving Custom Credential Harvesting Pages Malicious PyPI Packages spellcheckpy and spellcheckerpy Deliver Python RAT Top 10 AI Security Tools For 2026 Agent Skills Are Spreading Hallucinated npx Commands Understanding Open-Source License Risk in Modern Software The CISO Vibe Coding Checklist for Security Top 6 Graphite alternatives for AI code review in 2026 From “No Bullsh*t Security” to $1B: We Just Raised Our $60m Series B Critical n8n Vulnerability Allows Unauthenticated Remote Code Execution (CVE-2026-21858) Top 14 VS Code Extensions for 2026 AI-Driven Pentesting of Coolify: Seven CVEs Identified Top Continuous Pentesting Tools in 2026 SAST vs SCA: Securing the Code You Write and the Code You Depend On JavaScript, MSBuild, and the Blockchain: Anatomy of the NeoShadow npm Supply-Chain Attack How Engineering and Security Teams Can Meet DORA’s Technical Requirements IDOR Vulnerabilities Explained: Why They Persist in Modern Applications Shai Hulud strikes again - The golden path MongoBleed: MongoDB Zlib Vulnerability (CVE-2025-14847) and How to Fix It First Sophisticated Malware Discovered on Maven Central via Typosquatting Attack on Jackson The Fork Awakens: Why GitHub’s Invisible Networks Break Package Security Top 10 Cyber Security Tools For 2026 SAST in the IDE is now free: Moving SAST to where development actually happens AI Pentesting in Action: A TL;DV Recap of Our Live Demo The Top 7 Threat Intelligence Tools in 2026 React & Next.js DoS Vulnerability (CVE-2025-55184): What You Need to Fix After React2Shell OWASP Top 10 for Agentic Applications (2026): What Developers and Security Teams Need to Know DAST vs Pentesting v AI Pentesting: Why DAST Cannot Replace Modern Pentesting PromptPwnd: Prompt Injection Vulnerabilities in GitHub Actions Using AI Agents Top 7 Cloud Security Vulnerabilities Critical React & Next.js RCE Vulnerability (CVE-2025-55182): What You Need to Fix Now How to Comply With the UK Cybersecurity & Resilience Bill: A Practical Guide for Modern Engineering Teams Shai Hulud 2.0: What the Unknown Wonderer Tells Us About the Attackers’ Endgame SCA Everywhere: Scan and Fix Open-Source Dependencies in Your IDE Safe Chain now enforces a minimum package age before install Shai Hulud Attacks Persist Through GitHub Actions Vulnerabilities Shai Hulud Launches Second Supply-Chain Attack: Zapier, ENS, AsyncAPI, PostHog, Postman Compromised CORS Security: Beyond Basic Configuration Revolut Selects Aikido Security to Power Developer-First Software Security The Future of Pentesting Is Autonomous How Aikido and Deloitte are bringing developer-first security to enterprise Secrets Detection: A Practical Guide to Finding and Preventing Leaked Credentials Invisible Unicode Malware Strikes OpenVSX, Again AI as a Power Tool: How Windsurf and Devin Are Changing Secure Coding Building Fast, Staying Secure: Supabase’s Approach to Secure-by-Default Development OWASP Top 10 2025: Official List, Changes, and What Developers Need to Know Top 10 JavaScript Security Vulnerabilities in Modern Web Apps The Return of the Invisible Threat: Hidden PUA Unicode Hits GitHub repositorties Top 7 Black Duck Alternatives in 2026 What Is IaC Security Scanning? Terraform, Kubernetes & Cloud Misconfigurations Explained AutoTriage and the Swiss Cheese Model of Security Noise Reduction Top Software Supply Chain Security Vulnerabilities Explained The Top 7 Kubernetes Security Tools Top 10 Web Application Security Vulnerabilities Every Team Should Know What Is CSPM (and CNAPP)? Cloud Security Posture Management Explained
From detection to prevention: How Zen stops IDOR vulnerabilities at runtime
Hans Ott · 2026-02-16 · via Aikido Security's Blog

TL:DR

IDORs are the #1 way multi-tenant SaaS companies leak data, and they’re usually discovered after deployment. Aikido Zen makes tenant isolation non-optional. Zen parses all of your SQL queries at runtime using a proper SQL parser (written in Rust), checks that every query filters on the correct tenant, and throws an error if it doesn't. Developers can no longer accidentally ship cross-tenant access. It's available today for Node.js, with Python, PHP, Go, Ruby, Java, and .NET coming soon.

Why IDORs are more dangerous now

IDOR vulnerabilities, otherwise known as Insecure Direct Object References, are one of the most common and dangerous flaws in multi-tenant applications. They happen when a query forgets to filter by tenant, letting one account access another account's data.

For a long time, IDORs were hard to catch. They didn't show up in code scans and required a lot of manual effort. Because of that, many IDOR bugs were only discovered during expensive, laborious pentests, or when security researchers found them through bug bounty programs.

But that's shifted. Agentic security testing tools can now behave like real users, clicking through workflows, switching roles and automatically trying to access resources. This makes IDOR vulnerabilities much easier to detect. But this is a double-edged sword: If these flaws are easier to find, they're also easier to exploit. This is why organizations shouldn't just be focused on detecting IDORs, but preventing them.

Why detection is not enough

On the detection side, Aikido's AI Pentest can already find IDOR vulnerabilities, something that traditional pattern-based SAST tools could never reliably do, because IDOR requires understanding authorization context, not just code patterns. AI Pentest authenticates as real users, exercises complete workflows, and reuses object identifiers across roles to find actual exploitable IDOR flaws. It’s for this reason that many organizations that use our AI Pentest capability are predominantly interested in finding IDORs.

But finding IDOR vulnerabilities is only half the equation. In an ideal world, you'd prevent them from being introduced in the first place. That's what this post is about: IDOR protection in Zen, our open-source in-app firewall. It analyzes every SQL query at runtime and throws an error if a query is missing a tenant filter or uses the wrong tenant ID, catching the bug during development and testing before it ever reaches production.

In many enterprise environments, especially during security reviews or vendor assessments, one recurring question is how multi-tenancy is enforced and how cross-tenant data access is prevented.

Security teams and leadership want clear, technical assurances that tenant boundaries are enforced systematically, not just by convention.

Having a mechanism that automatically validates tenant scoping at the query level provides a straightforward, credible answer. It moves the conversation from "we rely on developers to remember this" to "the system enforces it automatically".

Here's what the setup looks like:

import Zen from "@aikidosec/firewall";

// 1. Tell Zen which column identifies the tenant
Zen.enableIdorProtection({
  tenantColumnName: "tenant_id",
  excludedTables: ["users"],
});

// 2. Set the tenant ID per request (e.g., in middleware after authentication)
app.use((req, res, next) => {
  Zen.setTenantId(req.user.organizationId);
  next();
});

// 3. Optionally bypass for specific queries (e.g., admin dashboards)
const result = await Zen.withoutIdorProtection(async () => {
  return await db.query("SELECT count(*) FROM orders WHERE status = 'active'");
});

What does an IDOR vulnerability look like?

If your app has accounts, organizations, workspaces, or teams, you probably have a column like tenant_id that keeps each account's data separate. But when the query forgets to filter on that column, or filters on the wrong value, it means one account can access another’s data. This is an IDOR vulnerability.  

Here's a simple example. You have an endpoint that returns a user's orders:

app.get("/orders/:orderId", async (req, res) => {
  const order = await db.query(
    "SELECT * FROM orders WHERE id = $1",
    [req.params.orderId]
  );

  res.json(order);
});

See the problem? There's no tenant_id filter. If Alice sends GET /orders/42 and order 42 belongs to Bob, Alice gets Bob's order. That's an IDOR.

The fix is straightforward, add a WHERE tenant_id = $2 clause. However, the bug is easy to introduce and hard to spot, especially in a large codebase with hundreds of queries across dozens of files. One missed filter is all it takes.

IDOR is a broad category. It also includes things like accessing other users' files via URL manipulation or API endpoints that do not check ownership. This post focuses specifically on the SQL tenant-filtering subset, making sure every database query is properly scoped to the current tenant. For a deeper dive into IDOR more broadly, check out our IDOR vulnerabilities explained post.
What's out there today

Aside from security scanning, which is more about finding existing bugs, there are other methods to prevent new IDORs from being introduced: framework-level libraries and database-level enforcement. Each has strengths and limitations.

Framework-level libraries

Several frameworks have libraries that automatically scope queries to the current tenant:

  • Ruby on Rails: acts_as_tenant adds automatic tenant scoping to ActiveRecord models. Declare acts_as_tenant(:account) and all queries on that model are filtered by the current tenant.
  • Django: django-multitenant does the same for Django's ORM. Set the current tenant in middleware, and Product.objects.all() automatically becomes tenant-scoped.
  • Laravel: Tenancy for Laravel provides both single-database and multi-database multi-tenancy, with automatic context switching.
  • .NET / EF Core: Global query filters let you apply WHERE tenant_id = X to every query automatically at the model level.

These libraries work well within their own ORM. The limitation is that they only protect queries that go through the ORM's abstraction. Raw SQL queries, queries from other libraries, or queries built with a different query builder in the same project will not be scoped. They're also opt-in, you have to remember to add the annotation to every model, and new models can slip through without anyone noticing. To be fair, acts_as_tenant has a require_tenant config that raises an error when no tenant is set, which mitigates the "forgot to set the tenant" risk significantly.

There are also subtle footguns. In Rails, for example, acts_as_tenant works by adding a default_scope. If a developer calls Project.unscoped to remove a different default scope, such as an archived filter, it strips all default scopes, including the tenant filter, with no error or warning. Rails has unscope (without the d) for surgically removing a single scope, but that requires knowing the tenant scope exists in the first place. In a codebase with many developers, someone will eventually reach for unscoped, and the tenant boundary silently disappears.

Database-level enforcement

PostgreSQL Row-Level Security (RLS) takes this a step further by enforcing tenant isolation at the database level. Instead of relying on your application to add WHERE tenant_id = ? to every query, you tell Postgres itself to enforce it:

-- 1. Enable RLS on each table
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

-- 2. Create a policy: only allow rows matching the session variable
CREATE POLICY tenant_isolation ON projects
  FOR ALL
  USING (tenant_id = current_setting('app.current_tenant_id')::uuid);

-- 3. Per request, set the tenant context before executing queries
SET app.current_tenant_id = 'aaaa-aaaa-aaaa';

-- Now even a bare SELECT only returns that tenant's rows
SELECT * FROM projects;

RLS is the strongest guarantee of the approaches listed here. Raw SQL or ORM queries, it does not matter. The database enforces it. And unlike acts_as_tenant, if you forget to set the session variable, no data is returned rather than all data. That is a much safer default.

But it comes with real trade-offs. RLS does not throw errors; queries silently return fewer rows or affect nothing. That is safer than returning all data, but it makes debugging harder. An UPDATE that should modify 100 rows might quietly affect 0 because of a policy mismatch, and it is difficult to distinguish this from "no data exists."

Connection pooling adds complexity as well. RLS with SET does not work properly with pgBouncer in statement or transaction pooling mode, which can lead to rows being returned for the wrong tenant. This may only surface in production.

There are also structural limitations. Superusers bypass all policies entirely, and views bypass RLS by default, so your app must connect as a non-superuser role. Finally, it is Postgres-only. If you need to support MySQL, SQLite for development, or another data store, your security layer does not come with you.

The pragmatic takeaway: RLS is excellent as a safety net for tenant isolation, but the operational complexity and debugging difficulty mean it is not a drop-in solution for every team.

Where Zen fits in

These are all valid approaches, and if you're using one of them, that's great. Zen's IDOR protection is designed for a different scenario: your queries go through a database driver, directly or via an ORM, and you want a safety net that works regardless of which ORM, query builder, or raw SQL pattern you use, without changing your database configuration or adopting a specific framework library.

Zen has its own trade-offs to be honest about. Like acts_as_tenant, it requires you to call setTenantId on every request. If you forget, Zen throws an error, so it fails loudly rather than silently, but it is the same class of per-request setup. And unlike RLS, Zen only covers queries that run inside your application. If someone connects to the database directly, for example through psql or a separate service without Zen, those queries are not checked.

It is also language-agnostic by design. Because the SQL analysis engine is written in Rust, we compile it to WebAssembly for Node.js and Go, and a native library that other agents call via FFI. IDOR protection specifically will be coming to the Python, PHP, Go, Ruby, Java, and .NET agents as well.

How Zen protects against IDORs

Zen sits inside your application and analyzes SQL queries at runtime, with full context about who is making the request.

A proper SQL parser, written in Rust

At the heart of Zen's IDOR protection is a real SQL parser built on the sqlparser crate in Rust, compiled to WebAssembly for Node.js and Go. It parses SQL the same way a database would by building a full Abstract Syntax Tree (AST) of your query, then walks the tree to extract:

  • Which tables the query touches (including aliases)
  • Which equality filters are in the WHERE clause
  • Which columns and values are in INSERT statements

Why not regex? Regex works fine for simple queries like SELECT * FROM orders WHERE tenant_id = ?. But real-world applications have CTEs, UNIONs, subqueries, JOINs with aliases, and all sorts of valid SQL that a regex-based approach has trouble with. As queries get more complex, regex-based parsing becomes increasingly fragile. It is not necessarily wrong, but it is hard to maintain and easy to get surprised by.

A proper parser handles all of this out of the box. It also correctly recognizes statements that do not need checking, such as DDL statements (CREATE TABLE, ALTER TABLE), transaction control (BEGIN, COMMIT, ROLLBACK), and session commands (SET, SHOW).

Here is what the analysis looks like under the hood. Given this query:

SELECT * FROM orders
LEFT JOIN order_items ON orders.id = order_items.order_id
WHERE orders.tenant_id = $1
AND orders.status = 'active';

The parser produces:

[
  {
    "kind": "select",
    "tables": [
      { "name": "orders" },
      { "name": "order_items" }
    ],
    "filters": [
      { "table": "orders", "column": "tenant_id", "value": "$1" },
      { "table": "orders", "column": "status", "value": "active" }
    ]
  }
]

Zen then checks whether every table in the query has a filter on tenant_id, and whether the filter value matches the current tenant.

The same applies to INSERT, UPDATE, and DELETE. Zen makes sure the tenant column is always present and always has the right value. These are thrown as errors, not just logged. IDOR is a developer bug, not an external attack, so you want it to surface loudly during development and testing rather than slip through to production.

Performance

Parsing SQL on every query sounds expensive, but in practice it is fast. The key insight is that most applications use prepared statements or parameterized queries. The SQL string stays the same, only the parameter values change. So SELECT * FROM orders WHERE tenant_id = $1 AND status = $2 gets parsed once, and every subsequent execution of that same query is a cache hit.

The first time Zen sees a new query string, the Rust parser builds the AST and extracts the tables and filters. Every time after that, it is just a cache lookup and a comparison of the tenant ID against the resolved placeholder value.

If you are embedding values directly into SQL strings, for example string concatenation instead of parameterized queries, each unique string requires a fresh parse. But you probably should not be doing that anyway. Parameterized queries protect against SQL injection and happen to make IDOR checking fast too.

The road to production: eating our own dog food

We deployed Zen's IDOR protection on several of Aikido's own internal services. This immediately surfaced edge cases that needed handling.

Transaction support was a blocker early on. Real applications use BEGIN, COMMIT, and ROLLBACK, and Zen needed to recognize these as safe statements that do not require tenant filtering rather than erroring on them. We added this quickly after seeing it fail on our first internal deployment.

Common Table Expressions (CTEs) were another challenge. A CTE like WITH active AS (SELECT * FROM orders WHERE tenant_id = $1) creates a virtual table that downstream queries reference. Zen needed to track CTE names and exclude them from the “real table” list while still analyzing the CTE body for proper filtering.

The withoutIdorProtection escape hatch also proved essential. Not every query needs tenant filtering, such as admin dashboards, background jobs, or cross-tenant analytics. We initially tried an ignoreNextQuery approach, where you would call a function before the query to skip the check for the next SQL statement:

Zen.ignoreNextQuery();
const result = await db.query("SELECT count(*) FROM orders");

This turned out to be fragile in practice. With connection pools, the "next query" on a given connection might not be the one you intended to skip. The callback-based withoutIdorProtection is explicit about scope. IDOR protection is disabled for the duration of the callback and nothing else.

How we protected our API that serves cloud asset data

One of the services we protected early on was the internal API that serves cloud asset data across the platform.

This API is used by the UI, background jobs, and several security engines whenever they need to read information about a customer’s infrastructure. It sits at the core of the system and handles thousands of requests per second.

Because the platform is fully multi-tenant, strict tenant isolation is critical. Every query must be scoped to the correct organization, and we cannot rely on developers remembering to add the right filter in every code path.

Before Zen supported IDOR protection natively, we had a custom implementation that enforced tenant scoping at the query level. Once Zen introduced first-class support for this behavior, we migrated from the homegrown solution to the built-in functionality, which is considerably less code for us to maintain.

Today, Zen automatically verifies that queries are properly scoped to the current tenant, even under heavy load. After introducing Zen’s IDOR protection, we saw no noticeable performance impact.

Aikido's AI Pentest finds IDOR vulnerabilities in your running application by simulating real attacks. Zen's IDOR protection prevents them from being introduced in the first place by catching missing tenant filters at development time.

Together, they cover both sides. AI Pentest validates that your existing code is safe, and Zen ensures new code stays safe. Use AI Pentest to audit what is already deployed. Use Zen to catch mistakes as you write them.

Getting started

IDOR protection is available today in @aikidosec/firewall for Node.js. Check out the setup guide to get started. Support for other languages is coming soon!