how-to

I Ran a 6-Point Security Audit on DecodeIQ Before Launch. Here's What It Found.

Jack Metalle||7 min read

I Ran a 6-Point Security Audit on DecodeIQ Before Launch. Here's What It Found.

By Jack | May 2026

DecodeIQ handles buyer intelligence data for e-commerce sellers. Category scans pull from public conversations across Reddit, YouTube, Amazon reviews, and forums. Users trust us with their research, their credit card, and their competitive strategy.

Before opening the doors, I wanted to know if the platform was safe. Not "we take security seriously" safe. Tested safe.

So I ran a full security audit using free tools. No paid pen test firm. No compliance consultant. Six tests, two production domains, zero budget.

Here's what happened.


The Setup

DecodeIQ runs on a standard modern SaaS stack: Next.js monorepo on Vercel, Supabase for database and auth, Stripe for billing, Cloudflare for CDN and WAF, Pinecone for vector search. Two domains: decodeiq.ai (marketing) and app.decodeiq.ai (platform).

The audit covered six areas. Each one tests something different.


Test 1: Mozilla Observatory (HTTP Headers)

What it checks: Security headers. CSP, HSTS, X-Frame-Options, referrer policy, cookie flags, and more.

What it found: The Content Security Policy header was in report-only mode. It was logging violations but not blocking anything. Score: -25.

Root cause: A case-sensitivity bug. The code checked process.env.CSP_ENFORCED === 'true' (lowercase), but the Vercel environment variable was set to TRUE (uppercase). JavaScript strict equality failed silently. The header never flipped to enforced mode.

What I fixed:

  • Made the comparison case-insensitive with .toLowerCase()
  • Removed unsafe-eval from script-src (Next.js production builds don't need it)
  • Added object-src 'none' to block plugin-based attacks
  • Added base-uri 'self' to prevent base tag hijacking
  • Added form-action 'self' to block form exfiltration
  • Added frame-ancestors 'none' for clickjacking protection
  • Added upgrade-insecure-requests for defense-in-depth
  • Removed the X-Powered-By: Next.js header from both apps

Remaining accepted tradeoff: unsafe-inline stays in script-src. Removing it requires nonce-based CSP middleware, which means generating a unique nonce per request and passing it to Next.js for inline script tags. That's a pre-launch hardening item, not a blocker. The real-world risk is low because our pages are server-rendered and we don't inject user input into inline scripts.


Test 2: OWASP ZAP Baseline Scan

What it checks: ZAP spiders your app and runs 60+ passive security checks. It looks for XSS vectors, injection points, missing headers, information disclosure, cookie misconfigurations, server version leaks, and directory listings.

What it found: Zero failures on both domains. 61 checks passed on marketing, 58 on platform. A few informational warnings about CSP strictness (expected, given the unsafe-inline tradeoff) and a 307 redirect on the platform (normal auth behavior).

Key passes: No vulnerable JavaScript libraries. No cookie issues. No mixed content. No debug error messages leaked. No PII disclosure. No open redirects. API endpoints properly return 401 without auth tokens.


Test 3: Nuclei CVE Scan

What it checks: Nuclei runs 6,000+ templates that test for known CVEs, default credentials, exposed admin panels, technology-specific misconfigurations, and common attack patterns.

What it found: Zero findings across 12,056 template checks (6,028 per domain). The marketing site was flagged as "unresponsive" by Nuclei because Vercel's bot protection blocked the scanner. That's a positive signal, not a failure.

Supplementary manual checks: No source maps exposed (403). No .git or .env files accessible. No server version headers. No open redirects. Supabase REST API returns 401 without a key. CORS headers absent on API routes (correct for a first-party app).


Test 4: Dependency Audit

What it checks: pnpm audit scans every dependency in the monorepo for known vulnerabilities.

What it found: 8 advisories. 1 low, 7 moderate, 0 high, 0 critical.

What I fixed: Updated turbo from 2.9.6 to 2.9.15 to patch two moderate vulnerabilities (a CSRF in the login callback and a code execution issue in Yarn Berry detection). Both were real, and the fix was a one-line pnpm update.

What I didn't fix (and why): The remaining 6 advisories are all transitive dependencies. PostCSS fires on Next.js's internal copy (our direct install is already patched). The ws advisory requires running a WebSocket server accepting untrusted binary frames, which we don't do. The uuid advisory requires non-standard buffer argument usage. The brace-expansion advisory is dev-only ESLint tooling. None of these are exploitable in our deployment.


Test 5: Supabase Security Advisors

What it checks: Supabase's built-in linter scans your database for security issues, specifically missing Row-Level Security policies, exposed tables, and misconfigured permissions.

What it found: 4 INFO-level findings. Four snapshot tables (used for the free Buyer Voice Snapshot feature) had RLS enabled but no explicit policies. This is secure by default, because RLS without policies means deny-all for non-service-role connections. But the intent wasn't explicit.

What I fixed: Added explicit service_role full access policies to all four tables. Re-ran the advisor. Zero findings.


Test 6: Full RLS Sweep

What it checks: A manual query across every table in the public schema to verify RLS is enabled and policies are correctly scoped.

What it found: All 19 tables have RLS enabled. Every user-facing table has ownership-scoped policies (users can only read, update, or delete their own data). Service-role-only tables have explicit service-role policies. No table allows cross-user data access.

Policy patterns verified:

  • User data tables (scans, generations, voice maps): full CRUD scoped to auth.uid()
  • Read-only tables (credit balances, usage logs): SELECT for owner, ALL for service role
  • Internal tables (raw content, buyer entities, stripe events): service-role only, no user access
  • Snapshot tables: service-role only, no user access

The Scorecard

TestToolResultFindings Fixed
HTTP HeadersMozilla ObservatoryCSP enforced, headers hardenedCase-sensitivity bug, 7 directive additions
Web App ScanOWASP ZAP0 failures, 61+ checks passX-Powered-By removed
CVE ScanNuclei0 findings, 12,056 templatesNone needed
Dependenciespnpm audit6 remaining (all transitive)turbo 2.9.6 to 2.9.15
Database AdvisorsSupabase Linter0 findings4 explicit RLS policies added
RLS SweepManual SQL19/19 tables coveredNone needed

Total cost: $0. Every tool was free.


What This Doesn't Cover

This audit tested the production attack surface from the outside and the database layer from the inside. It did not cover:

  • Authenticated scanning. ZAP and Nuclei tested unauthenticated access only. Authenticated fuzzing of dashboard routes and API endpoints is a separate test.
  • Business logic testing. Credit deduction race conditions, Stripe webhook replay attacks, and pipeline failure handling were tested during development but not re-validated in this audit.
  • Third-party supply chain. We trust Supabase, Vercel, Stripe, and Cloudflare to secure their own infrastructure. Their SOC 2 reports and security programs are part of our vendor assessment, not this audit.
  • Nonce-based CSP. The unsafe-inline tradeoff remains until we implement per-request nonce middleware. It's on the roadmap.

A security audit is a snapshot, not a certification. We run dependency scans in CI, monitor for anomalies with Sentry and PostHog, and patch critical vulnerabilities within 72 hours.


The Tools

All free. All open source (except Observatory, which is Mozilla-hosted).

  1. Mozilla Observatory (observatory.mozilla.org). HTTP header analysis. 2 minutes per domain.
  2. OWASP ZAP (zaproxy.org). Web app vulnerability scanner. Docker image, 5 minutes per domain.
  3. Nuclei (projectdiscovery.io). CVE-specific template scanner. 6,000+ templates, 2 minutes per domain.
  4. pnpm audit. Dependency vulnerability check. 10 seconds.
  5. Supabase Security Advisors. Built-in database linter. Available in every Supabase project.
  6. Manual SQL. Custom RLS verification query. 30 seconds.

Total active time: about 30 minutes for setup and execution, plus an afternoon of fixes.


Why This Matters for E-Commerce Sellers

If you're evaluating any SaaS tool that touches your product data, competitive research, or business strategy, ask them one question: "What did your last security audit find?"

If they can't answer with specifics, they haven't done one.

DecodeIQ's security posture is documented at decodeiq.ai/security. Our vulnerability management process accepts reports at security@decodeiq.ai.

We don't just say we take security seriously. We test it, fix what we find, and show the results.

Jack Metalle
Jack Metalle

Jack Metalle is the Founding Technical Architect of DecodeIQ, a buyer intelligence platform that helps e-commerce sellers understand how their customers actually think, compare, and decide. His M.Sc. thesis (2004) predicted the shift from keyword-based to semantic retrieval systems. He has spent two decades building systems that extract structured meaning from unstructured data.

See how your category's buyers actually talk

DecodeIQ scans real buyer conversations across Reddit, YouTube, reviews, and forums, then generates listing copy that speaks your buyer's language.