dep-oracle 1.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/LICENSE +21 -0
- package/README.md +208 -0
- package/action/action.yml +21 -0
- package/action/index.ts +302 -0
- package/dist/chunk-RQV3VHZS.js +3512 -0
- package/dist/chunk-RQV3VHZS.js.map +1 -0
- package/dist/cli/index.js +3989 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.js +325 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.js +472 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ertugrul Akben
|
|
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,208 @@
|
|
|
1
|
+
# dep-oracle 🔮
|
|
2
|
+
|
|
3
|
+
> Your dependencies have dependencies. Who's watching them?
|
|
4
|
+
|
|
5
|
+
**dep-oracle** is a predictive dependency security engine that calculates **Trust Scores** (0-100) for every package in your dependency tree. It detects zombie dependencies, measures blast radius, catches typosquatting attempts, and predicts future risks — before they become vulnerabilities.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/dep-oracle)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Why?
|
|
11
|
+
|
|
12
|
+
- **62% of breaches** in 2025 came from supply chain attacks
|
|
13
|
+
- The average project has **683 transitive dependencies**
|
|
14
|
+
- `npm audit` only catches **known** CVEs — dep-oracle **predicts** future risks
|
|
15
|
+
- You audit your code. But do you audit your **trust**?
|
|
16
|
+
|
|
17
|
+
**Claude Code Security** scans YOUR code. **dep-oracle** scans everything your code **depends on**.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Zero install — just run it
|
|
23
|
+
npx dep-oracle
|
|
24
|
+
|
|
25
|
+
# Or install globally
|
|
26
|
+
npm install -g dep-oracle
|
|
27
|
+
dep-oracle scan
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## What It Does
|
|
31
|
+
|
|
32
|
+
| Feature | Description |
|
|
33
|
+
|---------|-------------|
|
|
34
|
+
| **Trust Score** | 0-100 weighted score per package (maintainer health, security, activity, popularity, funding, license) |
|
|
35
|
+
| **Zombie Detection** | Finds unmaintained but critical packages (no commits in 12+ months) |
|
|
36
|
+
| **Blast Radius** | Shows how many files are affected if a dependency is compromised |
|
|
37
|
+
| **Typosquat Detection** | Catches suspicious package names similar to popular packages |
|
|
38
|
+
| **Trend Prediction** | 3-month risk projection based on download/commit trends |
|
|
39
|
+
| **Migration Advisor** | Suggests safer alternatives for risky dependencies |
|
|
40
|
+
| **Offline Mode** | Works from cache without internet (`--offline`) |
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Scan current project
|
|
46
|
+
dep-oracle scan
|
|
47
|
+
|
|
48
|
+
# Scan with specific output
|
|
49
|
+
dep-oracle scan --format json
|
|
50
|
+
dep-oracle scan --format html
|
|
51
|
+
dep-oracle scan --format sarif
|
|
52
|
+
|
|
53
|
+
# Check a single package
|
|
54
|
+
dep-oracle check lodash
|
|
55
|
+
|
|
56
|
+
# Offline mode (uses cached data)
|
|
57
|
+
dep-oracle scan --offline
|
|
58
|
+
|
|
59
|
+
# Set minimum score threshold (CI/CD)
|
|
60
|
+
dep-oracle scan --threshold 60
|
|
61
|
+
|
|
62
|
+
# Ignore specific packages
|
|
63
|
+
dep-oracle scan --ignore deprecated-but-needed,legacy-pkg
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Output Example
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
🔮 dep-oracle v1.0.0
|
|
70
|
+
Scanning package.json...
|
|
71
|
+
Found 47 direct dependencies, 683 transitive
|
|
72
|
+
Collecting data... [=============================] 100% (2.3s)
|
|
73
|
+
|
|
74
|
+
DEPENDENCY TRUST REPORT
|
|
75
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
76
|
+
|
|
77
|
+
🚫 CRITICAL (score < 50)
|
|
78
|
+
|
|
79
|
+
■ event-stream@3.3.6 Score: 12 💀 ZOMBIE
|
|
80
|
+
Last commit: 2018 | 0 maintainers active
|
|
81
|
+
Blast radius: 14 files | Alternative: highland
|
|
82
|
+
|
|
83
|
+
⚠ WARNING (score 50-79)
|
|
84
|
+
|
|
85
|
+
■ moment@2.29.4 Score: 58 💀 ZOMBIE
|
|
86
|
+
Maintenance mode | No new features
|
|
87
|
+
Blast radius: 23 files | Alternative: dayjs
|
|
88
|
+
|
|
89
|
+
✅ SAFE (score 80+): 679 packages
|
|
90
|
+
|
|
91
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
92
|
+
SUMMARY
|
|
93
|
+
Overall Trust Score: 74/100
|
|
94
|
+
Critical: 2 | Warning: 3 | Safe: 679
|
|
95
|
+
Zombies: 2 | Deprecated: 1
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Trust Score Algorithm
|
|
99
|
+
|
|
100
|
+
Each package is scored 0-100 based on six weighted metrics:
|
|
101
|
+
|
|
102
|
+
| Metric | Weight | What It Measures |
|
|
103
|
+
|--------|--------|------------------|
|
|
104
|
+
| Security History | 25% | CVE count, average patch time, vulnerability density |
|
|
105
|
+
| Maintainer Health | 25% | Active maintainers (bus factor), issue response time, PR merge speed |
|
|
106
|
+
| Activity | 20% | Commit frequency trend, release cadence, last publish date |
|
|
107
|
+
| Popularity | 15% | Weekly downloads, dependent count, GitHub stars |
|
|
108
|
+
| Funding | 10% | GitHub Sponsors, OpenCollective, corporate backing |
|
|
109
|
+
| License | 5% | MIT/Apache = safe, GPL = risk, Unknown = red flag |
|
|
110
|
+
|
|
111
|
+
**Score Ranges:** 80-100 ✅ Safe | 50-79 ⚠️ Warning | 0-49 🚫 Critical
|
|
112
|
+
|
|
113
|
+
## Claude Code Integration (MCP)
|
|
114
|
+
|
|
115
|
+
dep-oracle works as an MCP server for Claude Code:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
// .claude/settings.json
|
|
119
|
+
{
|
|
120
|
+
"mcpServers": {
|
|
121
|
+
"dep-oracle": {
|
|
122
|
+
"command": "npx",
|
|
123
|
+
"args": ["dep-oracle", "mcp"]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Then in Claude Code, just ask:
|
|
130
|
+
- "What's the riskiest dependency in this project?"
|
|
131
|
+
- "Is lodash safe to use?"
|
|
132
|
+
- "Show me zombie dependencies"
|
|
133
|
+
|
|
134
|
+
## GitHub Action
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
name: Dependency Trust Check
|
|
138
|
+
on: [pull_request]
|
|
139
|
+
|
|
140
|
+
jobs:
|
|
141
|
+
dep-oracle:
|
|
142
|
+
runs-on: ubuntu-latest
|
|
143
|
+
steps:
|
|
144
|
+
- uses: actions/checkout@v4
|
|
145
|
+
- uses: ertugrulakben/dep-oracle-action@v1
|
|
146
|
+
with:
|
|
147
|
+
threshold: 60
|
|
148
|
+
format: sarif
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Configuration
|
|
152
|
+
|
|
153
|
+
Create `.dep-oraclerc.json` in your project root:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"threshold": 60,
|
|
158
|
+
"ignore": ["known-risky-but-needed"],
|
|
159
|
+
"format": "terminal",
|
|
160
|
+
"offline": false,
|
|
161
|
+
"githubToken": "$GITHUB_TOKEN",
|
|
162
|
+
"cacheTtl": 86400
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Or add to `package.json`:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"dep-oracle": {
|
|
171
|
+
"threshold": 60,
|
|
172
|
+
"ignore": []
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Supported Package Managers
|
|
178
|
+
|
|
179
|
+
| Manager | Manifest | Lock File | Status |
|
|
180
|
+
|---------|----------|-----------|--------|
|
|
181
|
+
| npm | `package.json` | `package-lock.json` | ✅ Supported |
|
|
182
|
+
| yarn | `package.json` | `yarn.lock` | ✅ Supported |
|
|
183
|
+
| pnpm | `package.json` | `pnpm-lock.yaml` | ✅ Supported |
|
|
184
|
+
| pip | `requirements.txt` | `Pipfile.lock` | ✅ Supported |
|
|
185
|
+
| poetry | `pyproject.toml` | `poetry.lock` | ✅ Supported |
|
|
186
|
+
|
|
187
|
+
## Comparison
|
|
188
|
+
|
|
189
|
+
| Feature | npm audit | Dependabot | Socket.dev | Snyk | **dep-oracle** |
|
|
190
|
+
|---------|-----------|------------|------------|------|----------------|
|
|
191
|
+
| Known CVE scan | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
192
|
+
| Predictive risk | ❌ | ❌ | Partial | ❌ | **✅** |
|
|
193
|
+
| Trust Score (0-100) | ❌ | ❌ | ❌ | ❌ | **✅** |
|
|
194
|
+
| Zombie detection | ❌ | ❌ | ❌ | ❌ | **✅** |
|
|
195
|
+
| Blast radius | ❌ | ❌ | ❌ | ❌ | **✅** |
|
|
196
|
+
| Typosquat detection | ❌ | ❌ | ✅ | ❌ | **✅** |
|
|
197
|
+
| Trend prediction | ❌ | ❌ | ❌ | ❌ | **✅** |
|
|
198
|
+
| MCP integration | ❌ | ❌ | ✅ | ✅ | **✅** |
|
|
199
|
+
| Zero install (npx) | ✅ | ❌ | ❌ | ❌ | **✅** |
|
|
200
|
+
| Free | ✅ | ✅ | Freemium | Freemium | **✅** |
|
|
201
|
+
|
|
202
|
+
## Contributing
|
|
203
|
+
|
|
204
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and how to add new collectors/parsers.
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
[MIT](LICENSE) — Ertugrul Akben
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: 'dep-oracle'
|
|
2
|
+
description: 'Predictive dependency security scan — Trust Scores, zombie detection, blast radius analysis'
|
|
3
|
+
branding:
|
|
4
|
+
icon: 'shield'
|
|
5
|
+
color: 'blue'
|
|
6
|
+
inputs:
|
|
7
|
+
min-score:
|
|
8
|
+
description: 'Minimum trust score threshold (0-100). Packages below this score cause the action to fail.'
|
|
9
|
+
required: false
|
|
10
|
+
default: '50'
|
|
11
|
+
format:
|
|
12
|
+
description: 'Output format (table, json, sarif)'
|
|
13
|
+
required: false
|
|
14
|
+
default: 'table'
|
|
15
|
+
github-token:
|
|
16
|
+
description: 'GitHub token for API rate limits'
|
|
17
|
+
required: false
|
|
18
|
+
default: '${{ github.token }}'
|
|
19
|
+
runs:
|
|
20
|
+
using: 'node20'
|
|
21
|
+
main: 'index.js'
|
package/action/index.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Action entry point for dep-oracle.
|
|
3
|
+
*
|
|
4
|
+
* Reads inputs from environment variables (GitHub Actions convention),
|
|
5
|
+
* runs a full dependency scan using the core engine, and outputs results
|
|
6
|
+
* in the requested format. Exits with code 1 if any package falls below
|
|
7
|
+
* the minimum trust score threshold.
|
|
8
|
+
*
|
|
9
|
+
* Environment variables set by GitHub Actions:
|
|
10
|
+
* INPUT_MIN_SCORE - Minimum trust score threshold (default: 50)
|
|
11
|
+
* INPUT_FORMAT - Output format: table, json, sarif (default: table)
|
|
12
|
+
* INPUT_GITHUB_TOKEN - GitHub token for higher API rate limits
|
|
13
|
+
* GITHUB_WORKSPACE - Path to the checked-out repository
|
|
14
|
+
* GITHUB_OUTPUT - Path to the file for setting action outputs
|
|
15
|
+
* GITHUB_STEP_SUMMARY - Path to the file for job summary markdown
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { resolve } from 'node:path';
|
|
19
|
+
import { appendFile } from 'node:fs/promises';
|
|
20
|
+
|
|
21
|
+
import { CacheManager } from '../src/cache/store.js';
|
|
22
|
+
import { NpmParser } from '../src/parsers/npm.js';
|
|
23
|
+
import { PythonParser } from '../src/parsers/python.js';
|
|
24
|
+
import type { DependencyTree, TrustReport, ScanResult } from '../src/parsers/schema.js';
|
|
25
|
+
import { CollectorOrchestrator } from '../src/collectors/orchestrator.js';
|
|
26
|
+
import { TrustScoreEngine } from '../src/analyzers/trust-score.js';
|
|
27
|
+
import { ZombieDetector } from '../src/analyzers/zombie-detector.js';
|
|
28
|
+
import { MigrationAdvisor } from '../src/analyzers/migration-advisor.js';
|
|
29
|
+
import { TyposquatDetector } from '../src/analyzers/typosquat.js';
|
|
30
|
+
import { buildImportGraph, getBlastRadius } from '../src/utils/graph.js';
|
|
31
|
+
import { JsonReporter } from '../src/reporters/json.js';
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Configuration from GitHub Action inputs
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
process.env.GITHUB_TOKEN =
|
|
38
|
+
process.env.INPUT_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN ?? '';
|
|
39
|
+
|
|
40
|
+
const minScore = parseInt(process.env.INPUT_MIN_SCORE ?? '50', 10);
|
|
41
|
+
const format = (process.env.INPUT_FORMAT ?? 'table').toLowerCase();
|
|
42
|
+
const workspaceDir = resolve(process.env.GITHUB_WORKSPACE ?? process.cwd());
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Core engine instances
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const cache = new CacheManager();
|
|
49
|
+
const orchestrator = new CollectorOrchestrator(cache, {
|
|
50
|
+
githubToken: process.env.GITHUB_TOKEN,
|
|
51
|
+
});
|
|
52
|
+
const trustEngine = new TrustScoreEngine();
|
|
53
|
+
const zombieDetector = new ZombieDetector();
|
|
54
|
+
const migrationAdvisor = new MigrationAdvisor();
|
|
55
|
+
const typosquatDetector = new TyposquatDetector();
|
|
56
|
+
const jsonReporter = new JsonReporter();
|
|
57
|
+
const parsers = [new NpmParser(), new PythonParser()];
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Helpers
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
async function parseProject(dir: string): Promise<DependencyTree | null> {
|
|
64
|
+
for (const parser of parsers) {
|
|
65
|
+
if (await parser.detect(dir)) {
|
|
66
|
+
return parser.parse(dir);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function buildTrustReport(
|
|
73
|
+
packageName: string,
|
|
74
|
+
version: string,
|
|
75
|
+
blastRadius: number = 0,
|
|
76
|
+
): Promise<TrustReport> {
|
|
77
|
+
const results = await orchestrator.collectAll(packageName, version);
|
|
78
|
+
const trustResult = trustEngine.calculate(results);
|
|
79
|
+
const zombie = zombieDetector.detect(
|
|
80
|
+
results.registry.data,
|
|
81
|
+
results.github.data,
|
|
82
|
+
);
|
|
83
|
+
const typosquat = typosquatDetector.check(packageName);
|
|
84
|
+
const alternatives = migrationAdvisor.suggest(
|
|
85
|
+
packageName,
|
|
86
|
+
zombie.isZombie ? 'zombie dependency' : 'low trust score',
|
|
87
|
+
);
|
|
88
|
+
const trend = results.popularity.data?.trend ?? 'stable';
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
package: packageName,
|
|
92
|
+
version,
|
|
93
|
+
trustScore: trustResult.trustScore,
|
|
94
|
+
metrics: trustResult.metrics,
|
|
95
|
+
isZombie: zombie.isZombie,
|
|
96
|
+
blastRadius,
|
|
97
|
+
alternatives: alternatives.map((a) => a.alternative),
|
|
98
|
+
trend,
|
|
99
|
+
typosquatRisk: typosquat.isRisky ? 1.0 : 0.0,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Set a GitHub Actions output variable. */
|
|
104
|
+
async function setOutput(name: string, value: string): Promise<void> {
|
|
105
|
+
const outputFile = process.env.GITHUB_OUTPUT;
|
|
106
|
+
if (outputFile) {
|
|
107
|
+
await appendFile(outputFile, `${name}=${value}\n`, 'utf-8');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Append content to the GitHub Actions job summary. */
|
|
112
|
+
async function addSummary(markdown: string): Promise<void> {
|
|
113
|
+
const summaryFile = process.env.GITHUB_STEP_SUMMARY;
|
|
114
|
+
if (summaryFile) {
|
|
115
|
+
await appendFile(summaryFile, markdown + '\n', 'utf-8');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function formatTable(reports: TrustReport[], overallScore: number): string {
|
|
120
|
+
const lines: string[] = [];
|
|
121
|
+
|
|
122
|
+
lines.push('dep-oracle -- Dependency Trust Report');
|
|
123
|
+
lines.push('='.repeat(60));
|
|
124
|
+
lines.push(`Overall Trust Score: ${overallScore}/100`);
|
|
125
|
+
lines.push('');
|
|
126
|
+
lines.push(
|
|
127
|
+
'Package'.padEnd(30) +
|
|
128
|
+
'Score'.padEnd(8) +
|
|
129
|
+
'Zombie'.padEnd(8) +
|
|
130
|
+
'Blast'.padEnd(8) +
|
|
131
|
+
'Trend',
|
|
132
|
+
);
|
|
133
|
+
lines.push('-'.repeat(62));
|
|
134
|
+
|
|
135
|
+
const sorted = [...reports].sort((a, b) => a.trustScore - b.trustScore);
|
|
136
|
+
|
|
137
|
+
for (const r of sorted) {
|
|
138
|
+
lines.push(
|
|
139
|
+
r.package.padEnd(30) +
|
|
140
|
+
String(r.trustScore).padEnd(8) +
|
|
141
|
+
(r.isZombie ? 'Yes' : 'No').padEnd(8) +
|
|
142
|
+
String(r.blastRadius).padEnd(8) +
|
|
143
|
+
r.trend,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return lines.join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatMarkdownSummary(
|
|
151
|
+
reports: TrustReport[],
|
|
152
|
+
overallScore: number,
|
|
153
|
+
): string {
|
|
154
|
+
const lines: string[] = [];
|
|
155
|
+
|
|
156
|
+
const statusEmoji = overallScore >= 80 ? 'white_check_mark' : overallScore >= 50 ? 'warning' : 'x';
|
|
157
|
+
lines.push(`## dep-oracle Dependency Report :${statusEmoji}:`);
|
|
158
|
+
lines.push('');
|
|
159
|
+
lines.push(`**Overall Trust Score:** ${overallScore}/100`);
|
|
160
|
+
lines.push('');
|
|
161
|
+
|
|
162
|
+
const zombieCount = reports.filter((r) => r.isZombie).length;
|
|
163
|
+
const criticalCount = reports.filter((r) => r.trustScore < 50).length;
|
|
164
|
+
const typosquatCount = reports.filter((r) => r.typosquatRisk > 0.5).length;
|
|
165
|
+
|
|
166
|
+
lines.push(`| Metric | Count |`);
|
|
167
|
+
lines.push(`|--------|-------|`);
|
|
168
|
+
lines.push(`| Total scanned | ${reports.length} |`);
|
|
169
|
+
lines.push(`| Critical (score < 50) | ${criticalCount} |`);
|
|
170
|
+
lines.push(`| Zombie dependencies | ${zombieCount} |`);
|
|
171
|
+
lines.push(`| Typosquat risks | ${typosquatCount} |`);
|
|
172
|
+
lines.push('');
|
|
173
|
+
|
|
174
|
+
// Package table
|
|
175
|
+
lines.push('| Package | Score | Zombie | Blast Radius | Trend |');
|
|
176
|
+
lines.push('|---------|-------|--------|--------------|-------|');
|
|
177
|
+
|
|
178
|
+
const sorted = [...reports].sort((a, b) => a.trustScore - b.trustScore);
|
|
179
|
+
for (const r of sorted) {
|
|
180
|
+
const scoreIcon = r.trustScore >= 80 ? ':green_circle:' : r.trustScore >= 50 ? ':yellow_circle:' : ':red_circle:';
|
|
181
|
+
lines.push(
|
|
182
|
+
`| ${r.package} | ${scoreIcon} ${r.trustScore} | ${r.isZombie ? ':skull:' : ':heavy_check_mark:'} | ${r.blastRadius} files | ${r.trend} |`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Migration suggestions
|
|
187
|
+
const withAlternatives = reports.filter(
|
|
188
|
+
(r) => r.trustScore < 50 && r.alternatives.length > 0,
|
|
189
|
+
);
|
|
190
|
+
if (withAlternatives.length > 0) {
|
|
191
|
+
lines.push('');
|
|
192
|
+
lines.push('### Migration Suggestions');
|
|
193
|
+
lines.push('');
|
|
194
|
+
for (const r of withAlternatives) {
|
|
195
|
+
lines.push(`- **${r.package}** -> consider: ${r.alternatives.join(', ')}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
lines.push('');
|
|
200
|
+
lines.push('---');
|
|
201
|
+
lines.push('*Generated by [dep-oracle](https://github.com/ertugrulakben/dep-oracle)*');
|
|
202
|
+
|
|
203
|
+
return lines.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Main
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
async function main(): Promise<void> {
|
|
211
|
+
console.log(`dep-oracle: scanning ${workspaceDir}`);
|
|
212
|
+
console.log(` min-score: ${minScore}, format: ${format}`);
|
|
213
|
+
|
|
214
|
+
// Parse project
|
|
215
|
+
const tree = await parseProject(workspaceDir);
|
|
216
|
+
if (!tree) {
|
|
217
|
+
console.error(
|
|
218
|
+
'dep-oracle: No supported manifest file found in the workspace. ' +
|
|
219
|
+
'Supported: package.json, requirements.txt, pyproject.toml, Pipfile.',
|
|
220
|
+
);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Build import graph for blast radius
|
|
225
|
+
const importGraph = await buildImportGraph(workspaceDir);
|
|
226
|
+
|
|
227
|
+
// Collect trust reports
|
|
228
|
+
const directNodes = Array.from(tree.nodes.values()).filter((n) => n.isDirect);
|
|
229
|
+
console.log(`dep-oracle: analyzing ${directNodes.length} direct dependencies...`);
|
|
230
|
+
|
|
231
|
+
const reports: TrustReport[] = [];
|
|
232
|
+
for (const node of directNodes) {
|
|
233
|
+
const blastRadius = getBlastRadius(node.name, importGraph);
|
|
234
|
+
const report = await buildTrustReport(node.name, node.version, blastRadius);
|
|
235
|
+
reports.push(report);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Overall score
|
|
239
|
+
const overallScore =
|
|
240
|
+
reports.length > 0
|
|
241
|
+
? Math.round(
|
|
242
|
+
reports.reduce((sum, r) => sum + r.trustScore, 0) / reports.length,
|
|
243
|
+
)
|
|
244
|
+
: 100;
|
|
245
|
+
|
|
246
|
+
// Output in requested format
|
|
247
|
+
if (format === 'json' || format === 'sarif') {
|
|
248
|
+
const zombieCount = reports.filter((r) => r.isZombie).length;
|
|
249
|
+
const criticalCount = reports.filter((r) => r.trustScore < 50).length;
|
|
250
|
+
const scanResult: ScanResult = {
|
|
251
|
+
tree,
|
|
252
|
+
reports,
|
|
253
|
+
overallScore,
|
|
254
|
+
summary:
|
|
255
|
+
`Scanned ${reports.length} direct dependencies. ` +
|
|
256
|
+
`Overall trust score: ${overallScore}/100. ` +
|
|
257
|
+
`${zombieCount} zombie(s) detected. ` +
|
|
258
|
+
`${criticalCount} package(s) below trust threshold.`,
|
|
259
|
+
};
|
|
260
|
+
console.log(jsonReporter.report(scanResult));
|
|
261
|
+
} else {
|
|
262
|
+
console.log(formatTable(reports, overallScore));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Set GitHub Actions outputs
|
|
266
|
+
await setOutput('overall-score', String(overallScore));
|
|
267
|
+
await setOutput('package-count', String(reports.length));
|
|
268
|
+
await setOutput(
|
|
269
|
+
'zombie-count',
|
|
270
|
+
String(reports.filter((r) => r.isZombie).length),
|
|
271
|
+
);
|
|
272
|
+
await setOutput(
|
|
273
|
+
'critical-count',
|
|
274
|
+
String(reports.filter((r) => r.trustScore < minScore).length),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// Write job summary
|
|
278
|
+
await addSummary(formatMarkdownSummary(reports, overallScore));
|
|
279
|
+
|
|
280
|
+
// Determine exit code
|
|
281
|
+
const failedPackages = reports.filter((r) => r.trustScore < minScore);
|
|
282
|
+
if (failedPackages.length > 0) {
|
|
283
|
+
console.error('');
|
|
284
|
+
console.error(
|
|
285
|
+
`dep-oracle: ${failedPackages.length} package(s) below minimum trust score of ${minScore}:`,
|
|
286
|
+
);
|
|
287
|
+
for (const pkg of failedPackages) {
|
|
288
|
+
console.error(` - ${pkg.package} (score: ${pkg.trustScore})`);
|
|
289
|
+
}
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log('');
|
|
294
|
+
console.log(
|
|
295
|
+
`dep-oracle: All ${reports.length} packages meet the minimum trust score of ${minScore}. Passed.`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
main().catch((err) => {
|
|
300
|
+
console.error('dep-oracle: Unexpected error:', err);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
});
|