capscan 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,128 @@
1
+ import type { ScanResult, CapabilityCategory, Permission, CapabilityFinding, PackageResult } from '@capscan/engine';
2
+
3
+ const CATEGORY_ICONS: Record<CapabilityCategory, string> = {
4
+ filesystem: '📁',
5
+ network: '🌐',
6
+ process: '⚙️',
7
+ environment: '🔑',
8
+ crypto: '🔐',
9
+ dynamic_code: '📦',
10
+ native: '🧩',
11
+ installation: '🔧',
12
+ obfuscation: '🎭',
13
+ };
14
+
15
+ const CATEGORY_LABELS: Record<CapabilityCategory, string> = {
16
+ filesystem: 'Filesystem',
17
+ network: 'Network',
18
+ process: 'Process',
19
+ environment: 'Environment',
20
+ crypto: 'Crypto',
21
+ dynamic_code: 'Dynamic Code',
22
+ native: 'Native',
23
+ installation: 'Installation',
24
+ obfuscation: 'Obfuscation',
25
+ };
26
+
27
+ const CONFIDENCE_COLORS: Record<string, (s: string) => string> = {
28
+ high: (s: string) => `\x1b[32m${s}\x1b[0m`,
29
+ medium: (s: string) => `\x1b[33m${s}\x1b[0m`,
30
+ low: (s: string) => `\x1b[31m${s}\x1b[0m`,
31
+ };
32
+
33
+ function padRight(str: string, len: number): string {
34
+ return str.length >= len ? str : str + ' '.repeat(len - str.length);
35
+ }
36
+
37
+ function groupByCategory(findings: CapabilityFinding[]): Map<CapabilityCategory, CapabilityFinding[]> {
38
+ const grouped = new Map<CapabilityCategory, CapabilityFinding[]>();
39
+ for (const f of findings) {
40
+ const arr = grouped.get(f.capability.category) || [];
41
+ arr.push(f);
42
+ grouped.set(f.capability.category, arr);
43
+ }
44
+ return grouped;
45
+ }
46
+
47
+ function formatEvidenceLine(e: { file: string; line: number; symbol: string; confidence: string }, indent: string): string {
48
+ const confColor = CONFIDENCE_COLORS[e.confidence] || CONFIDENCE_COLORS.medium;
49
+ const location = e.line > 0 ? `${e.file}:${e.line}` : e.file;
50
+ return `${indent}${confColor('●')} ${dim(location)} ${bold(e.symbol)}`;
51
+ }
52
+
53
+ function dim(s: string): string { return `\x1b[2m${s}\x1b[0m`; }
54
+ function bold(s: string): string { return `\x1b[1m${s}\x1b[0m`; }
55
+
56
+ function formatPackage(pkg: PackageResult): string[] {
57
+ const lines: string[] = [];
58
+ const grouped = groupByCategory(pkg.capabilities);
59
+ lines.push(` \x1b[33m●\x1b[0m ${bold(pkg.name)}@${dim(pkg.version)}`);
60
+
61
+ for (const [category, findings] of grouped) {
62
+ const icon = CATEGORY_ICONS[category];
63
+ const label = CATEGORY_LABELS[category];
64
+ lines.push(` ${icon} ${bold(label)}`);
65
+
66
+ const byPermission = new Map<string, CapabilityFinding[]>();
67
+ for (const f of findings) {
68
+ const perm = f.capability.permission;
69
+ const arr = byPermission.get(perm) || [];
70
+ arr.push(f);
71
+ byPermission.set(perm, arr);
72
+ }
73
+
74
+ for (const [perm, permFindings] of byPermission) {
75
+ lines.push(` ${dim(perm)}`);
76
+ for (const f of permFindings.slice(0, 3)) {
77
+ for (const e of f.evidence.slice(0, 2)) {
78
+ lines.push(formatEvidenceLine(e, ' '));
79
+ }
80
+ }
81
+ if (permFindings.length > 3) lines.push(dim(` ... and ${permFindings.length - 3} more`));
82
+ }
83
+ }
84
+
85
+ return lines;
86
+ }
87
+
88
+ export function reportTerminal(result: ScanResult): string {
89
+ const lines: string[] = [];
90
+ lines.push('');
91
+ lines.push(`${bold('\x1b[36mCapScan\x1b[0m')} ${dim('v0.1.0 — deterministic capability analysis engine')}`);
92
+ lines.push('');
93
+ lines.push(`${bold('Project:')} ${result.meta.scanPath}`);
94
+ lines.push(`${bold('Package Manager:')} ${result.meta.packageManager}`);
95
+ lines.push(`${bold('Dependencies:')} ${result.summary.totalPackages.toString()}`);
96
+ lines.push('');
97
+ lines.push(bold('\x1b[4mCapabilities\x1b[0m'));
98
+ lines.push(' ' + '─'.repeat(35));
99
+
100
+ for (const [cat, perms] of Object.entries(result.summary.capabilities)) {
101
+ const catLabel = CATEGORY_LABELS[cat as CapabilityCategory];
102
+ const icon = CATEGORY_ICONS[cat as CapabilityCategory];
103
+ const entries = Object.entries(perms).filter(([, c]) => c > 0);
104
+ if (entries.length === 0) continue;
105
+ lines.push(` ${icon} ${bold(catLabel)}`);
106
+ for (const [perm, count] of entries) {
107
+ const dots = '.'.repeat(Math.max(0, 25 - perm.length));
108
+ const pkgText = count === 1 ? 'package' : 'packages';
109
+ lines.push(` ${dim(perm)} ${dots} ${count.toString()} ${pkgText}`);
110
+ }
111
+ }
112
+
113
+ const packagesWithCaps = result.packages.filter(p => p.capabilities.length > 0);
114
+ if (packagesWithCaps.length > 0) {
115
+ lines.push('');
116
+ lines.push(bold('\x1b[4mPackages with Capabilities\x1b[0m'));
117
+ lines.push(' ' + '─'.repeat(35));
118
+ lines.push('');
119
+ for (const pkg of packagesWithCaps) {
120
+ lines.push(...formatPackage(pkg));
121
+ lines.push('');
122
+ }
123
+ }
124
+
125
+ lines.push(dim(` Scan completed at ${result.meta.timestamp}`));
126
+ lines.push('');
127
+ return lines.join('\n');
128
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*.ts"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: { index: 'src/index.ts' },
5
+ format: ['esm'],
6
+ clean: true,
7
+ sourcemap: true,
8
+ splitting: false,
9
+ banner: { js: '#!/usr/bin/env node' },
10
+ });