Skip to main content
interlace
Plugin: pgRules

no-unsafe-search-path

Prevents dynamic SET searchpath queries that could enable schema hijacking.

CWE: CWE-22

Prevents dynamic SET search_path queries that could enable schema hijacking.

⚠️ This rule errors by default in the recommended config.

Quick Summary

AspectDetails
CWE ReferenceCWE-426 (Untrusted Search Path)
SeverityHigh (CVSS: 7.5)
CategorySecurity

Rule Details

If an attacker controls the search_path, they can redirect function/table lookups to malicious schema objects.

❌ Incorrect

// Dynamic schema from user input
await client.query(`SET search_path TO ${userSchema}`);

// Template literal with variable
await pool.query(`SET search_path = ${schema}`);

// Concatenation
await client.query('SET search_path = ' + userSchema);

✅ Correct

const x = 1;

Error Message Format

The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:

🔒 CWE-426 OWASP:A03 CVSS:7.5 | Untrusted Search Path detected | HIGH
   Fix: Review and apply the recommended fix | https://owasp.org/Top10/A03_2021/

Message Components

ComponentPurposeExample
Risk StandardsSecurity benchmarksCWE-426 OWASP:A03 CVSS:7.5
Issue DescriptionSpecific vulnerabilityUntrusted Search Path detected
Severity & ComplianceImpact assessmentHIGH
Fix InstructionActionable remediationFollow the remediation steps below
Technical TruthOfficial referenceOWASP Top 10

When Not To Use It

  • For multi-tenant apps where schema is validated from a trusted source
  • In admin tools where schema input is fully controlled

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Query from Variable

Why: Query strings in variables are not analyzed.

// ❌ NOT DETECTED - SET search_path in variable
const searchPathQuery = `SET search_path = ${userSchema}`;
await client.query(searchPathQuery);

Mitigation: Use inline query strings. Define safe schema constants.

pg-format or Identifier Escaping — reported by design

format('SET search_path = %I', userSchema) and `SET search_path TO ${escapeIdentifier(userSchema)}` ARE reported, and that is correct rather than a false positive: quoting stops injection, but it does nothing about WHICH schema the caller selects, and CWE-426 is about the selection. Validate the schema against an allowlist (an inline if (…) throw or an asserts helper) before the sink.

// ❌ REPORTED - quoted, but the caller still picks the schema
import format from 'pg-format';
await client.query(format('SET search_path = %I', userSchema));

Multi-Tenant Schema Selection

Why: Complex tenant resolution logic is not understood.

// ❌ NOT DETECTED - Tenant-based schema
const schema = getTenantSchema(req.headers['x-tenant-id']);
await client.query(`SET search_path = ${schema}`);

Mitigation: Use centralized tenant schema resolver with allowlist validation.

Also checked (since 2026-10)

  • options: `-c search_path=${userSchema}` on a pg Pool / Client.
  • SELECT set_config('search_path', $1, …) with a non-constant value in the bound array — a parameter stops injection, not hijacking.
  • An allowlist check extracted into a TypeScript asserts helper called before the sink (assertSchema(s): asserts s is …) counts as a guard.

Use this rule

npm install -D oxlint-plugin-postgresql-security
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-postgresql-security"],  "rules": {    "postgresql-security/no-unsafe-search-path": "error"  }}
vite.config.ts
import { defineConfig } from 'vite-plus';export default defineConfig({  lint: {    jsPlugins: ['oxlint-plugin-postgresql-security'],    rules: {      'postgresql-security/no-unsafe-search-path': 'error',    },  },});

oxlint-plugin-postgresql-security re-exports eslint-plugin-postgresql-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.

Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.