vanguard-cli 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ {
2
+ "hooks": {
3
+ "before:init": [
4
+ "npm test"
5
+ ]
6
+ },
7
+ "git": {
8
+ "commitMessage": "chore: release v${version}",
9
+ "tagName": "v${version}",
10
+ "requireCleanWorkingDir": true
11
+ },
12
+ "github": {
13
+ "release": true,
14
+ "releaseName": "v${version}",
15
+ "tokenRef": "GITHUB_TOKEN"
16
+ },
17
+ "npm": {
18
+ "publish": true,
19
+ "skipChecks": false
20
+ },
21
+ "plugins": {
22
+ "@release-it/conventional-changelog": {
23
+ "preset": "angular",
24
+ "infile": "CHANGELOG.md"
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,44 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ ## Our Standards
8
+
9
+ Examples of behavior that contributes to creating a positive environment include:
10
+ * Using welcoming and inclusive language
11
+ * Being respectful of differing viewpoints and experiences
12
+ * Gracefully accepting constructive criticism
13
+ * Focusing on what is best for the community
14
+ * Showing empathy towards other community members
15
+
16
+ Examples of unacceptable behavior by participants include:
17
+ * The use of sexualized language or imagery and unwelcome sexual attention or advances
18
+ * Trolling, insulting/derogatory comments, and personal or political attacks
19
+ * Public or private harassment
20
+ * Publishing others' private information, such as a physical or electronic address, without explicit permission
21
+ * Other conduct which could reasonably be considered inappropriate in a professional setting
22
+
23
+ ## Our Responsibilities
24
+
25
+ Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
26
+
27
+ Maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
28
+
29
+ ## Scope
30
+
31
+ This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
32
+
33
+ ## Enforcement
34
+
35
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainers at [your-email-here]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
36
+
37
+ Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
38
+
39
+ ## Attribution
40
+
41
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
42
+
43
+ [homepage]: http://contributor-covenant.org
44
+ [version]: http://contributor-covenant.org/version/1/4
@@ -0,0 +1,48 @@
1
+ # Contributing to Vanguard 🛡️
2
+
3
+ First off, thank you for helping make the software supply chain safer! We welcome contributors of all skill levels.
4
+
5
+ ## 🛡️ Security First
6
+ Vanguard is a security tool. When contributing code, please ensure:
7
+ 1. **No new dependencies** are added unless absolutely necessary.
8
+ 2. **AI Logic** is updated in `lib/services/scanner.js` with professional prompt engineering if you add new detection capabilities.
9
+ 3. **Every new feature** MUST include unit tests in `test/`.
10
+
11
+ ## 🧪 Development Workflow
12
+ 1. **Fork & Clone**:
13
+ ```bash
14
+ git clone https://github.com/bazobehram/vanguard.git
15
+ cd vanguard
16
+ npm install
17
+ ```
18
+ 2. **Standardize**:
19
+ ```bash
20
+ npm run format # Prettier
21
+ npm run lint # ESLint Flat Config
22
+ ```
23
+ 3. **Verify**:
24
+ ```bash
25
+ npm test # Run Vitest suite
26
+ node scripts/qa-wizard.js # Run interactive stress tests
27
+ ```
28
+
29
+ ## 🧠 Improving Intelligence
30
+ The "Brain" of Vanguard lives in `lib/threats.json` and the `VanguardScanner` system instructions.
31
+ - If you find a bypass, please submit a PR with an updated prompt or threat signature.
32
+ - Specify which model you tested with (e.g., Gemini 2.0, Qwen 2.5 7B).
33
+
34
+ ## 🚀 How to Release
35
+ Maintainers can trigger a synchronized release (Version bump, Tags, GitHub Release, Changelog, and NPM) using:
36
+
37
+ 1. **Ensure environment**: You must have a `GITHUB_TOKEN` with `repo` permissions in your shell.
38
+ 2. **Execute**:
39
+ ```bash
40
+ npm run release
41
+ ```
42
+ 3. **Dry Run**: To verify changes without side effects:
43
+ ```bash
44
+ npm run release:dry
45
+ ```
46
+
47
+ ---
48
+ ☕ Support the research: [Buy Me a Coffee](https://buymeacoffee.com/bazobehram)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Behram Bazo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ <div align="center">
2
+
3
+ # 🛡️ Vanguard: AI Supply Chain Firewall
4
+ ### The Enterprise-Grade Security Layer for Git
5
+
6
+ [![npm version](https://img.shields.io/npm/v/vanguard-cli.svg?style=flat-square)](https://www.npmjs.com/package/vanguard-cli)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
8
+ [![AI Power](https://img.shields.io/badge/AI-Gemini%202.0%20%7C%20Ollama-blue?style=flat-square)](https://deepmind.google/technologies/gemini/)
9
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/bazobehram/vanguard/ci.yml?style=flat-square)](https://github.com/bazobehram/vanguard/actions)
10
+
11
+ <p align="center">
12
+ <b>Intercepts</b> <code>git clone</code> & <code>git pull</code> to detect malware, backdoors, and vulnerabilities <br>
13
+ <b>BEFORE</b> they reach your disk. Powered by Hybrid Intelligence.
14
+ </p>
15
+
16
+ [Installation](#-installation) • [Quick Start](#-quick-start) • [How It Works](#🏗️-architecture) • [Configuration](#⚙️-configuration)
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+ ## 🚀 Why Vanguard?
23
+
24
+ Modern software supply chain attacks are sophisticated. Traditional antiviruses miss them. **Vanguard** acts as a firewall between the internet and your codebase.
25
+
26
+ * 🧠 **Hybrid Intelligence:** Combines **Google Gemini 2.0**'s reasoning with a curated **Threat Intelligence DB** and **OSV.dev** vulnerability data.
27
+ * ⚡ **Zero-Latency Caching:** Uses SHA-256 hashing to memorize safe files. Re-scanning a project takes milliseconds.
28
+ * 🔒 **Privacy First:** Supports **Local Ollama**. Your code never leaves your machine if you choose local models.
29
+ * 🛡️ **Proactive Defense:** It doesn't just scan; it intercepts Git commands to prevent bad code from reaching your machine.
30
+
31
+ ---
32
+
33
+ ## 🏗️ Architecture
34
+
35
+ Vanguard operates as a protective layer between your command line and the remote repository, combining local heuristics with global intelligence.
36
+
37
+ ```mermaid
38
+ graph TD
39
+ User["User Command (clone/pull)"] --> CLI["Vanguard CLI"]
40
+ CLI --> Cache{"Cache Check (SHA-256)"}
41
+ Cache -- "Hit (Safe)" --> Finish["Verdict: PASS"]
42
+ Cache -- "Miss" --> Sync["Sync Intelligence"]
43
+ Sync --> DB["GitHub Threat DB"]
44
+ Sync --> OSV["OSV.dev API"]
45
+ DB --> Analysis["AI Analysis"]
46
+ OSV --> Analysis
47
+ Analysis -- "Gemini 2.0 / Ollama" --> Verdict{"Verdict"}
48
+ Verdict -- "Malicious" --> Block["Verdict: BLOCK"]
49
+ Verdict -- "Clean" --> SaveCache["Save to Cache"]
50
+ SaveCache --> Finish
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 📦 Installation
56
+
57
+ Ensure you have Node.js (v18+) installed.
58
+
59
+ ```bash
60
+ npm install -g vanguard-cli
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 🚀 Quick Start
66
+
67
+ ### ⚙️ Configuration
68
+ Setup your Gemini API key or local Ollama endpoint:
69
+ ```bash
70
+ vanguard config
71
+ ```
72
+
73
+ ### 📥 Secure Clone
74
+ Audit and clone any repository in a single command. Vanguard isolates the repo in a sandbox first.
75
+ ```bash
76
+ vanguard clone <repository-url>
77
+ ```
78
+
79
+ ### 🔄 Secure Pull
80
+ Safely update your local codebase. Vanguard audits the diff before merging.
81
+ ```bash
82
+ vanguard pull
83
+ ```
84
+
85
+ ---
86
+
87
+ ## 🤝 Contribution
88
+
89
+ We welcome contributors! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for local setup instructions and the [CODE_OF_CONDUCT.md](CODE_of_CONDUCT.md) for community guidelines.
90
+
91
+ ## 🔐 Security
92
+
93
+ To report a vulnerability or an exploit bypass, please follow the protocol in [SECURITY.md](SECURITY.md).
94
+
95
+ ---
96
+
97
+ ## ⚖️ License
98
+
99
+ MIT © 2026 [Behram Bazo](https://github.com/bazob)
package/SECURITY.md ADDED
@@ -0,0 +1,34 @@
1
+ # Security Policy 🛡️
2
+
3
+ ## Supported Versions
4
+
5
+ We provide security updates for the following versions:
6
+
7
+ | Version | Supported |
8
+ | ------- | ------------------ |
9
+ | 3.0.x | ✅ Yes |
10
+ | 2.x.x | ❌ No |
11
+ | 1.x.x | ❌ No |
12
+
13
+ ## Reporting a Vulnerability
14
+
15
+ Vanguard is a security tool designed to protect developers. We take security vulnerabilities very seriously.
16
+
17
+ **If you discover a vulnerability or an exploit bypass, please DO NOT open a public issue.**
18
+
19
+ Instead, please report it privately via email to:
20
+ **[behrambazo@gmail.com](mailto:behrambazo@gmail.com)**
21
+
22
+ Please include:
23
+ - A detailed description of the vulnerability.
24
+ - Steps to reproduce (a Proof of Concept).
25
+ - Possible remediation steps if known.
26
+
27
+ We will acknowledge your report within 48 hours and provide a timeline for a patch.
28
+
29
+ ## Disclosure Policy
30
+
31
+ - We follow a responsible disclosure policy.
32
+ - We ask you not to share the vulnerability publicly until we have had a chance to fix it and release a security advisory.
33
+
34
+ Thank you for helping us keep Vanguard secure! 🦾🛡️
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import inquirer from 'inquirer';
5
+ import { runConfigWizard } from '../lib/commands/config.js';
6
+ import { handlePull, handleClone } from '../lib/commands/scan.js';
7
+ import { showBanner, showFooter } from '../lib/utils/ui.js';
8
+ import config from '../lib/utils/config.js';
9
+
10
+ const program = new Command();
11
+
12
+ async function handleAction(actionName, logicFn) {
13
+ showBanner();
14
+ const options = program.opts();
15
+ config.set('VERBOSE', !!options.verbose);
16
+
17
+ const { proceed } = await inquirer.prompt([
18
+ {
19
+ type: 'confirm',
20
+ name: 'proceed',
21
+ message: `🛡️ Vanguard Enterprise Edition detected a ${actionName}. Activate Hybrid Intelligence?`,
22
+ default: true,
23
+ },
24
+ ]);
25
+
26
+ if (!proceed) {
27
+ console.log('⚠️ Skipping protection. Proceeding at your own risk.');
28
+ return;
29
+ }
30
+
31
+ if (config.get('AI_PROVIDER') === 'gemini' && !config.get('GEMINI_KEY')) {
32
+ console.log('❌ Gemini not configured.');
33
+ await runConfigWizard();
34
+ }
35
+
36
+ await logicFn();
37
+ showFooter();
38
+ }
39
+
40
+ program
41
+ .name('vanguard')
42
+ .description('Enterprise-Grade AI Supply Chain Firewall')
43
+ .version('3.0.0-enterprise')
44
+ .option('-m, --model <name>', 'Override AI model')
45
+ .option('-v, --verbose', 'Show verbose connection logs')
46
+ .option('--clear-cache', 'Flush local audit cache');
47
+
48
+ program.command('config').description('Configure AI providers').action(runConfigWizard);
49
+
50
+ program
51
+ .command('pull')
52
+ .description('Audit and pull upstream changes')
53
+ .option('-f, --force', 'Force merge even if threats are detected')
54
+ .action(async (cmdOptions) => {
55
+ await handleAction('PULL', () => handlePull(cmdOptions, program.opts()));
56
+ });
57
+
58
+ program
59
+ .command('clone')
60
+ .description('Deep audit a repository during clone')
61
+ .argument('<url>', 'Git URL')
62
+ .argument('[directory]', 'Target directory')
63
+ .action(async (url, directory) => {
64
+ await handleAction('CLONE', () => handleClone(url, directory, program.opts()));
65
+ });
66
+
67
+ program.parse(process.argv);
@@ -0,0 +1,81 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import config from '../utils/config.js';
4
+ import { createSpinner } from '../utils/spinner.js';
5
+ import { showBanner } from '../utils/ui.js';
6
+
7
+ export async function runConfigWizard() {
8
+ showBanner();
9
+ console.log('🛰️ Vanguard Configuration Wizard\n');
10
+
11
+ const { providerSelection } = await inquirer.prompt([
12
+ {
13
+ type: 'list',
14
+ name: 'providerSelection',
15
+ message: 'Select AI Provider:',
16
+ choices: [
17
+ { name: 'Google Gemini 2.0 (Cloud - Fast & Smart)', value: 'gemini' },
18
+ { name: 'Local Ollama (Private - Offline)', value: 'ollama' },
19
+ ],
20
+ },
21
+ ]);
22
+
23
+ config.set('AI_PROVIDER', providerSelection);
24
+
25
+ if (providerSelection === 'gemini') {
26
+ const { apiKey } = await inquirer.prompt([
27
+ {
28
+ type: 'password',
29
+ name: 'apiKey',
30
+ message: 'Enter your Gemini API Key:',
31
+ default: config.get('GEMINI_KEY'),
32
+ validate: (val) => val.length > 0 || 'Key is required',
33
+ },
34
+ ]);
35
+ config.set('GEMINI_KEY', apiKey);
36
+ console.log('✅ Gemini 2.0 configured and saved.');
37
+ } else {
38
+ const spinner = createSpinner('🔍 Detecting local Ollama models...').start();
39
+ try {
40
+ const res = await fetch(`${config.get('OLLAMA_URL')}/api/tags`);
41
+ if (!res.ok) throw new Error();
42
+ const data = await res.json();
43
+ spinner.stop();
44
+
45
+ const models = data.models.map((m) => m.name);
46
+ if (models.length === 0) {
47
+ console.log('❌ No local models found. Run "ollama pull qwen2.5-coder" first.');
48
+ return;
49
+ }
50
+
51
+ console.log(
52
+ chalk.blue('\n💡 Tip: For security auditing, we recommend models with strong coding logic.')
53
+ );
54
+ console.log(chalk.dim(' Recommended: qwen2.5-coder (7b+), codellama, llama3.1 (8b+)\n'));
55
+
56
+ const choices = models.map((name) => {
57
+ const isRecommended = ['qwen', 'llama3', 'coder', 'deepseek'].some((kw) =>
58
+ name.toLowerCase().includes(kw)
59
+ );
60
+ return {
61
+ name: isRecommended ? `${name} ${chalk.green.bold('(Recommended)')}` : name,
62
+ value: name,
63
+ };
64
+ });
65
+
66
+ const { model } = await inquirer.prompt([
67
+ {
68
+ type: 'list',
69
+ name: 'model',
70
+ message: 'Select your preferred local model:',
71
+ choices: choices,
72
+ default: config.get('OLLAMA_MODEL'),
73
+ },
74
+ ]);
75
+ config.set('OLLAMA_MODEL', model);
76
+ console.log(`✅ Ollama configured with model: ${model}`);
77
+ } catch {
78
+ spinner.fail('❌ Ollama not detected. Run "ollama serve" first.');
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,135 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import { createSpinner } from '../utils/spinner.js';
6
+ import { showAlert } from '../utils/ui.js';
7
+ import { VanguardScanner } from '../services/scanner.js';
8
+ import { IntelligenceEngine } from '../services/intelligence.js';
9
+ import { CacheManager } from '../utils/cache.js';
10
+ import { walkProject } from '../utils/walker.js';
11
+ import {
12
+ fetchUpstream,
13
+ getUpstreamDiff,
14
+ mergeUpstream,
15
+ createSandboxClone,
16
+ finalizeClone,
17
+ cleanupSandbox,
18
+ } from '../services/git.js';
19
+
20
+ export async function handlePull(cmdOptions, programOptions) {
21
+ if (programOptions.clearCache) CacheManager.clear();
22
+
23
+ const intel = new IntelligenceEngine(programOptions.verbose);
24
+ const spinner = createSpinner('🚀 Syncing intelligence & probing connections...').start();
25
+
26
+ const pkgContent = await fs.readFile(path.join(process.cwd(), 'package.json'), 'utf-8').catch(() => null);
27
+ const context = await intel.sync(pkgContent);
28
+
29
+ spinner.text = '🔍 Auditing diff for supply chain threats...';
30
+ await fetchUpstream();
31
+ const diff = await getUpstreamDiff();
32
+
33
+ if (!diff || diff.trim() === '') {
34
+ spinner.succeed('✅ No upstream changes to audit.');
35
+ await mergeUpstream();
36
+ return;
37
+ }
38
+
39
+ const scanner = new VanguardScanner(programOptions.model, context);
40
+ const analysis = await scanner.scan('git_diff', diff);
41
+ spinner.stop();
42
+
43
+ showAlert(analysis);
44
+
45
+ if (analysis.verdict === 'SAFE') {
46
+ console.log('✅ Integrity verified. Merging changes.');
47
+ await mergeUpstream();
48
+ } else {
49
+ const { force } = cmdOptions.force
50
+ ? { force: true }
51
+ : await inquirer.prompt([
52
+ {
53
+ type: 'confirm',
54
+ name: 'force',
55
+ message: '⚠️ BLOCK VERDICT. Force override merge?',
56
+ default: false,
57
+ },
58
+ ]);
59
+ if (force) {
60
+ await mergeUpstream();
61
+ console.log('🎉 Forced merge completed.');
62
+ } else {
63
+ console.log('❌ Operation aborted to protect your codebase.');
64
+ }
65
+ }
66
+ }
67
+
68
+ export async function handleClone(url, directory, programOptions) {
69
+ const targetPath = directory || path.basename(url, '.git');
70
+
71
+ const intel = new IntelligenceEngine(programOptions.verbose);
72
+ const spinner = createSpinner('⬇️ Isolating repository in sandbox...').start();
73
+
74
+ const tempPath = await createSandboxClone(url);
75
+ spinner.text = '🔄 Building intelligence & dependency map...';
76
+
77
+ const pkgContent = await fs.readFile(path.join(tempPath, 'package.json'), 'utf-8').catch(() => null);
78
+ const context = await intel.sync(pkgContent);
79
+
80
+ const files = await walkProject(tempPath);
81
+ spinner.stop();
82
+
83
+ console.log(chalk.cyan(`\n📑 Batch Audit: ${files.length} files identified.`));
84
+
85
+ const scanner = new VanguardScanner(programOptions.model, context);
86
+ let finalVerdict = 'SAFE';
87
+ let allThreats = [];
88
+ let cachedCount = 0;
89
+ let skippedCount = 0;
90
+
91
+ const scanSpinner = createSpinner(`🧠 Auditing ${files.length} files...`).start();
92
+
93
+ for (const file of files) {
94
+ const stats = await fs.stat(file);
95
+
96
+ // Enterprise Filtering
97
+ if (scanner.shouldSkip(file, stats)) {
98
+ skippedCount++;
99
+ continue;
100
+ }
101
+
102
+ const content = await fs.readFile(file, 'utf-8').catch(() => '');
103
+ const cached = CacheManager.check(file, content);
104
+
105
+ if (cached) {
106
+ cachedCount++;
107
+ continue;
108
+ }
109
+
110
+ const result = await scanner.scan(file, content);
111
+ if (result.verdict === 'BLOCK') {
112
+ finalVerdict = 'BLOCK';
113
+ allThreats.push(
114
+ ...result.threats.map((t) => ({ ...t, file: path.relative(tempPath, file) }))
115
+ );
116
+ } else {
117
+ CacheManager.save(file, content, 'SAFE');
118
+ }
119
+ }
120
+
121
+ scanSpinner.succeed(`Audit Complete: ${files.length} identified (${cachedCount} cached, ${skippedCount} skipped).`);
122
+
123
+ if (finalVerdict === 'BLOCK') {
124
+ showAlert({
125
+ verdict: 'BLOCK',
126
+ risk_score: 99,
127
+ threats: allThreats,
128
+ summary: 'Enterprise Deep Audit identified multiple high-risk vectors.',
129
+ });
130
+ await cleanupSandbox(tempPath);
131
+ } else {
132
+ console.log(chalk.green(`\n✅ Deep Audit Passed. Finalizing clone to ${targetPath}...`));
133
+ await finalizeClone(tempPath, targetPath);
134
+ }
135
+ }
@@ -0,0 +1,72 @@
1
+ import simpleGit from 'simple-git';
2
+ import path from 'path';
3
+ import fs from 'fs/promises';
4
+
5
+
6
+ export const git = simpleGit();
7
+
8
+ /**
9
+ * Fetches changes from upstream without merging.
10
+ */
11
+ export async function fetchUpstream() {
12
+ await git.fetch();
13
+ }
14
+
15
+ /**
16
+ * Gets the diff string for scanning.
17
+ */
18
+ export async function getUpstreamDiff() {
19
+ // Compare HEAD vs FETCH_HEAD (which is what was just fetched)
20
+ return await git.diff(['HEAD..FETCH_HEAD']);
21
+ }
22
+
23
+ /**
24
+ * Merges FETCH_HEAD into the current branch.
25
+ */
26
+ export async function mergeUpstream() {
27
+ await git.merge(['FETCH_HEAD']);
28
+ }
29
+
30
+ /**
31
+ * Clones into a temporary sandbox.
32
+ */
33
+ export async function createSandboxClone(url) {
34
+ const tempPath = path.join(process.cwd(), '.vanguard_temp');
35
+
36
+ // Cleanup if exists
37
+ await fs.rm(tempPath, { recursive: true, force: true }).catch(() => { });
38
+ await fs.mkdir(tempPath, { recursive: true });
39
+
40
+ const tempGit = simpleGit(tempPath);
41
+ await tempGit.clone(url, '.');
42
+ return tempPath;
43
+ }
44
+
45
+ /**
46
+ * Moves files from sandbox to target and cleans up.
47
+ * Improved for Windows EPERM stability.
48
+ */
49
+ export async function finalizeClone(tempPath, targetPath) {
50
+ const absTemp = path.resolve(tempPath);
51
+ const absTarget = path.resolve(targetPath);
52
+
53
+ try {
54
+ await fs.rename(absTemp, absTarget);
55
+ } catch (e) {
56
+ if (e.code === 'EPERM' || e.code === 'EXDEV' || e.code === 'EACCES') {
57
+ // Fallback: Force purge target if it exists, then copy
58
+ await fs.rm(absTarget, { recursive: true, force: true }).catch(() => { });
59
+ await fs.cp(absTemp, absTarget, { recursive: true, force: true });
60
+ await fs.rm(absTemp, { recursive: true, force: true });
61
+ } else {
62
+ throw e;
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Completely removes the sandbox.
69
+ */
70
+ export async function cleanupSandbox(tempPath) {
71
+ await fs.rm(tempPath, { recursive: true, force: true }).catch(() => { });
72
+ }
@@ -0,0 +1,89 @@
1
+ import fetch from 'node-fetch';
2
+ import chalk from 'chalk';
3
+ import Table from 'cli-table3';
4
+
5
+ const THREAT_DB_URL = 'https://raw.githubusercontent.com/bazobehram/vanguard-db/main/threats.json';
6
+
7
+ export class IntelligenceEngine {
8
+ constructor(verbose = false) {
9
+ this.verbose = verbose;
10
+ this.curatedRules = [];
11
+ this.vulnerabilities = [];
12
+ }
13
+
14
+ async sync(pkgJson = null) {
15
+ await Promise.allSettled([this.fetchCuratedRules(), this.queryOSV(pkgJson)]);
16
+
17
+ return this.generateContext();
18
+ }
19
+
20
+ async fetchCuratedRules() {
21
+ try {
22
+ if (this.verbose) console.log(chalk.dim(`📡 Fetching threat DB from: ${THREAT_DB_URL}`));
23
+ const res = await fetch(THREAT_DB_URL, { timeout: 5000 });
24
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
25
+
26
+ const data = await res.json();
27
+ this.curatedRules = data.ai_instructions || [];
28
+
29
+ console.log(
30
+ chalk.green(
31
+ ` ✔ Threat DB Sync: ${this.curatedRules.length} rules loaded (Version: ${data.version || 'v1'})`
32
+ )
33
+ );
34
+ } catch {
35
+ console.log(chalk.yellow(' ⚠️ Using Local Rules Only (Offline Fallback)'));
36
+ }
37
+ }
38
+
39
+ async queryOSV(pkgJson) {
40
+ if (!pkgJson) return;
41
+ try {
42
+ const pkg = JSON.parse(pkgJson);
43
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
44
+ const queries = Object.entries(deps).map(([name, version]) => ({
45
+ package: { name, ecosystem: 'npm' },
46
+ version: version.replace(/[\^~]/g, ''),
47
+ }));
48
+
49
+ if (queries.length === 0) return;
50
+
51
+ const res = await fetch('https://api.osv.dev/v1/querybatch', {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify({ queries: queries.slice(0, 50) }),
55
+ });
56
+
57
+ const data = await res.json();
58
+
59
+ const table = new Table({
60
+ head: [chalk.cyan('Dependency'), chalk.cyan('Status'), chalk.cyan('Vulnerabilities')],
61
+ colWidths: [20, 10, 20],
62
+ });
63
+
64
+ data.results.forEach((r, i) => {
65
+ const depName = queries[i].package.name;
66
+ table.push([depName, res.status, r.vulns ? chalk.red(r.vulns.length) : chalk.green('0')]);
67
+ if (r.vulns) this.vulnerabilities.push(...r.vulns);
68
+ });
69
+
70
+ console.log(chalk.bold('\n📦 OSV.dev Dependency Audit:'));
71
+ console.log(table.toString());
72
+ } catch (e) {
73
+ if (this.verbose) console.log(chalk.red(` ✘ OSV Audit Failed: ${e.message}`));
74
+ }
75
+ }
76
+
77
+ generateContext() {
78
+ let ctx = '\n### 🚨 LIVE THREAT INTELLIGENCE:\n';
79
+ if (this.curatedRules.length > 0) {
80
+ ctx += 'Curated Rules:\n' + this.curatedRules.map((r) => `- ${r}`).join('\n') + '\n';
81
+ }
82
+ if (this.vulnerabilities.length > 0) {
83
+ ctx +=
84
+ 'Known Vulnerabilities in dependencies:\n' +
85
+ this.vulnerabilities.map((v) => `- ${v.id}: ${v.summary}`).join('\n');
86
+ }
87
+ return ctx;
88
+ }
89
+ }
@@ -0,0 +1,155 @@
1
+ import { GoogleGenerativeAI } from '@google/generative-ai';
2
+ import config from '../utils/config.js';
3
+ import fetch from 'node-fetch';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import chalk from 'chalk';
8
+ import pLimit from 'p-limit';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const THREATS_PATH = path.join(__dirname, '../threats.json');
12
+
13
+ const limit = pLimit(3); // Enterprise concurrency limit: 3 files at a time
14
+
15
+ export class VanguardScanner {
16
+ constructor(modelOverride, intelligenceContext = '') {
17
+ this.provider = config.get('AI_PROVIDER');
18
+ this.modelOverride = modelOverride;
19
+ this.intelligenceContext = intelligenceContext;
20
+ }
21
+
22
+ /**
23
+ * Enterprise Filtering Logic: Skip binaries, images, and massive files.
24
+ */
25
+ shouldSkip(filePath, stats) {
26
+ const ext = path.extname(filePath).toLowerCase();
27
+ const binaryExtensions = [
28
+ '.exe',
29
+ '.dll',
30
+ '.so',
31
+ '.bin',
32
+ '.png',
33
+ '.jpg',
34
+ '.jpeg',
35
+ '.gif',
36
+ '.pdf',
37
+ '.zip',
38
+ '.gz',
39
+ ];
40
+
41
+ if (binaryExtensions.includes(ext)) return true;
42
+ if (stats.size > 100 * 1024) return true; // 100KB limit
43
+ if (filePath.includes('node_modules')) return true;
44
+
45
+ return false;
46
+ }
47
+
48
+ async getSystemInstruction() {
49
+ const threatsData = await fs.readFile(THREATS_PATH, 'utf-8');
50
+ const threats = JSON.parse(threatsData);
51
+
52
+ const categoriesListing = threats
53
+ .map((t, i) => `${i + 1}. **${t.name}:** ${t.description}`)
54
+ .join('\n');
55
+
56
+ return `
57
+ You are a Senior Security Architect & Auditor. YOUR MISSION: Prevent Supply Chain Attacks.
58
+ Analyze the provided code/diff with extreme scrutiny.
59
+
60
+ YOU MUST BLOCK IF YOU DETECT ANY OF THESE CATEGORIES:
61
+ ${categoriesListing}
62
+
63
+ ${this.intelligenceContext}
64
+
65
+ TASK: Combine the code analysis with the latest intelligence and vulnerability data above.
66
+ If OSV reports a critical vulnerability or the remote rules flag a new pattern, YOU MUST VERDICT: BLOCK.
67
+
68
+ JSON OUTPUT RULES:
69
+ - risk_score: 0-100 (Be aggressive).
70
+ - verdict: "BLOCK" (if risk found) or "SAFE".
71
+ - threats: Array of objects: { "file": "filename", "line": number, "threat": "category", "reason": "why" }
72
+ - summary: Clear high-level explanation for a developer.
73
+
74
+ RESPONSE FORMAT (JSON ONLY):
75
+ { "risk_score": 95, "verdict": "BLOCK", "threats": [{ "file": "lib/utils.js", "line": 42, "threat": "Secret Exfiltration", "reason": "Accesses .env and sends to remote server." }], "summary": "Critical vulnerability found." }
76
+ `;
77
+ }
78
+
79
+ async scan(filePath, content) {
80
+ return limit(() => this.scanWithRetry(filePath, content));
81
+ }
82
+
83
+ async scanWithRetry(filePath, content, retries = 3) {
84
+ for (let i = 0; i < retries; i++) {
85
+ try {
86
+ if (this.provider === 'gemini') {
87
+ return await this.scanWithGemini(content);
88
+ } else {
89
+ return await this.scanWithOllama(content);
90
+ }
91
+ } catch (error) {
92
+ const isRateLimit =
93
+ error.message.includes('429') || error.message.includes('Too Many Requests');
94
+ if (isRateLimit && i < retries - 1) {
95
+ const delay = Math.pow(2, i) * 1000;
96
+ if (config.get('VERBOSE'))
97
+ console.log(chalk.yellow(` ⚠ Rate limited. Retrying in ${delay}ms...`));
98
+ await new Promise((res) => setTimeout(res, delay));
99
+ continue;
100
+ }
101
+ throw error;
102
+ }
103
+ }
104
+ }
105
+
106
+ async scanWithGemini(content) {
107
+ const apiKey = config.get('GEMINI_KEY');
108
+ if (!apiKey) throw new Error('Gemini API Key missing. Run "vanguard config"');
109
+
110
+ const genAI = new GoogleGenerativeAI(apiKey);
111
+ const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
112
+ const instruction = await this.getSystemInstruction();
113
+
114
+ const result = await model.generateContent({
115
+ contents: [
116
+ { role: 'user', parts: [{ text: `${instruction}\n\nFILE CONTENT:\n${content}` }] },
117
+ ],
118
+ });
119
+
120
+ const response = await result.response;
121
+ return this.parseResponse(response.text());
122
+ }
123
+
124
+ async scanWithOllama(content) {
125
+ const model = this.modelOverride || config.get('OLLAMA_MODEL');
126
+ const url = `${config.get('OLLAMA_URL')}/api/generate`;
127
+ const instruction = await this.getSystemInstruction();
128
+
129
+ const response = await fetch(url, {
130
+ method: 'POST',
131
+ headers: { 'Content-Type': 'application/json' },
132
+ body: JSON.stringify({
133
+ model: model,
134
+ prompt: `${instruction}\n\nFILE CONTENT:\n${content}`,
135
+ stream: false,
136
+ format: 'json',
137
+ }),
138
+ });
139
+
140
+ if (!response.ok) throw new Error(`Ollama error: ${response.statusText}`);
141
+ const data = await response.json();
142
+ return this.parseResponse(data.response);
143
+ }
144
+
145
+ parseResponse(text) {
146
+ try {
147
+ const start = text.indexOf('{');
148
+ const end = text.lastIndexOf('}');
149
+ if (start === -1 || end === -1) throw new Error('No JSON object found');
150
+ return JSON.parse(text.substring(start, end + 1));
151
+ } catch (e) {
152
+ throw new Error(`Invalid AI JSON format: ${e.message}`);
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,57 @@
1
+ [
2
+ {
3
+ "id": "CREDENTIAL_HYGIENE",
4
+ "name": "Secret Exfiltration",
5
+ "description": "Detection of hardcoded secrets (.env, API keys, SSH keys) being accessed and transmitted to external endpoints (Credential Hygiene Abuse)."
6
+ },
7
+ {
8
+ "id": "FINANCIAL_FRAUD",
9
+ "name": "Web3/Wallet Draining",
10
+ "description": "Unauthorized access to cryptocurrency wallets (window.ethereum, private keys) or triggering of fraudulent transactions."
11
+ },
12
+ {
13
+ "id": "DATA_HARVESTING",
14
+ "name": "Spyware/Harvesting",
15
+ "description": "Unnecessary collection of system metadata, user information, browser history, or network topology (Information Gathering)."
16
+ },
17
+ {
18
+ "id": "PIPELINE_POISONING",
19
+ "name": "Malicious Installers",
20
+ "description": "Abuse of package lifecycle scripts (postinstall, preinstall) to download and execute arbitrary remote binaries (PPE)."
21
+ },
22
+ {
23
+ "id": "DETECTION_EVASION",
24
+ "name": "Obfuscation & Evasion",
25
+ "description": "Heavy use of eval(), nested encoding (Base64/Hex), or polymorphic code designed to bypass static analysis and firewalls."
26
+ },
27
+ {
28
+ "id": "REVERSE_SHELL",
29
+ "name": "Reverse Shells/Backdoors",
30
+ "description": "Opening outbound persistent connections (sockets, SSH) or spawning interactive shells to remote Command & Control (C2) servers."
31
+ },
32
+ {
33
+ "id": "DEPENDENCY_ABUSE",
34
+ "name": "Dependency Hijacking/Confusion",
35
+ "description": "Exploiting package manager resolution to inject malicious versions of private or public dependencies (Dependency Chain Abuse)."
36
+ },
37
+ {
38
+ "id": "SYSTEM_PERSISTENCE",
39
+ "name": "Persistence Mechanisms",
40
+ "description": "Attempts to establish long-term access by modifying startup scripts, Cron jobs, systemd units, or OS registries."
41
+ },
42
+ {
43
+ "id": "GATED_EXECUTION",
44
+ "name": "Logic/Time Bombs",
45
+ "description": "Gating malicious activity behind specific dates, system conditions, or external triggers to evade initial analysis."
46
+ },
47
+ {
48
+ "id": "IMPACT_DESTRUCTION",
49
+ "name": "Ransomware/Wiping",
50
+ "description": "Unauthorized mass modification, encryption (Ransomware), or deletion (Wiping) of localized data and configurations."
51
+ },
52
+ {
53
+ "id": "QA_MOCK_THREAT",
54
+ "name": "QA Mock Threat (PROJECT_OMEGA)",
55
+ "description": "A specialized signature for manual QA stress testing. Verifies that the audit engine can target and block specific strings used in behavioral simulations."
56
+ }
57
+ ]
@@ -0,0 +1,42 @@
1
+ import crypto from 'crypto';
2
+ import Conf from 'conf';
3
+
4
+ const cacheStore = new Conf({ projectName: 'vanguard-enterprise-cache' });
5
+
6
+ export class CacheManager {
7
+ /**
8
+ * Calculate SHA-256 hash of file content.
9
+ */
10
+ static getHash(content) {
11
+ return crypto.createHash('sha256').update(content).digest('hex');
12
+ }
13
+
14
+ /**
15
+ * Check if file is already verified as SAFE.
16
+ */
17
+ static check(filePath, content) {
18
+ const hash = this.getHash(content);
19
+ const cached = cacheStore.get(hash);
20
+
21
+ if (cached && cached.verdict === 'SAFE') {
22
+ return cached;
23
+ }
24
+ return null;
25
+ }
26
+
27
+ /**
28
+ * Save verification verdict for a file hash.
29
+ */
30
+ static save(filePath, content, verdict) {
31
+ const hash = this.getHash(content);
32
+ cacheStore.set(hash, {
33
+ path: filePath,
34
+ verdict,
35
+ timestamp: Date.now(),
36
+ });
37
+ }
38
+
39
+ static clear() {
40
+ cacheStore.clear();
41
+ }
42
+ }
@@ -0,0 +1,25 @@
1
+ import Conf from 'conf';
2
+
3
+ const schema = {
4
+ AI_PROVIDER: {
5
+ type: 'string',
6
+ default: 'gemini',
7
+ enum: ['gemini', 'ollama'],
8
+ },
9
+ GEMINI_KEY: {
10
+ type: 'string',
11
+ default: '',
12
+ },
13
+ OLLAMA_MODEL: {
14
+ type: 'string',
15
+ default: 'qwen2.5:7b',
16
+ },
17
+ OLLAMA_URL: {
18
+ type: 'string',
19
+ default: 'http://localhost:11434',
20
+ },
21
+ };
22
+
23
+ const config = new Conf({ projectName: 'vanguard', schema });
24
+
25
+ export default config;
@@ -0,0 +1,13 @@
1
+ import chalk from 'chalk';
2
+
3
+ const VERBOSE = process.env.VANGUARD_VERBOSE === 'true';
4
+
5
+ export const logger = {
6
+ info: (msg) => console.log(chalk.blue(`ℹ ${msg}`)),
7
+ success: (msg) => console.log(chalk.green(`✔ ${msg}`)),
8
+ warn: (msg) => console.log(chalk.yellow(`⚠ ${msg}`)),
9
+ error: (msg) => console.log(chalk.red(`✘ ${msg}`)),
10
+ debug: (msg) => {
11
+ if (VERBOSE) console.log(chalk.dim(`DEBUG: ${msg}`));
12
+ },
13
+ };
@@ -0,0 +1,9 @@
1
+ import ora from 'ora';
2
+
3
+ export function createSpinner(text) {
4
+ return ora({
5
+ text: text,
6
+ color: 'yellow',
7
+ spinner: 'dots',
8
+ });
9
+ }
@@ -0,0 +1,68 @@
1
+ import figlet from 'figlet';
2
+ import chalk from 'chalk';
3
+ import boxen from 'boxen';
4
+ import ora from 'ora';
5
+
6
+ export function showBanner() {
7
+ console.log(chalk.cyan(figlet.textSync('VANGUARD', { horizontalLayout: 'full' })));
8
+ console.log(chalk.dim('🛡️ Software Supply Chain Firewall | AI-Powered Security 🛡️\n'));
9
+ }
10
+
11
+ export function showAlert(analysis) {
12
+ const color = analysis.verdict === 'BLOCK' ? 'red' : 'green';
13
+ const title = analysis.verdict === 'BLOCK' ? '⚠️ HIGH RISK DETECTED' : '✅ SAFE';
14
+
15
+ const threats = analysis.threats || [];
16
+ const riskScore = analysis.risk_score ?? analysis.riskScore ?? 0;
17
+ const verdict = analysis.verdict || 'UNKNOWN';
18
+ const summary = analysis.summary || 'No summary provided.';
19
+
20
+ const content = [
21
+ chalk.bold(`Verdict: ${verdict}`),
22
+ chalk.bold(`Risk Score: ${riskScore}/100`),
23
+ '',
24
+ chalk.bold('🚨 Detailed Threat Audit:'),
25
+ ...(threats.length > 0
26
+ ? threats.map((t) => {
27
+ const loc = t.file ? chalk.yellow(`[${t.file}${t.line ? `:${t.line}` : ''}] `) : '';
28
+ const tag = t.threat ? chalk.red(`(${t.threat})`) : '';
29
+ return `• ${loc}${tag}\n ${chalk.dim(t.reason || t)}`;
30
+ })
31
+ : [chalk.dim('• No targeted threats identified.')]),
32
+ '',
33
+ chalk.bold('📝 Auditor Summary:'),
34
+ summary,
35
+ ].join('\n');
36
+
37
+ console.log(
38
+ boxen(content, {
39
+ padding: 1,
40
+ margin: 1,
41
+ borderStyle: 'double',
42
+ borderColor: color,
43
+ title: title,
44
+ titleAlignment: 'center',
45
+ })
46
+ );
47
+ }
48
+
49
+
50
+
51
+ export function showFooter() {
52
+ console.log(
53
+ chalk.dim('\n----------------------------------------------------------------------')
54
+ );
55
+ console.log(
56
+ chalk.yellow('⭐ Star on GitHub: ') + chalk.underline('https://github.com/bazob/vanguard')
57
+ );
58
+ console.log(
59
+ chalk.green('🤝 Contribute: ') + chalk.underline('https://github.com/bazob/vanguard/pulls')
60
+ );
61
+ console.log(
62
+ chalk.cyan('☕ Support the project: ') +
63
+ chalk.underline('https://www.buymeacoffee.com/bazobehram')
64
+ );
65
+ console.log(
66
+ chalk.dim('----------------------------------------------------------------------\n')
67
+ );
68
+ }
@@ -0,0 +1,39 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', 'venv', '.env'];
5
+ const IGNORE_EXTENSIONS = [
6
+ '.png',
7
+ '.jpg',
8
+ '.jpeg',
9
+ '.gif',
10
+ '.svg',
11
+ '.mp4',
12
+ '.zip',
13
+ '.exe',
14
+ '.dll',
15
+ '.lock',
16
+ ];
17
+ const MAX_FILE_SIZE = 100 * 1024; // 100KB
18
+
19
+ export async function walkProject(dir) {
20
+ let results = [];
21
+ const list = await fs.readdir(dir);
22
+
23
+ for (const file of list) {
24
+ const fullPath = path.join(dir, file);
25
+ const stat = await fs.stat(fullPath);
26
+
27
+ if (stat.isDirectory()) {
28
+ if (IGNORE_DIRS.includes(file)) continue;
29
+ results = results.concat(await walkProject(fullPath));
30
+ } else {
31
+ const ext = path.extname(file).toLowerCase();
32
+ if (IGNORE_EXTENSIONS.includes(ext)) continue;
33
+ if (stat.size > MAX_FILE_SIZE) continue;
34
+ results.push(fullPath);
35
+ }
36
+ }
37
+
38
+ return results;
39
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "vanguard-cli",
3
+ "version": "3.0.0",
4
+ "description": "AI-Powered Supply Chain Firewall for Git",
5
+ "type": "module",
6
+ "bin": {
7
+ "vanguard": "./bin/vanguard.js"
8
+ },
9
+ "scripts": {
10
+ "test": "vitest run",
11
+ "format": "prettier --write .",
12
+ "lint": "eslint .",
13
+ "release": "release-it",
14
+ "release:dry": "release-it --dry-run"
15
+ },
16
+ "keywords": [],
17
+ "author": "Behram Bazo",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/bazob/vanguard.git"
22
+ },
23
+ "dependencies": {
24
+ "@google/generative-ai": "^0.21.0",
25
+ "boxen": "^8.0.1",
26
+ "chalk": "^4.1.2",
27
+ "cli-table3": "^0.6.5",
28
+ "commander": "^13.1.0",
29
+ "conf": "^12.0.0",
30
+ "crypto-js": "^4.2.0",
31
+ "figlet": "^1.8.0",
32
+ "inquirer": "^8.2.4",
33
+ "node-fetch": "^2.7.0",
34
+ "ora": "^8.1.1",
35
+ "p-limit": "^7.2.0",
36
+ "simple-git": "^3.27.0"
37
+ },
38
+ "devDependencies": {
39
+ "@eslint/js": "^9.39.2",
40
+ "@release-it/conventional-changelog": "^10.0.4",
41
+ "eslint": "^9.39.2",
42
+ "eslint-config-prettier": "^10.1.8",
43
+ "eslint-plugin-node": "^11.1.0",
44
+ "globals": "^17.1.0",
45
+ "nock": "^14.0.10",
46
+ "prettier": "^3.8.1",
47
+ "release-it": "^19.2.4",
48
+ "vitest": "^4.0.18"
49
+ }
50
+ }