Ban an API pattern

This is the single most common reason teams adopt opensip: "I want to fail CI when anyone uses X." The X is usually a deprecated function, an unsafe primitive, a private/internal API that leaked, or a slow path you've already deprecated in the docs.

This guide uses crypto.createCipher as the running example. It's a real-world ban shape: createCipher is a legacy, unsafe API family (no IV, MD5-based KDF), and the safer replacement is crypto.createCipheriv(). Banning the deprecated form in your codebase is exactly the kind of architectural rule opensip-cli exists to enforce.

We'll write the ban two ways: with a regex (5 minutes, works for ~80% of cases) and with the TypeScript AST (a bit more code, catches the cases regex misses).

The regex version

When the API name is distinctive enough that you can grep for it, a regex check is sharp and tiny.

Create opensip-cli/fit/checks/no-create-cipher.mjs:

import { defineCheck } from '@opensip-cli/fitness';

export default defineCheck({
  id: '2b2b2b2b-2b2b-4b2b-8b2b-2b2b2b2b2b2b',
  slug: 'no-create-cipher',
  description: 'Disallow crypto.createCipher (legacy unsafe API). Use createCipheriv instead.',
  tags: ['security', 'deprecated-api'],
  scope: { languages: ['typescript'], concerns: [] },
  // Strip strings + comments so the check doesn't false-positive on
  // a comment that mentions `createCipher` or a string literal.
  contentFilter: 'strip-strings-and-comments',

  analyze(content, filePath) {
    const violations = [];
    const pattern = /\bcreateCipher\s*\(/g;
    let match;
    while ((match = pattern.exec(content)) !== null) {
      // Count line number by scanning newlines before the match
      const line = content.slice(0, match.index).split('\n').length;
      violations.push({
        line,
        message: 'Use crypto.createCipheriv(); createCipher is a legacy unsafe API.',
        severity: 'error',
      });
    }
    return violations;
  },
});

Three things worth noting:

Run it:

opensip fit --check no-create-cipher --verbose

When regex isn't enough

The regex catches createCipher(...). It misses:

If those cases matter, you want an AST-driven check. The TypeScript-AST helpers live in @opensip-cli/lang-typescript:

import { defineCheck } from '@opensip-cli/fitness';
import { getSharedSourceFile, walkNodes } from '@opensip-cli/lang-typescript';

export default defineCheck({
  id: '3c3c3c3c-3c3c-4c3c-8c3c-3c3c3c3c3c3c',
  slug: 'no-create-cipher-ast',
  description: 'Disallow crypto.createCipher (AST-based — catches aliases and indirection)',
  tags: ['security', 'deprecated-api'],
  scope: { languages: ['typescript'], concerns: [] },

  analyze(content, filePath) {
    const violations = [];
    const sf = getSharedSourceFile(filePath, content);

    walkNodes(sf, (node) => {
      // Direct call: createCipher(...) or crypto.createCipher(...)
      if (node.kind === /* ts.SyntaxKind.CallExpression */ 213) {
        const expr = node.expression;
        const name = expr.name?.escapedText ?? expr.escapedText;
        if (name === 'createCipher') {
          violations.push({
            line: sf.getLineAndCharacterOfPosition(node.getStart()).line + 1,
            message: 'Use crypto.createCipheriv(); createCipher is a legacy unsafe API.',
            severity: 'error',
          });
        }
      }
    });

    return violations;
  },
});

The AST version catches crypto.createCipher(...), createCipher(...) (post-import), and aliased imports if you walk the import map (omitted here for brevity — see findEnclosingScope and getPropertyChain in @opensip-cli/lang-typescript).

Pick one, not both. Regex is faster to write and faster to run. AST is more robust. For a banned-API check, regex usually wins; the cases AST catches are rare enough that a code-review catches them too. The exception: if the API name is a common English word (load, process, run), AST is the only way to avoid false positives.

Add to a recipe

Once the check is in opensip-cli/fit/checks/, it auto-loads. To group it with other deprecated-API bans:

// opensip-cli/fit/recipes/deprecated-apis.mjs
export const recipes = [{
  id: 'URCP_deprecated_apis',
  name: 'deprecated-apis',
  displayName: 'Deprecated APIs',
  description: 'Bans for APIs that should not appear in new code',
  checks: { type: 'tags', include: ['deprecated-api'] },
  execution: { mode: 'parallel', stopOnFirstFailure: false, timeout: 30_000 },
  reporting: { format: 'table', verbose: false },
}];

Run with opensip fit --recipe deprecated-apis.

Adopting an existing-violation ban

If the codebase already has uses of the banned API, gating on "all violations" blocks every PR until cleanup is done. Use the baseline flow instead:

opensip fit --recipe deprecated-apis --gate-save     # captures current state
opensip fit --recipe deprecated-apis --gate-compare  # fails only on *new* uses

You can fix the baseline cases over time. New PRs are blocked from adding fresh violations from day one. Full walkthrough in wire into CI and adopt in a monorepo.

Where to go next

| You want to … | Go to … |

|---|---|

| Walk the full check API surface | Plugin authoring |

| Add the GitHub Actions step | Wire into CI |

| See every built-in security/deprecation check | Checks reference |