owasp-security-checker
README.md
OWASP Security Checker
A static code analyzer for OWASP Top 10:2025 vulnerabilities—A04 (Cryptographic Failures) and A07 (Identification and Authentication Failures). It works as a VS Code extension and as a CLI for CI/CD.
Supported languages (9): JavaScript, TypeScript (+ JSX/TSX), Python, Java, Kotlin, Go, PHP, C, C++. For JS/TS, it uses semantic AST analysis (TypeScript Compiler API); for other languages, it uses a regex engine with comment masking; an ML classifier for false positives is applied to the findings across all languages.
Version 0.2.0 includes three key improvements:
| The ability to | What this provides |
|---|---|
| AST analysis (TypeScript Compiler API) | Accurate detection for JS/TS without false positives on comments and strings |
| ML classification (logistic regression) | Context-based false positive suppression (placeholders, namespace URLs, test files) |
| GitHub Actions + CLI (SARIF) | Automatic checking with every commit/PR |
Architecture
src/
├── core/ ← ядро анализа (НЕ зависит от vscode → работает и в CLI)
│ ├── engine.ts ← оркестратор: выбор анализатора + ML
│ ├── astAnalyzer.ts ← AST-детекторы для JS/TS (ts.createSourceFile)
│ ├── regexAnalyzer.ts ← regex-движок для Python/Java/Kotlin/Go/PHP/C/C++
│ ├── text.ts ← позиции, комментарии (лексер)
│ └── ml/
│ ├── features.ts ← извлечение контекстных признаков
│ ├── classifier.ts ← инференс (sigmoid(w·x))
│ ├── dataset.ts ← размеченный обучающий набор
│ ├── train.ts ← обучение (градиентный спуск + L2)
│ └── model.ts ← обученные веса (генерируется)
├── rules.ts ← база из 70+ правил (CWE/OWASP), 9 языков
├── diagnosticProvider.ts ← адаптер ядра → vscode.Diagnostic
├── hoverProvider.ts · codeActionProvider.ts · reportPanel.ts
├── extension.ts ← точка входа расширения
└── cli.ts ← headless-сканер (SARIF) для CI
The extension and CLI use the same core, so the results in the editor and in CI are identical.
How it works (full analysis pipeline)
Analysis of a single document goes through a single pipeline engine.analyze() — it is the same in both the editor and the CLI. Let’s break it down step by step.
Step 0. Input
The input is an object { code, languageId, filePath }:
code— the file’s text;languageId— a VS Code-style language identifier (javascript,kotlin,python, …). In the editor, VS Code provides this; in the CLI, it’s derived from the file extension (.kt/.kts→kotlin);filePath— path, used as a contextual hint for ML (hits in test directories are downweighted).
First, computeLineStarts() it builds a table of line-start offsets—this allows it to O(log n) convert any offset into “line:column” coordinates, independent of the editor’s API.
Step 1. Select a parser by language
┌──────────────────────────────────────────────┐
JS / TS / JSX/TSX │ AST-детекторы (TypeScript Compiler API) │
─────────────────► │ + regex-fallback для правил без AST-детектора │
│ (матчи внутри комментариев отбрасываются) │
└──────────────────────────────────────────────┘
Kotlin, Python, ┌──────────────────────────────────────────────┐
Java, Go, PHP, │ regex-движок + лексер комментариев (text.ts) │
C, C++ ───► │ (матчи внутри // … и /* … */ отбрасываются) │
└──────────────────────────────────────────────┘
- JS/TS: the code is parsed into an AST (
ts.createSourceFile). Semantic detectors (see below) look for node patterns rather than text. Rules for which there is no AST detector (AES-ECB, DES, RC4, etc.) are handled by the regex engine, but matches within comment ranges obtained fromts.createScannerare discarded. - Other languages (including Kotlin): the regex engine is used
runRegexRules(). Before that, a lightweight lexerfindCommentRanges()identifies comment ranges (//,/* */, and for Python/PHP—#), tracking string literals so that//they aren’t mistaken for comments within a string. Any match that begins inside a comment is ignored.
At this step, each rule that matches generates a “raw” hit Finding with coordinates, match text, and an engine flag (ast | regex).
Step 2. Filtering by Rules and Severity
Even before the engines are launched, ruleAllowed(): the following are taken into account disabledRules, enabledRules and minSeverity. This allows you to enable or disable individual rules and raise the severity threshold at the configuration level.
Step 3. ML scoring of each hit
For each raw hit, a vector of 12 contextual features (features.ts), and logistic regression produces P(уязвимость):
признаки(находка, строка, путь) → P = σ(w · x) → сравнение с порогом (0.5)
The features are grouped by rule type: value-carrying features (entropy, length, placeholder, namespace-URL) are calculated only for secret rules (*-hardcoded-secret) and HTTP rules (*-http-url). Therefore, for example, kotlin-hardcoded-secret both kotlin-http-url automatically receive “meaningful” features—precisely because of the uniform naming convention for rules, supporting a new language does not require changing the ML code.
If P < порога, the match is marked suppressed and is hidden by default (in the CLI, it can be shown with the flag --include-suppressed; in the editor, via the setting showSuppressedAsHints).
Step 4. Result
The remaining hits are sorted by position and returned. Next:
- in the editor
diagnosticProvider.tsconverts them tovscode.Diagnostic(with the engine tag and % confidence); - in the CLI, they are printed and/or exported to SARIF 2.1.0 for GitHub Code Scanning.
Adding Support for a New Language (Using Kotlin as an Example)
- Define rules with
languages: ['kotlin']inrules.ts. For secret/URL rules, follow the naming convention (kotlin-hardcoded-secret,kotlin-http-url) so that ML features are enabled automatically. - Register
kotlininSUPPORTED_LANGUAGES(extension.ts) and map extensions.kt/.ktsincli.ts. - (Optional) add the language to the general rules (for example,
jwt-alg-none). - Expand the training set
dataset.tswith examples in the new language and runnpm run train.
No need to write an AST parser: Kotlin, like Java/Go/PHP, is handled by the regex engine with comment masking.
How AST analysis works
For JavaScript/TypeScript, the code is parsed by the TypeScript parser into a tree (AST). Detectors match semantic forms, not text:
crypto.createHash('md5')only a mention within// комментарииor within a string"...createHash('md5')..."is ignored;res.cookie(..., { httpOnly: true, secure: true })not highlighted, because the flags are actually present in the object;{ alg: 'none' },origin: '*',rejectUnauthorized: falseare found as tree nodes.
Other languages use a regex engine with comment masking (the lexer in text.ts).
How the ML classifier works
Each match is converted into a vector of contextual features (features.ts): rule type, severity, entropy, and value length; placeholder features; context reading; test path; namespace URL; and more. Logistic regression calculates the probability that the match is a genuine vulnerability. Findings below the threshold (mlThreshold, default 0.5) are hidden.
Retraining the model:
npm run train # компилирует проект и пересчитывает src/core/ml/model.ts
How to see how ML works
By default, ML hides false positives, so its effect is not immediately apparent. To see it:
- In the editor, enable
owaspChecker.showSuppressedAsHints = true: suppressed detections will appear in gray with the label “🤖 Suppressed by ML (confidence N%)”. - In the console—run the CLI with
--include-suppressed(confidence percentages are visible):
node ./out/cli.js demo/ml-secrets.js --include-suppressed
Sample files where true and false positives look identical for a regex, but ML distinguishes between them:
| File | What it shows | Result |
|---|---|---|
demo/ml-secrets.js |
Real secrets vs. placeholders/low entropy | 4 retained (91–94%), 7 suppressed (3–7%) |
demo/ml-urls.ts |
Real HTTP traffic vs. XML namespace | 3 left (91%), 6 suppressed (12%) |
demo/ml-context.py |
Secrets in code vs. placeholders | 3 left (90–93%), 5 suppressed (3–7%) |
CLI and GitHub Action
npm run compile
node ./out/cli.js <пути...> [опции]
--sarif <file> SARIF 2.1.0 отчёт (для GitHub Code Scanning)
--min-severity <sev> critical|high|medium|low (по умолчанию low)
--fail-on <sev> выход с кодом 1 при находке ≥ уровня (по умолчанию high)
--no-ml отключить ML-классификатор
--include-suppressed показать и подавленные находки
--json | --quiet
GitHub Action (.github/workflows/security-scan.yml in the repository root) runs on every push/PR, generates a SARIF, and uploads it to Security → Code scanning; it fails on CRITICAL/HIGH.
Pre-commit hook (.githooks/pre-commit):
git config core.hooksPath .githooks
Testing
npm test # 24 теста (node:test): AST, ML, движок, метрики, Kotlin
Accuracy comparison using a set of false positives (demo/false-positives.js):
| Method | Detections | Reference | Precision |
|---|---|---|---|
| Regex (naive) | 11 | 2 | 18% |
| AST | 7 | 2 | 29% |
| AST + ML | 2 | 2 | 100% |
Compilation and Installation
npm install
npm run compile
npm run package # → owasp-security-checker-0.2.0.vsix
code --install-extension owasp-security-checker-0.2.0.vsix
Extension Settings
| Parameter | Default | Description |
|---|---|---|
owaspChecker.enabled |
true |
Auto-analysis on opening/saving |
owaspChecker.analyzeOnType |
true |
Live analysis during input, without saving |
owaspChecker.analyzeOnTypeDelay |
400 |
Delay (ms) before re-analysis |
owaspChecker.minSeverity |
low |
Minimum level |
owaspChecker.useMlClassifier |
true |
ML false positive suppression |
owaspChecker.mlThreshold |
0.5 |
ML confidence threshold |
owaspChecker.showSuppressedAsHints |
false |
Show suppressed ML findings as hints |
owaspChecker.disabledRules / enabledRules |
[] |
Rule management |
Releases
No releases yet.
Open issues
No open issues 🎉