Skip to content

Repository files navigation

Sentinel

Catch integration issues, API contract mismatches, configuration problems, and breaking changes before deployment.

CI

License: MIT

Today: Sentinel's phase-one CLI is functional and installable from npm. It scans frontend codebases, extracts API calls, and reports static analysis findings. When you point contractSource at a local OpenAPI v3 JSON file, it also checks request-body shapes against your backend schema for matched routes.

Not yet in this release: response-shape diffing, unmatched-endpoint flagging, third-party rules, and the cloud SDK, VS Code extension, and GitHub Action (future phases).


What is Sentinel?

Sentinel is an open-source static analysis platform focused on API contract matching between backend and frontend. It helps teams detect integration problems at development time rather than in production.

The CLI (sentinel scan) walks your source, extracts fetch/axios/similar call sites, runs built-in rules, and optionally compares request bodies against an OpenAPI spec.

Contract checking (v1)

When contractSource is set, Sentinel:

  1. Parses a local OpenAPI v3 JSON spec
  2. Matches frontend calls to backend routes (method + path pattern)
  3. Diffs statically resolvable request bodies against the route schema

What v1 flags: missing required fields, unexpected fields, and literal type mismatches (api-contract-mismatch rule).

What v1 does not flag: response bodies, calls that cannot be matched to a route, calls with dynamic/unresolvable URLs or bodies, or query-string/trailing-slash path variants. Those cases are skipped (debug-logged during development, not surfaced as Findings).

OpenAPI support is deliberately scoped: local JSON file paths only — no remote URLs, no YAML.

See Testing API Contract Mismatches for a step-by-step walkthrough.


Current Status

  • sentinel scan — end-to-end analysis (config load → scan → format → stdout or file)
  • sentinel init — scaffolds sentinel.config.ts
  • Built-in rules:
    • no-hardcoded-url (default: error)
    • missing-error-handler (default: warning)
    • api-contract-mismatch (default: error, active when contractSource is configured)
  • CLI output: --format text|json|sarif, --output <file>, --max-warnings, exit codes 0/1/2
  • Source extensions: .ts, .tsx, .js, .jsx, .mts, .cts
  • Published on npm: @sentinel-scan/core, @sentinel-scan/cli

Not yet

  • Response-shape diffing
  • Unmatched-endpoint flagging
  • Third-party / custom rule loading
  • @sentinel-scan/ai — AI explanations (future phase)
  • @sentinel-scan/cloud-sdk — cloud dashboard upload (future phase)
  • sentinel-vscode — VS Code extension (future phase)
  • sentinel-action — GitHub Action (future phase)

Installation

Global install (recommended)

npm install -g @sentinel-scan/cli
sentinel init
sentinel scan ./src

Project-local install

npm install --save-dev @sentinel-scan/cli
npx sentinel init
npx sentinel scan ./src

Requires Node.js >=20.0.0.

Example configuration

sentinel init scaffolds a starter config with the two original rules. To enable contract checking, add contractSource and api-contract-mismatch manually:

import type { SentinelConfig } from '@sentinel-scan/core'

export default {
  include: ['src/**/*.{ts,tsx,js,jsx,mts,cts}'],
  // Omit exclude to use engine defaults, or add patterns (merged onto defaults):
  // exclude: ['generated/**'],
  rules: {
    'no-hardcoded-url': 'error',
    'missing-error-handler': 'warning',
    'api-contract-mismatch': 'error',
  },
  // Request-body contract checking against a local OpenAPI v3 JSON file only
  // (no remote URLs, no YAML)
  contractSource: './openapi/api.json',
} satisfies SentinelConfig

contractSource paths are resolved relative to rootDir (the directory you scan, or the config file's directory when discovered automatically).

Testing API Contract Mismatches

This section walks through enabling api-contract-mismatch and verifying it works end to end — including v1 scope limits discovered from real-project testing.

Prerequisites

You need a local OpenAPI v3 JSON spec file on disk.

Supported: OpenAPI 3.x documents saved as .json with an openapi version field and a paths object.

Not supported:

  • YAML specs (.yaml / .yml) — rejected with an explicit parse error
  • Remote URLs — contractSource must be a filesystem path
  • OpenAPI 2 / Swagger (swagger: "2.0") — validation requires openapi: "3.x.x"
  • Postman collection exports — these are not OpenAPI specs. Pointing contractSource at a Postman JSON file produces a config-warning (for example, OpenAPI spec must declare openapi version 3.x.x). Convert via Postman's export-as-OpenAPI feature (if available in your Postman version) or generate a real spec from your backend framework.

Config setup

Minimal working config:

import type { SentinelConfig } from '@sentinel-scan/core'

export default {
  contractSource: './openapi.json',
  rules: {
    'api-contract-mismatch': 'error',
  },
} satisfies SentinelConfig

Contract checking runs when contractSource is set and the rule is not 'off'. If you omit the rule from config, the engine default is error.

What v1 actually checks

Be precise about scope — silence in a real codebase is often expected, not a bug:

  • Request body mismatches only — missing required fields, unexpected fields, and literal type mismatches. Response shapes are not checked.
  • Statically resolvable URLs only — the URL in the call must be a string literal or a matchable template literal (static path segments with ${…} placeholders). A call like fetch(apiUrl('/api/users')) where the URL is returned by a helper (call-expression urlKind) will not match any route and produces no finding.
  • Statically resolvable request bodies only — the body must be an object literal directly in the call (e.g. axios.post('/users', { name: 'Alice' })). GET requests, calls with no body, or bodies built from variables/spreads/functions produce no finding (not-diffable — expected).
  • Exact route matching — method and path segments must align with the spec (including {param} templates). v1 does not normalize query strings (/users/1?page=1 won't match {id}) or trailing slashes (/users/ vs /users).
  • Unmatched calls produce no output — if no route in the spec corresponds to the call, Sentinel skips it silently (known v1 limitation, not an error).

Findings appear only when a matched call has request-body discrepancies (discrepancies-found). Unmatched, unresolvable, compatible, and not-diffable cases emit no findings (use --verbose for debug details).

Minimal “does it work” test

Use this isolated recipe to confirm the feature works before debugging your real codebase's calling conventions:

1. Create openapi.json:

{
  "openapi": "3.0.3",
  "info": { "title": "Test", "version": "1.0.0" },
  "paths": {
    "/users": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": { "201": { "description": "Created" } }
      }
    }
  }
}

2. Create api.ts:

axios.post('/users', { wrongField: 'x' })

Use a direct string-literal URL and inline object literal — not a URL helper or variable body.

3. Create sentinel.config.ts (as shown in Config setup above).

4. Run:

sentinel scan . --verbose

5. Expect: one api-contract-mismatch finding mentioning a missing required field name for POST /users.

If this recipe passes but your project shows silence, the feature is working — your project's URL/body patterns likely fall outside v1 static-analysis limits.

Troubleshooting — silence is often expected

CLI inspection:

sentinel scan ./src --verbose --format json
  • --verbose (stderr): Parsed N backend route(s), Contract match skipped (unmatched/unresolvable + reason), Contract diff skipped (not-diffable + reason)
  • --format json: inspect apiCalls[], findings[], and diagnostics[] (look for config-warning if the spec failed to parse)

Common reasons for no api-contract-mismatch finding:

Cause Example Verbose hint
Unresolvable URL fetch(apiUrl('/users')) Contract match skippedcall-expression
No static request body GET request, or axios.post(url, payload) where payload is a variable Contract diff skipped — body not statically resolvable
Unmatched route Path or method doesn't exist in spec Contract match skippedunmatched
Compatible diff Body matches spec (no discrepancy) No diff skip — simply no findings
Spec parse failure Wrong file format, Postman export, missing openapi 3.x config-warning in diagnostics

Programmatic debugging (@sentinel-scan/core exports):

import {
  scan,
  resolveConfig,
  parseOpenApiSpec,
  matchApiCalls,
  diffRequestBodies,
} from '@sentinel-scan/core'

const result = await scan(resolveConfig({ rootDir: '.', contractSource: './openapi.json' }))
const spec = await parseOpenApiSpec('/absolute/path/to/openapi.json')
if (spec.ok) {
  const matches = matchApiCalls(result.apiCalls, spec.value)
  const diffs = diffRequestBodies(result.apiCalls, matches, spec.value)
  // Inspect match.status / match.reason and diff.status / diff.reason per call
}

CLI examples

# Scan with JSON output
sentinel scan ./src --format json

# Write SARIF for GitHub Code Scanning
sentinel scan ./src --format sarif --output results.sarif

# Fail CI if more than 10 warnings
sentinel scan ./src --max-warnings 10

Configuration reference

Sentinel looks for config files by walking up from the scan directory:

sentinel.config.tssentinel.config.jssentinel.config.mjs.sentinelrc.json

If none is found, engine defaults apply. Override with --config <path>.

Field Default / notes
include **/*.{ts,tsx,js,jsx,mts,cts} — replaces default when set in config
exclude Merged onto defaults: node_modules, dist, build, *.test.* / *.spec.* (TS), *.d.ts, *.min.js / *.min.mjs, vendor dirs
rules Per-rule severity: error, warning, info, hint, or off
contractSource Optional path to OpenAPI v3 JSON spec (relative to rootDir). No URLs or YAML.
baseUrl Optional prefix for relative URL display
tsConfigPath Optional path to tsconfig.json for path alias resolution

Default rule severities (when not overridden in config):

Rule Default
no-hardcoded-url error
missing-error-handler warning
api-contract-mismatch error

CLI reference

sentinel scan [path] [options]
Flag Description
-f, --format <text|json|sarif> Output format (default: text)
-o, --output <file> Write output to file instead of stdout
-c, --config <file> Explicit config file path
--root-dir <dir> Override scan root directory
--max-warnings <n> Exit 1 if warning count exceeds n (default: disabled)
-v, --verbose Enable debug logging (stderr)
--no-color Disable ANSI colors
-h, --help Show help

Exit codes

Code Meaning
0 Scan completed; no error-severity findings; warnings within --max-warnings threshold (if set)
1 Scan completed; one or more error findings, or warnings exceeded --max-warnings
2 Setup failure (invalid path, config load error, usage error)

Log output goes to stderr; scan results go to stdout (or --output).


Programmatic usage

@sentinel-scan/core exposes the analysis engine for library consumers:

npm install @sentinel-scan/core typescript

TypeScript (>=5.0.0) is a required peer dependency — it is used at runtime for AST parsing. Install it alongside core if your project does not already have it.

import { resolveConfig, scan } from '@sentinel-scan/core'

const result = await scan(
  resolveConfig({
    rootDir: './src',
    contractSource: './openapi/api.json',
  }),
)

console.log(result.findings)
console.log(result.diagnostics)

Packages

Package Status
@sentinel-scan/core Published — analysis engine (npm)
@sentinel-scan/cli Published — CLI (npm)
@sentinel-scan/ai Planned — not on npm (future phase)
@sentinel-scan/cloud-sdk Planned — not on npm (future phase)
sentinel-vscode Planned — not on npm (future phase)
sentinel-action Planned — not on npm (future phase)

Developing from source

For contributors working from a clone (not the primary install path):

git clone https://github.com/blaycoder/Sentinel.git
cd Sentinel
npm install
npm run build
npm exec -w @sentinel-scan/cli -- sentinel scan ./src

See CONTRIBUTING.md for commit conventions, dependency boundaries, and the PR checklist.

Architecture docs are planned.


License

MIT

Maintainers

See RELEASING.md for npm publish instructions.

About

No description, website, or topics provided.

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages