AI assistants generate TypeScript with the same structural blind spots they produce in Python — just wearing a type annotation. The types compile. The security bugs ship. BrassCoders scans TypeScript automatically, using three layered scanners, and the patterns it finds in AI-generated TS code cluster around four categories: hardcoded credentials in configuration files, eval() in utility functions, unvalidated request data reaching SQL and command sinks, and XSS via template-literal DOM writes.
BrassCoders and TypeScript: The Scanner Stack
BrassCoders scans .ts and .tsx files through three distinct engines that run in the same pass as the Python scan. First: a Babel-based AST scanner (JavaScriptTypeScriptScanner) that parses TypeScript into an abstract syntax tree and matches patterns at the node level — dangerous_eval, innerHTML_usage, hardcoded credential assignments. Second: the Semgrep taint engine, whose bundled rules explicitly declare languages: [javascript, typescript], covering SQL injection, XSS, command injection, and SSRF with full taint tracking from source to sink. Third: ast-grep, with dedicated TypeScript rule files (xss_ts.yml, sql_injection_ts.yml) that match template-literal interpolation patterns Babel misses. The detect-secrets layer also covers .ts and .tsx for credential format matching.
That's narrower than Python, and deliberately so. Bandit has no TypeScript equivalent. Pyre/Pysa — the cross-file taint engine — is Python-only. The TypeScript scanner stack catches what the pattern and taint layers can see; it does not trace taint across module boundaries the way Pysa does for Python.
Hardcoded API Keys in TypeScript Configurations
BrassCoders detects hardcoded credentials in TypeScript files through two mechanisms: the detect-secrets plugin suite (which covers .ts and .tsx explicitly in its extension list) and the Babel AST scanner's credential-pattern detector, which flags potential_api_key, aws_api_key, and hardcoded_password patterns based on identifier names and assignment context.
AI assistants produce this pattern at a high rate. A model generating a Next.js API route or an Express middleware will often inline an API key directly into the source file rather than reaching for process.env. The pattern looks structurally correct: it's typed, it's wired up properly, it passes the compiler. The credential in source control is the problem, and neither TypeScript nor ESLint's standard ruleset catches it. The Babel scanner does — the finding points at the assignment node, not a text match that might fire inside a comment.
One nuance worth stating: the Babel scanner requires the assignment to look like a real credential context, not a test fixture. A hardcoded password in a file under e2e/tests/ or __tests__/ gets its severity downgraded by one rung. Production source keeps full severity.
eval() in AI-Generated TypeScript Utilities
BrassCoders flags eval() calls in TypeScript via the Babel AST scanner, which checks CallExpression nodes for callee.name === 'eval' at parse time.
The pattern appears in AI-generated TypeScript more often than in human-authored TypeScript for a specific reason: AI assistants trained on JavaScript frequently reach for eval() as a shortcut in utility functions — JSON parsing fallbacks, dynamic property access, templating helpers. TypeScript doesn't remove the risk. eval() in TypeScript still executes arbitrary code in the current scope; the type annotations on the surrounding function don't affect what the string argument does at runtime. The Babel finding identifies the specific call node and recommends JSON.parse() or structured alternatives. The finding is AST-aware: it won't fire on a string that says "eval" inside a comment or a documentation block.
Missing Input Validation in Express Handlers
BrassCoders catches unvalidated request data reaching dangerous sinks via Semgrep taint rules, not via a missing-validation check.
The distinction matters. BrassCoders doesn't flag an Express handler that accepts req.body without a schema — that's a code-quality concern, and catching it without taint context would produce substantial noise. It flags req.body data that flows to a SQL execution sink without parameterization, to a shell execution call, to an outbound URL, or to an innerHTML assignment. The Semgrep SQL injection rule covers pg, mysql2, sqlite3, knex, and the Prisma $queryRawUnsafe / $executeRawUnsafe variants. The XSS rule covers React's dangerouslySetInnerHTML, direct DOM writes, and jQuery's .html(). Both rules declare languages: [javascript, typescript] and handle Next.js App Router patterns — route handler parameters typed as NextRequest are recognized as taint sources. These findings carry CRITICAL or HIGH severity and a taint path showing source and sink location.
What BrassCoders Misses in TypeScript
BrassCoders's TypeScript coverage has a real ceiling, and stating it matters.
Cross-file taint doesn't work for TypeScript. Pyre/Pysa traces tainted values across module boundaries in Python; there's no equivalent engine in the TypeScript stack. A tainted value that enters req.body in one file, gets extracted by a helper in a second file, and reaches a SQL call in a third file — the taint path breaks at the import boundary. Semgrep's taint mode is intraprocedural. For TypeScript services where deep dataflow coverage is critical, pair BrassCoders with a TypeScript-native analyzer like CodeQL — BrassCoders for the patterns-plus-credential pass, CodeQL for full interprocedural taint.
Missing input validation as a standalone pattern is also outside scope. BrassCoders doesn't emit a finding for a handler that fails to check a schema before trusting req.body, unless that body data reaches a known dangerous sink. The handler is noisy but not flagged. This follows the division of labor the product is built on: BrassCoders reports what the scanners can see structurally; the AI assistant consuming the YAML output provides the context-aware triage.
Finally: there is no TypeScript equivalent of Bandit's rule set. Bandit covers ~50 Python-specific security patterns (weak crypto, subprocess shell injection, hardcoded passwords in function calls). The TypeScript coverage is narrower — the Babel, Semgrep, and ast-grep layers together cover the highest-value patterns in AI-generated TS, not the full surface Bandit reaches for Python.
How to Run the Scan
pip install brasscoders
brasscoders scan
TypeScript detection activates automatically when the scan finds .ts or .tsx files. Node.js 18+ must be on PATH for the Babel AST layer; if Node.js is absent, the Babel scanner soft-fails and the Semgrep and ast-grep layers continue. Findings land in .brass/ tagged by scanner. The YAML output is designed for consumption by Claude Code or Cursor — each finding includes a remediation note, a severity, and a taint path where the scanner produced one.
For projects with both Python and TypeScript, a single brasscoders scan covers both in one pass. The .brass/ output merges findings from all scanner layers, deduplicated, with the language tagged in each finding's metadata.
Top comments (0)