Skip to content

TypeScript API

The CLI is the fastest way to get started. The TypeScript API is for when you need Firmis inside your own tooling — custom CI scripts, security dashboards, editor integrations, or automated remediation pipelines.

Install firmis-scanner as a dependency rather than running it via npx.

Terminal
npm install firmis-scanner
Terminal
# or with pnpm / yarn
pnpm add firmis-scanner
yarn add firmis-scanner
security-check.ts
import { ScanEngine } from 'firmis-scanner'
import type { FirmisConfig, ScanResult } from 'firmis-scanner'
const config: FirmisConfig = {
severity: 'low',
output: 'json',
verbose: false,
concurrency: 4,
}
const engine = new ScanEngine(config)
await engine.initialize()
const result: ScanResult = await engine.scan()
console.log(`Security grade: ${result.score}`)
console.log(`Threats found: ${result.summary.threatsFound}`)
console.log(`Critical: ${result.summary.bySeverity.critical}`)
console.log(`High: ${result.summary.bySeverity.high}`)
scan-directory.ts
import { ScanEngine } from 'firmis-scanner'
import type { FirmisConfig } from 'firmis-scanner'
async function scanDirectory(targetPath: string): Promise<void> {
const config: FirmisConfig = {
targetPath,
severity: 'high',
output: 'json',
verbose: false,
concurrency: 4,
failOnSeverity: 'high',
}
const engine = new ScanEngine(config)
await engine.initialize()
const result = await engine.scan()
if (result.summary.bySeverity.critical > 0 || result.summary.bySeverity.high > 0) {
console.error(`Found ${result.summary.threatsFound} threats — grade ${result.score}`)
process.exit(1)
}
console.log(`Clean scan — grade ${result.score}`)
}
await scanDirectory('./packages/agent')
scan-mcp.ts
import { ScanEngine } from 'firmis-scanner'
import type { FirmisConfig, PlatformType } from 'firmis-scanner'
const platforms: PlatformType[] = ['mcp', 'claude']
const config: FirmisConfig = {
platforms,
severity: 'low',
output: 'json',
verbose: false,
concurrency: 4,
}
const engine = new ScanEngine(config)
await engine.initialize()
const result = await engine.scan()
for (const platformResult of result.platforms) {
console.log(`Platform: ${platformResult.platform}`)
console.log(` Threats: ${platformResult.threats.length}`)
for (const threat of platformResult.threats) {
console.log(` [${threat.severity.toUpperCase()}] ${threat.ruleId}: ${threat.message}`)
console.log(` at ${threat.location.file}:${threat.location.line}`)
}
}

Use onProgress to stream scan milestones to your UI or logs.

scan-with-progress.ts
import { ScanEngine } from 'firmis-scanner'
import type { FirmisConfig, ProgressEvent } from 'firmis-scanner'
const config: FirmisConfig = {
severity: 'low',
output: 'json',
verbose: false,
concurrency: 4,
onProgress: (event: ProgressEvent) => {
switch (event.type) {
case 'rules_loaded':
console.log('[1/3] Rules loaded')
break
case 'discovery_complete':
console.log('[2/3] Discovery complete')
break
case 'platform_start':
console.log(`[3/3] Scanning ${event.platform}...`)
break
case 'component_complete':
console.log(` Done: ${event.component}`)
break
}
},
}
const engine = new ScanEngine(config)
await engine.initialize()
const result = await engine.scan()
console.log(`Finished — ${result.summary.threatsFound} threats in ${result.duration}ms`)

GitHub, VS Code, and every major SAST dashboard speaks SARIF. Generate it directly from the API:

scan-to-sarif.ts
import { ScanEngine, getReporter } from 'firmis-scanner'
import type { FirmisConfig } from 'firmis-scanner'
import { writeFile } from 'node:fs/promises'
const config: FirmisConfig = {
severity: 'low',
output: 'sarif',
outputFile: 'firmis.sarif',
verbose: false,
concurrency: 4,
}
const engine = new ScanEngine(config)
await engine.initialize()
const result = await engine.scan()
const reporter = getReporter('sarif')
const sarifOutput = await reporter.generate(result, config)
await writeFile('firmis.sarif', sarifOutput)
console.log('SARIF written to firmis.sarif')

Load additional YAML rule files alongside the 209 built-in rules.

scan-with-custom-rules.ts
import { ScanEngine } from 'firmis-scanner'
import type { FirmisConfig } from 'firmis-scanner'
import { resolve } from 'node:path'
const config: FirmisConfig = {
severity: 'low',
output: 'json',
verbose: false,
concurrency: 4,
customRules: [
resolve('./rules/company-policy.yaml'),
resolve('./rules/internal-secrets.yaml'),
],
}
const engine = new ScanEngine(config)
await engine.initialize()
const result = await engine.scan()
console.log(`Scanned with ${result.platforms.length} platforms`)
scan-with-ignores.ts
import { ScanEngine } from 'firmis-scanner'
import type { FirmisConfig } from 'firmis-scanner'
const config: FirmisConfig = {
severity: 'low',
output: 'json',
verbose: false,
concurrency: 4,
ignoreRules: ['sd-015', 'pi-003'], // skip these rule IDs
exclude: ['test/fixtures/', 'dist/'], // skip these paths
}
const engine = new ScanEngine(config)
await engine.initialize()
const result = await engine.scan()
scan-with-error-handling.ts
import { ScanEngine, FirmisError, isFirmisError } from 'firmis-scanner'
import type { FirmisConfig, ScanResult } from 'firmis-scanner'
async function runScan(targetPath: string): Promise<ScanResult | null> {
const config: FirmisConfig = {
targetPath,
severity: 'low',
output: 'json',
verbose: false,
concurrency: 4,
}
try {
const engine = new ScanEngine(config)
await engine.initialize()
return await engine.scan()
} catch (err) {
if (isFirmisError(err)) {
// Structured Firmis error with a user-friendly message
console.error(`Firmis error [${err.code}]: ${err.message}`)
} else {
console.error('Unexpected error during scan:', err)
}
return null
}
}
const result = await runScan('./agent')
if (result) {
console.log(`Grade: ${result.score}`)
}

The main entry point for programmatic scanning.

class ScanEngine {
constructor(config: FirmisConfig)
initialize(): Promise<void>
scan(): Promise<ScanResult>
}

Configuration object passed to ScanEngine.

interface FirmisConfig {
/** Platforms to scan (undefined = auto-detect all) */
platforms?: PlatformType[]
/** Target path to scan */
targetPath?: string
/** Minimum severity to report */
severity: SeverityLevel
/** Custom rule file paths */
customRules?: string[]
/** Paths to exclude */
exclude?: string[]
/** Output format */
output: OutputFormat
/** Output file path (for json/sarif/html) */
outputFile?: string
/** Enable verbose logging */
verbose: boolean
/** Parallel worker count */
concurrency: number
/** Stop on first critical threat */
failFast?: boolean
/** Suppress terminal output */
quiet?: boolean
/** Rule IDs to skip */
ignoreRules?: string[]
/** Severity that triggers non-zero exit */
failOnSeverity?: SeverityLevel
/** Progress callback */
onProgress?: (event: ProgressEvent) => void
}

Returned by engine.scan().

interface ScanResult {
id: string
startedAt: Date
completedAt: Date
duration: number // milliseconds
platforms: PlatformScanResult[]
summary: ScanSummary
score: SecurityGrade // 'A' | 'B' | 'C' | 'D' | 'F'
runtimeRisksNotCovered: string[]
}

Individual detected threat.

interface Threat {
id: string
ruleId: string
category: ThreatCategory
severity: SeverityLevel // 'low' | 'medium' | 'high' | 'critical'
message: string
evidence: Evidence[]
location: SourceLocation
confidence: number // 0–1
confidenceTier: ConfidenceTier // 'suspicious' | 'likely' | 'confirmed'
remediation?: string
}

Get a reporter instance for a given output format.

import { getReporter } from 'firmis-scanner'
const reporter = getReporter('sarif') // 'terminal' | 'json' | 'sarif' | 'html'
const output = await reporter.generate(result, config)
import { FirmisError, ConfigurationError, PlatformError, isFirmisError } from 'firmis-scanner'
// isFirmisError narrows unknown to FirmisError
if (isFirmisError(err)) {
console.error(err.code, err.message)
}

Firmis is written in TypeScript and ships its own types. No @types/firmis-scanner package is needed.

tsconfig.json
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2022",
"strict": true
}
}