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.
- package/.release-it.json +27 -0
- package/CODE_OF_CONDUCT.md +44 -0
- package/CONTRIBUTING.md +48 -0
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/SECURITY.md +34 -0
- package/bin/vanguard.js +67 -0
- package/lib/commands/config.js +81 -0
- package/lib/commands/scan.js +135 -0
- package/lib/services/git.js +72 -0
- package/lib/services/intelligence.js +89 -0
- package/lib/services/scanner.js +155 -0
- package/lib/threats.json +57 -0
- package/lib/utils/cache.js +42 -0
- package/lib/utils/config.js +25 -0
- package/lib/utils/logger.js +13 -0
- package/lib/utils/spinner.js +9 -0
- package/lib/utils/ui.js +68 -0
- package/lib/utils/walker.js +39 -0
- package/package.json +50 -0
package/.release-it.json
ADDED
|
@@ -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
|
package/CONTRIBUTING.md
ADDED
|
@@ -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
|
+
[](https://www.npmjs.com/package/vanguard-cli)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://deepmind.google/technologies/gemini/)
|
|
9
|
+
[](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! 🦾🛡️
|
package/bin/vanguard.js
ADDED
|
@@ -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
|
+
}
|
package/lib/threats.json
ADDED
|
@@ -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
|
+
};
|
package/lib/utils/ui.js
ADDED
|
@@ -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
|
+
}
|