UNPKG

@aikidosec/firewall

Version:

Zen by Aikido is an embedded Application Firewall that autonomously protects Node.js apps against common and critical attacks, provides rate limiting, detects malicious traffic (including bots), and more.

137 lines (136 loc) 6.46 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkContextForIdor = checkContextForIdor; const AgentSingleton_1 = require("../../agent/AgentSingleton"); const withoutIdorProtection_1 = require("../../agent/context/withoutIdorProtection"); const LRUMap_1 = require("../../ratelimiting/LRUMap"); const zen_internals_1 = require("../../internals/zen_internals"); function checkContextForIdor({ sql, context, dialect, resolvePlaceholder, }) { const agent = (0, AgentSingleton_1.getInstance)(); if (!agent) { return undefined; } const config = agent.getIdorProtectionConfig(); if (!config) { return undefined; } if ((0, withoutIdorProtection_1.isIdorProtectionIgnored)()) { return undefined; } if (!context.tenantId) { return violation("Zen IDOR protection: setTenantId() was not called for this request. Every request must have a tenant ID when IDOR protection is enabled."); } const analysis = getAnalysisResults(sql, dialect); if (!analysis) { return violation("Zen IDOR protection: failed to analyze SQL query"); } if ("error" in analysis) { return violation(`Zen IDOR protection: ${analysis.error}`); } for (const queryResult of analysis.results) { if (queryResult.kind === "insert") { const insertViolation = checkInsert(queryResult, config, context, resolvePlaceholder); if (insertViolation) { return insertViolation; } } else { const whereViolation = checkWhereFilters(queryResult, config, context, resolvePlaceholder); if (whereViolation) { return whereViolation; } } } return undefined; } function checkWhereFilters(queryResult, config, context, resolvePlaceholder) { for (const table of queryResult.tables) { if (config.excludedTables.includes(table.name)) { continue; } const tenantFilter = queryResult.filters.find((f) => { if (f.column !== config.tenantColumnName) { return false; } // If qualified (e.g. u.tenant_id), match against table name or alias if (f.table) { return (f.table === table.name || (table.alias && f.table === table.alias)); } // Unqualified column (e.g. WHERE tenant_id = $1 without table prefix): // We can only safely attribute it to the current table when there's // exactly one table in the query. With multiple tables, we can't know // which table the unqualified column belongs to. return queryResult.tables.length === 1; }); if (!tenantFilter) { return violation(`Zen IDOR protection: query on table '${table.name}' is missing a filter on column '${config.tenantColumnName}'`); } const resolvedValue = resolvePlaceholder(tenantFilter.value, tenantFilter.placeholder_number); if (context.tenantId !== undefined) { const resolved = typeof resolvedValue === "string" || typeof resolvedValue === "number"; // Placeholder could not be resolved (missing param or bug in the sink's resolve logic) if (tenantFilter.is_placeholder && !resolved) { return violation(`Zen IDOR protection: query on table '${table.name}' has a placeholder for '${config.tenantColumnName}' that could not be resolved`); } const value = resolved ? String(resolvedValue) : tenantFilter.value; const tenantIdStr = context.tenantId.toString(); if (value !== tenantIdStr) { return violation(`Zen IDOR protection: query on table '${table.name}' filters '${config.tenantColumnName}' with value '${value}' but tenant ID is '${tenantIdStr}'`); } } } return undefined; } function checkInsert(queryResult, config, context, resolvePlaceholder) { for (const table of queryResult.tables) { if (config.excludedTables.includes(table.name)) { continue; } if (!queryResult.insert_columns) { // INSERT ... SELECT without explicit columns — can't verify tenant column return violation(`Zen IDOR protection: INSERT on table '${table.name}' is missing column '${config.tenantColumnName}'`); } for (const row of queryResult.insert_columns) { const tenantCol = row.find((c) => c.column === config.tenantColumnName); if (!tenantCol) { return violation(`Zen IDOR protection: INSERT on table '${table.name}' is missing column '${config.tenantColumnName}'`); } const resolvedValue = resolvePlaceholder(tenantCol.value, tenantCol.placeholder_number); if (context.tenantId !== undefined) { const resolved = typeof resolvedValue === "string" || typeof resolvedValue === "number"; // Placeholder could not be resolved (missing param or bug in the sink's resolve logic) if (tenantCol.is_placeholder && !resolved) { return violation(`Zen IDOR protection: INSERT on table '${table.name}' has a placeholder for '${config.tenantColumnName}' that could not be resolved`); } const value = resolved ? String(resolvedValue) : tenantCol.value; const tenantIdStr = context.tenantId.toString(); if (value !== tenantIdStr) { return violation(`Zen IDOR protection: INSERT on table '${table.name}' sets '${config.tenantColumnName}' to '${value}' but tenant ID is '${tenantIdStr}'`); } } } } return undefined; } const cache = new LRUMap_1.LRUMap(1000); function getAnalysisResults(sql, dialect) { const cacheKey = `${dialect.getWASMDialectInt()}:${sql}`; const cached = cache.get(cacheKey); if (cached) { return { results: cached }; } const result = (0, zen_internals_1.wasm_idor_analyze_sql)(sql, dialect.getWASMDialectInt()); if (!result) { return undefined; } if (result.error) { return { error: result.error }; } const results = result; cache.set(cacheKey, results); return { results }; } function violation(message) { return { idorViolation: true, message }; }