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 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
+ [![npm version](https://img.shields.io/npm/v/dep-oracle.svg)](https://www.npmjs.com/package/dep-oracle)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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'
@@ -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
+ });