@cyberhub/shieldpm 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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +239 -0
  3. package/dist/analyzer/static.d.ts +35 -0
  4. package/dist/analyzer/static.d.ts.map +1 -0
  5. package/dist/analyzer/static.js +416 -0
  6. package/dist/analyzer/static.js.map +1 -0
  7. package/dist/analyzer/typosquat.d.ts +30 -0
  8. package/dist/analyzer/typosquat.d.ts.map +1 -0
  9. package/dist/analyzer/typosquat.js +211 -0
  10. package/dist/analyzer/typosquat.js.map +1 -0
  11. package/dist/cli.d.ts +10 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +621 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/diff/dependency.d.ts +51 -0
  16. package/dist/diff/dependency.d.ts.map +1 -0
  17. package/dist/diff/dependency.js +222 -0
  18. package/dist/diff/dependency.js.map +1 -0
  19. package/dist/fingerprint/profile.d.ts +68 -0
  20. package/dist/fingerprint/profile.d.ts.map +1 -0
  21. package/dist/fingerprint/profile.js +233 -0
  22. package/dist/fingerprint/profile.js.map +1 -0
  23. package/dist/index.d.ts +21 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +22 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/monitor/permissions.d.ts +45 -0
  28. package/dist/monitor/permissions.d.ts.map +1 -0
  29. package/dist/monitor/permissions.js +265 -0
  30. package/dist/monitor/permissions.js.map +1 -0
  31. package/dist/sandbox/runner.d.ts +46 -0
  32. package/dist/sandbox/runner.d.ts.map +1 -0
  33. package/dist/sandbox/runner.js +216 -0
  34. package/dist/sandbox/runner.js.map +1 -0
  35. package/dist/utils/colors.d.ts +31 -0
  36. package/dist/utils/colors.d.ts.map +1 -0
  37. package/dist/utils/colors.js +54 -0
  38. package/dist/utils/colors.js.map +1 -0
  39. package/dist/utils/logger.d.ts +26 -0
  40. package/dist/utils/logger.d.ts.map +1 -0
  41. package/dist/utils/logger.js +77 -0
  42. package/dist/utils/logger.js.map +1 -0
  43. package/package.json +24 -0
  44. package/src/analyzer/static.ts +483 -0
  45. package/src/analyzer/typosquat.ts +272 -0
  46. package/src/cli.ts +700 -0
  47. package/src/diff/dependency.ts +297 -0
  48. package/src/fingerprint/profile.ts +333 -0
  49. package/src/index.ts +34 -0
  50. package/src/monitor/permissions.ts +330 -0
  51. package/src/sandbox/runner.ts +302 -0
  52. package/src/utils/colors.ts +58 -0
  53. package/src/utils/logger.ts +87 -0
  54. package/tsconfig.json +19 -0
@@ -0,0 +1,297 @@
1
+ /**
2
+ * ShieldPM — Dependency Diff
3
+ * Compare two states of package-lock.json to detect meaningful changes
4
+ * in the dependency tree — new packages, version bumps, and red flags.
5
+ */
6
+
7
+ import { readFile } from 'node:fs/promises';
8
+
9
+ // ── Types ────────────────────────────────────────────────────────────────
10
+
11
+ export interface LockPackageInfo {
12
+ version: string;
13
+ resolved?: string;
14
+ integrity?: string;
15
+ dependencies?: Record<string, string>;
16
+ devDependencies?: Record<string, string>;
17
+ hasInstallScript?: boolean;
18
+ hasBin?: boolean;
19
+ }
20
+
21
+ export interface PackageDelta {
22
+ name: string;
23
+ type: 'added' | 'removed' | 'changed';
24
+ oldVersion?: string;
25
+ newVersion?: string;
26
+ /** Significant changes detected */
27
+ flags: DeltaFlag[];
28
+ }
29
+
30
+ export interface DeltaFlag {
31
+ type: 'new-install-script' | 'new-native-module' | 'new-network-dep' | 'major-bump' |
32
+ 'new-dependency' | 'removed-dependency' | 'version-downgrade' | 'new-bin';
33
+ message: string;
34
+ }
35
+
36
+ export interface DependencyDiffReport {
37
+ /** Total packages in the old lock */
38
+ oldPackageCount: number;
39
+ /** Total packages in the new lock */
40
+ newPackageCount: number;
41
+ /** Added packages */
42
+ added: PackageDelta[];
43
+ /** Removed packages */
44
+ removed: PackageDelta[];
45
+ /** Changed packages */
46
+ changed: PackageDelta[];
47
+ /** High-level summary */
48
+ summary: string;
49
+ /** Red flags found */
50
+ flags: DeltaFlag[];
51
+ }
52
+
53
+ // ── Lock file parsing ────────────────────────────────────────────────────
54
+
55
+ interface LockfileV2 {
56
+ lockfileVersion?: number;
57
+ packages?: Record<string, LockPackageInfo>;
58
+ dependencies?: Record<string, LockPackageInfo>;
59
+ }
60
+
61
+ function parseLockPackages(lockContent: string): Map<string, LockPackageInfo> {
62
+ const lock: LockfileV2 = JSON.parse(lockContent);
63
+ const packages = new Map<string, LockPackageInfo>();
64
+
65
+ if (lock.packages) {
66
+ // v2/v3 format: keys are "node_modules/<name>"
67
+ for (const [key, info] of Object.entries(lock.packages)) {
68
+ if (key === '') continue; // Root package
69
+ const name = key.replace(/^node_modules\//, '');
70
+ // Skip nested node_modules
71
+ if (name.includes('node_modules/')) continue;
72
+ packages.set(name, info);
73
+ }
74
+ } else if (lock.dependencies) {
75
+ // v1 format
76
+ for (const [name, info] of Object.entries(lock.dependencies)) {
77
+ packages.set(name, info);
78
+ }
79
+ }
80
+
81
+ return packages;
82
+ }
83
+
84
+ // ── Known network-capable packages ───────────────────────────────────────
85
+
86
+ const NETWORK_PACKAGES = new Set([
87
+ 'node-fetch', 'axios', 'got', 'request', 'superagent', 'undici',
88
+ 'http-proxy', 'http-proxy-agent', 'https-proxy-agent', 'socks-proxy-agent',
89
+ 'socket.io', 'ws', 'websocket', 'net', 'dgram',
90
+ ]);
91
+
92
+ const NATIVE_PACKAGES = new Set([
93
+ 'node-gyp', 'node-pre-gyp', 'prebuild-install', 'nan', 'napi',
94
+ 'sharp', 'bcrypt', 'better-sqlite3', 'canvas', 'grpc',
95
+ 'sqlite3', 'pg-native', 'libxmljs',
96
+ ]);
97
+
98
+ // ── Version comparison ───────────────────────────────────────────────────
99
+
100
+ function parseSemver(version: string): { major: number; minor: number; patch: number } | null {
101
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
102
+ if (!match) return null;
103
+ return {
104
+ major: parseInt(match[1], 10),
105
+ minor: parseInt(match[2], 10),
106
+ patch: parseInt(match[3], 10),
107
+ };
108
+ }
109
+
110
+ function isMajorBump(oldVer: string, newVer: string): boolean {
111
+ const o = parseSemver(oldVer);
112
+ const n = parseSemver(newVer);
113
+ if (!o || !n) return false;
114
+ return n.major > o.major;
115
+ }
116
+
117
+ function isDowngrade(oldVer: string, newVer: string): boolean {
118
+ const o = parseSemver(oldVer);
119
+ const n = parseSemver(newVer);
120
+ if (!o || !n) return false;
121
+ if (n.major < o.major) return true;
122
+ if (n.major === o.major && n.minor < o.minor) return true;
123
+ if (n.major === o.major && n.minor === o.minor && n.patch < o.patch) return true;
124
+ return false;
125
+ }
126
+
127
+ // ── Diff computation ─────────────────────────────────────────────────────
128
+
129
+ function computeFlags(
130
+ name: string,
131
+ oldInfo: LockPackageInfo | undefined,
132
+ newInfo: LockPackageInfo | undefined
133
+ ): DeltaFlag[] {
134
+ const flags: DeltaFlag[] = [];
135
+
136
+ if (newInfo && !oldInfo) {
137
+ // Newly added package
138
+ if (newInfo.hasInstallScript) {
139
+ flags.push({
140
+ type: 'new-install-script',
141
+ message: `New package "${name}" has install scripts`,
142
+ });
143
+ }
144
+ if (NATIVE_PACKAGES.has(name)) {
145
+ flags.push({
146
+ type: 'new-native-module',
147
+ message: `New package "${name}" is a native module`,
148
+ });
149
+ }
150
+ if (NETWORK_PACKAGES.has(name)) {
151
+ flags.push({
152
+ type: 'new-network-dep',
153
+ message: `New package "${name}" has network capabilities`,
154
+ });
155
+ }
156
+ if (newInfo.hasBin) {
157
+ flags.push({
158
+ type: 'new-bin',
159
+ message: `New package "${name}" installs binaries`,
160
+ });
161
+ }
162
+ }
163
+
164
+ if (oldInfo && newInfo) {
165
+ // Version change checks
166
+ if (oldInfo.version && newInfo.version) {
167
+ if (isMajorBump(oldInfo.version, newInfo.version)) {
168
+ flags.push({
169
+ type: 'major-bump',
170
+ message: `"${name}" major version bump: ${oldInfo.version} -> ${newInfo.version}`,
171
+ });
172
+ }
173
+ if (isDowngrade(oldInfo.version, newInfo.version)) {
174
+ flags.push({
175
+ type: 'version-downgrade',
176
+ message: `"${name}" version downgrade: ${oldInfo.version} -> ${newInfo.version}`,
177
+ });
178
+ }
179
+ }
180
+
181
+ // New install script added
182
+ if (!oldInfo.hasInstallScript && newInfo.hasInstallScript) {
183
+ flags.push({
184
+ type: 'new-install-script',
185
+ message: `"${name}" added install scripts in ${newInfo.version}`,
186
+ });
187
+ }
188
+
189
+ // New sub-dependencies
190
+ const oldDeps = new Set(Object.keys(oldInfo.dependencies ?? {}));
191
+ const newDeps = Object.keys(newInfo.dependencies ?? {});
192
+ for (const dep of newDeps) {
193
+ if (!oldDeps.has(dep)) {
194
+ flags.push({
195
+ type: 'new-dependency',
196
+ message: `"${name}" added new dependency "${dep}"`,
197
+ });
198
+ }
199
+ }
200
+ }
201
+
202
+ return flags;
203
+ }
204
+
205
+ /**
206
+ * Compare two package-lock.json contents and produce a diff report.
207
+ */
208
+ export function diffLockfiles(
209
+ oldLockContent: string,
210
+ newLockContent: string
211
+ ): DependencyDiffReport {
212
+ const oldPackages = parseLockPackages(oldLockContent);
213
+ const newPackages = parseLockPackages(newLockContent);
214
+
215
+ const added: PackageDelta[] = [];
216
+ const removed: PackageDelta[] = [];
217
+ const changed: PackageDelta[] = [];
218
+ const allFlags: DeltaFlag[] = [];
219
+
220
+ // Find added and changed
221
+ for (const [name, newInfo] of newPackages) {
222
+ const oldInfo = oldPackages.get(name);
223
+
224
+ if (!oldInfo) {
225
+ const flags = computeFlags(name, undefined, newInfo);
226
+ added.push({
227
+ name,
228
+ type: 'added',
229
+ newVersion: newInfo.version,
230
+ flags,
231
+ });
232
+ allFlags.push(...flags);
233
+ } else if (oldInfo.version !== newInfo.version) {
234
+ const flags = computeFlags(name, oldInfo, newInfo);
235
+ changed.push({
236
+ name,
237
+ type: 'changed',
238
+ oldVersion: oldInfo.version,
239
+ newVersion: newInfo.version,
240
+ flags,
241
+ });
242
+ allFlags.push(...flags);
243
+ }
244
+ }
245
+
246
+ // Find removed
247
+ for (const [name, oldInfo] of oldPackages) {
248
+ if (!newPackages.has(name)) {
249
+ removed.push({
250
+ name,
251
+ type: 'removed',
252
+ oldVersion: oldInfo.version,
253
+ flags: [],
254
+ });
255
+ }
256
+ }
257
+
258
+ // Sort alphabetically
259
+ added.sort((a, b) => a.name.localeCompare(b.name));
260
+ removed.sort((a, b) => a.name.localeCompare(b.name));
261
+ changed.sort((a, b) => a.name.localeCompare(b.name));
262
+
263
+ // Summary
264
+ const parts: string[] = [];
265
+ if (added.length > 0) parts.push(`${added.length} added`);
266
+ if (removed.length > 0) parts.push(`${removed.length} removed`);
267
+ if (changed.length > 0) parts.push(`${changed.length} changed`);
268
+ if (allFlags.length > 0) parts.push(`${allFlags.length} flags`);
269
+
270
+ const summary = parts.length > 0
271
+ ? `Dependency changes: ${parts.join(', ')}`
272
+ : 'No dependency changes';
273
+
274
+ return {
275
+ oldPackageCount: oldPackages.size,
276
+ newPackageCount: newPackages.size,
277
+ added,
278
+ removed,
279
+ changed,
280
+ summary,
281
+ flags: allFlags,
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Load and diff two package-lock.json files from disk.
287
+ */
288
+ export async function diffLockfilesByPath(
289
+ oldPath: string,
290
+ newPath: string
291
+ ): Promise<DependencyDiffReport> {
292
+ const [oldContent, newContent] = await Promise.all([
293
+ readFile(oldPath, 'utf-8'),
294
+ readFile(newPath, 'utf-8'),
295
+ ]);
296
+ return diffLockfiles(oldContent, newContent);
297
+ }
@@ -0,0 +1,333 @@
1
+ /**
2
+ * ShieldPM — Behavioral Fingerprinting
3
+ * Creates and compares behavioral profiles for packages to detect
4
+ * unexpected changes between versions.
5
+ */
6
+
7
+ import { readFile, writeFile, readdir, stat, mkdir } from 'node:fs/promises';
8
+ import { join, extname, relative } from 'node:path';
9
+ import { createHash } from 'node:crypto';
10
+
11
+ // ── Types ────────────────────────────────────────────────────────────────
12
+
13
+ export interface BehaviorProfile {
14
+ /** Package name */
15
+ name: string;
16
+ /** Package version */
17
+ version: string;
18
+ /** ISO timestamp of when the profile was generated */
19
+ generatedAt: string;
20
+ /** SHA-256 hash of all .js file contents concatenated */
21
+ contentHash: string;
22
+ /** Individual file hashes */
23
+ fileHashes: Record<string, string>;
24
+ /** All require() and import statements found */
25
+ imports: string[];
26
+ /** Native module bindings (e.g., .node files, node-gyp) */
27
+ nativeBindings: string[];
28
+ /** Network endpoints parsed from source */
29
+ networkEndpoints: string[];
30
+ /** Filesystem paths parsed from source */
31
+ fsPaths: string[];
32
+ /** Total file count */
33
+ fileCount: number;
34
+ /** Total size in bytes */
35
+ totalSize: number;
36
+ }
37
+
38
+ export interface ProfileDiff {
39
+ /** Newly added imports */
40
+ addedImports: string[];
41
+ /** Removed imports */
42
+ removedImports: string[];
43
+ /** Newly added network endpoints */
44
+ addedNetworkEndpoints: string[];
45
+ /** Removed network endpoints */
46
+ removedNetworkEndpoints: string[];
47
+ /** New filesystem paths */
48
+ addedFsPaths: string[];
49
+ /** Removed filesystem paths */
50
+ removedFsPaths: string[];
51
+ /** New native bindings */
52
+ addedNativeBindings: string[];
53
+ /** Removed native bindings */
54
+ removedNativeBindings: string[];
55
+ /** Files added */
56
+ addedFiles: string[];
57
+ /** Files removed */
58
+ removedFiles: string[];
59
+ /** Files with changed content */
60
+ changedFiles: string[];
61
+ /** Whether the overall content hash changed */
62
+ contentHashChanged: boolean;
63
+ /** Human-readable summary */
64
+ summary: string;
65
+ }
66
+
67
+ // ── Profile storage ──────────────────────────────────────────────────────
68
+
69
+ const PROFILE_DIR = '.shieldpm/profiles';
70
+
71
+ function profilePath(baseDir: string, name: string, version: string): string {
72
+ return join(baseDir, PROFILE_DIR, `${name.replace('/', '__')}@${version}.json`);
73
+ }
74
+
75
+ export async function saveProfile(baseDir: string, profile: BehaviorProfile): Promise<string> {
76
+ const dir = join(baseDir, PROFILE_DIR);
77
+ await mkdir(dir, { recursive: true });
78
+
79
+ const path = profilePath(baseDir, profile.name, profile.version);
80
+ await writeFile(path, JSON.stringify(profile, null, 2) + '\n', 'utf-8');
81
+ return path;
82
+ }
83
+
84
+ export async function loadProfile(
85
+ baseDir: string,
86
+ name: string,
87
+ version: string
88
+ ): Promise<BehaviorProfile | null> {
89
+ const path = profilePath(baseDir, name, version);
90
+ try {
91
+ const raw = await readFile(path, 'utf-8');
92
+ return JSON.parse(raw) as BehaviorProfile;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ // ── Source parsing helpers ────────────────────────────────────────────────
99
+
100
+ const JS_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts']);
101
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'test', 'tests', '__tests__']);
102
+
103
+ async function collectSourceFiles(dir: string): Promise<string[]> {
104
+ const files: string[] = [];
105
+
106
+ async function walk(d: string): Promise<void> {
107
+ let entries;
108
+ try {
109
+ entries = await readdir(d, { withFileTypes: true });
110
+ } catch {
111
+ return;
112
+ }
113
+ for (const entry of entries) {
114
+ const full = join(d, entry.name);
115
+ if (entry.isDirectory()) {
116
+ if (!SKIP_DIRS.has(entry.name)) await walk(full);
117
+ } else if (entry.isFile() && JS_EXTENSIONS.has(extname(entry.name))) {
118
+ files.push(full);
119
+ }
120
+ }
121
+ }
122
+
123
+ await walk(dir);
124
+ return files.sort();
125
+ }
126
+
127
+ function extractImports(source: string): string[] {
128
+ const imports = new Set<string>();
129
+
130
+ // CommonJS require
131
+ const requireRe = /require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
132
+ let m: RegExpExecArray | null;
133
+ while ((m = requireRe.exec(source)) !== null) {
134
+ imports.add(m[1]);
135
+ }
136
+
137
+ // ESM import
138
+ const importRe = /(?:import|export)\s+.*?from\s+['"`]([^'"`]+)['"`]/g;
139
+ while ((m = importRe.exec(source)) !== null) {
140
+ imports.add(m[1]);
141
+ }
142
+
143
+ // Dynamic import
144
+ const dynImportRe = /import\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
145
+ while ((m = dynImportRe.exec(source)) !== null) {
146
+ imports.add(m[1]);
147
+ }
148
+
149
+ return [...imports].sort();
150
+ }
151
+
152
+ function extractNetworkEndpoints(source: string): string[] {
153
+ const endpoints = new Set<string>();
154
+
155
+ // URL literals
156
+ const urlRe = /['"`](https?:\/\/[^'"`\s]+)['"`]/g;
157
+ let m: RegExpExecArray | null;
158
+ while ((m = urlRe.exec(source)) !== null) {
159
+ endpoints.add(m[1]);
160
+ }
161
+
162
+ // fetch/http.request with template literals are harder — capture hostname patterns
163
+ const hostRe = /(?:hostname|host)\s*[:=]\s*['"`]([^'"`]+)['"`]/g;
164
+ while ((m = hostRe.exec(source)) !== null) {
165
+ endpoints.add(m[1]);
166
+ }
167
+
168
+ return [...endpoints].sort();
169
+ }
170
+
171
+ function extractFsPaths(source: string): string[] {
172
+ const paths = new Set<string>();
173
+
174
+ // readFile, writeFile, etc. with string literals
175
+ const fsRe = /(?:readFile|writeFile|readdir|unlink|stat|access|mkdir|rmdir|rename|copyFile|appendFile)\w*\s*\(\s*['"`]([^'"`]+)['"`]/g;
176
+ let m: RegExpExecArray | null;
177
+ while ((m = fsRe.exec(source)) !== null) {
178
+ paths.add(m[1]);
179
+ }
180
+
181
+ return [...paths].sort();
182
+ }
183
+
184
+ function extractNativeBindings(files: string[], source: string): string[] {
185
+ const bindings = new Set<string>();
186
+
187
+ // .node files
188
+ for (const f of files) {
189
+ if (f.endsWith('.node')) {
190
+ bindings.add(f);
191
+ }
192
+ }
193
+
194
+ // require with .node extension
195
+ const nodeReqRe = /require\s*\(\s*['"`]([^'"`]*\.node)['"`]\s*\)/g;
196
+ let m: RegExpExecArray | null;
197
+ while ((m = nodeReqRe.exec(source)) !== null) {
198
+ bindings.add(m[1]);
199
+ }
200
+
201
+ // node-gyp / node-pre-gyp / prebuild patterns
202
+ if (/binding\.gyp|node-gyp|node-pre-gyp|prebuild-install|napi_/.test(source)) {
203
+ bindings.add('<native-addon>');
204
+ }
205
+
206
+ return [...bindings].sort();
207
+ }
208
+
209
+ // ── Profile generation ───────────────────────────────────────────────────
210
+
211
+ /**
212
+ * Generate a behavioral profile for a package directory.
213
+ */
214
+ export async function generateProfile(
215
+ packageDir: string,
216
+ name: string,
217
+ version: string
218
+ ): Promise<BehaviorProfile> {
219
+ const files = await collectSourceFiles(packageDir);
220
+
221
+ const allImports = new Set<string>();
222
+ const allEndpoints = new Set<string>();
223
+ const allFsPaths = new Set<string>();
224
+ const fileHashes: Record<string, string> = {};
225
+ const contentParts: string[] = [];
226
+ let totalSize = 0;
227
+ let allSourceConcat = '';
228
+
229
+ for (const file of files) {
230
+ let content: string;
231
+ try {
232
+ content = await readFile(file, 'utf-8');
233
+ } catch {
234
+ continue;
235
+ }
236
+
237
+ const relPath = relative(packageDir, file);
238
+ const hash = createHash('sha256').update(content).digest('hex');
239
+ fileHashes[relPath] = hash;
240
+ contentParts.push(content);
241
+ totalSize += Buffer.byteLength(content);
242
+ allSourceConcat += content + '\n';
243
+
244
+ for (const imp of extractImports(content)) allImports.add(imp);
245
+ for (const ep of extractNetworkEndpoints(content)) allEndpoints.add(ep);
246
+ for (const fp of extractFsPaths(content)) allFsPaths.add(fp);
247
+ }
248
+
249
+ const contentHash = createHash('sha256')
250
+ .update(contentParts.join('\n'))
251
+ .digest('hex');
252
+
253
+ const nativeBindings = extractNativeBindings(
254
+ files.map((f) => relative(packageDir, f)),
255
+ allSourceConcat
256
+ );
257
+
258
+ return {
259
+ name,
260
+ version,
261
+ generatedAt: new Date().toISOString(),
262
+ contentHash,
263
+ fileHashes,
264
+ imports: [...allImports].sort(),
265
+ nativeBindings,
266
+ networkEndpoints: [...allEndpoints].sort(),
267
+ fsPaths: [...allFsPaths].sort(),
268
+ fileCount: files.length,
269
+ totalSize,
270
+ };
271
+ }
272
+
273
+ // ── Profile comparison ───────────────────────────────────────────────────
274
+
275
+ function arrayDiff<T>(oldArr: T[], newArr: T[]): { added: T[]; removed: T[] } {
276
+ const oldSet = new Set(oldArr);
277
+ const newSet = new Set(newArr);
278
+ return {
279
+ added: newArr.filter((x) => !oldSet.has(x)),
280
+ removed: oldArr.filter((x) => !newSet.has(x)),
281
+ };
282
+ }
283
+
284
+ /**
285
+ * Compare two behavioral profiles and report differences.
286
+ */
287
+ export function diffProfiles(oldProfile: BehaviorProfile, newProfile: BehaviorProfile): ProfileDiff {
288
+ const importDiff = arrayDiff(oldProfile.imports, newProfile.imports);
289
+ const netDiff = arrayDiff(oldProfile.networkEndpoints, newProfile.networkEndpoints);
290
+ const fsDiff = arrayDiff(oldProfile.fsPaths, newProfile.fsPaths);
291
+ const nativeDiff = arrayDiff(oldProfile.nativeBindings, newProfile.nativeBindings);
292
+
293
+ const oldFiles = Object.keys(oldProfile.fileHashes);
294
+ const newFiles = Object.keys(newProfile.fileHashes);
295
+ const fileDiff = arrayDiff(oldFiles, newFiles);
296
+
297
+ const commonFiles = oldFiles.filter((f) => newFiles.includes(f));
298
+ const changedFiles = commonFiles.filter(
299
+ (f) => oldProfile.fileHashes[f] !== newProfile.fileHashes[f]
300
+ );
301
+
302
+ const contentHashChanged = oldProfile.contentHash !== newProfile.contentHash;
303
+
304
+ // Build summary
305
+ const parts: string[] = [];
306
+ if (fileDiff.added.length > 0) parts.push(`${fileDiff.added.length} files added`);
307
+ if (fileDiff.removed.length > 0) parts.push(`${fileDiff.removed.length} files removed`);
308
+ if (changedFiles.length > 0) parts.push(`${changedFiles.length} files changed`);
309
+ if (importDiff.added.length > 0) parts.push(`${importDiff.added.length} new imports`);
310
+ if (importDiff.removed.length > 0) parts.push(`${importDiff.removed.length} removed imports`);
311
+ if (netDiff.added.length > 0) parts.push(`${netDiff.added.length} new network endpoints`);
312
+ if (nativeDiff.added.length > 0) parts.push(`${nativeDiff.added.length} new native bindings`);
313
+
314
+ const summary = parts.length > 0
315
+ ? `Changes: ${parts.join(', ')}`
316
+ : 'No behavioral changes detected';
317
+
318
+ return {
319
+ addedImports: importDiff.added,
320
+ removedImports: importDiff.removed,
321
+ addedNetworkEndpoints: netDiff.added,
322
+ removedNetworkEndpoints: netDiff.removed,
323
+ addedFsPaths: fsDiff.added,
324
+ removedFsPaths: fsDiff.removed,
325
+ addedNativeBindings: nativeDiff.added,
326
+ removedNativeBindings: nativeDiff.removed,
327
+ addedFiles: fileDiff.added,
328
+ removedFiles: fileDiff.removed,
329
+ changedFiles,
330
+ contentHashChanged,
331
+ summary,
332
+ };
333
+ }
package/src/index.ts ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ShieldPM — Public API
3
+ * Runtime-aware package firewall for Node.js
4
+ *
5
+ * @module shieldpm
6
+ */
7
+
8
+ // Static analysis
9
+ export { analyzePackage, analyzeSource } from './analyzer/static.js';
10
+ export type { Finding, RiskReport, Severity } from './analyzer/static.js';
11
+
12
+ // Typosquatting detection
13
+ export { checkTyposquatting, checkMultiple, levenshtein, POPULAR_PACKAGES } from './analyzer/typosquat.js';
14
+ export type { TyposquatResult, DetectionMethod } from './analyzer/typosquat.js';
15
+
16
+ // Sandbox execution
17
+ export { runSandboxed, runPostInstall } from './sandbox/runner.js';
18
+ export type { SandboxOptions, SandboxResult } from './sandbox/runner.js';
19
+
20
+ // Permission manifest
21
+ export { loadManifest, saveManifest, validateAccess, generateManifest } from './monitor/permissions.js';
22
+ export type { PermissionManifest, PackagePermissions, AccessCheck, ResourceType } from './monitor/permissions.js';
23
+
24
+ // Behavioral fingerprinting
25
+ export { generateProfile, diffProfiles, saveProfile, loadProfile } from './fingerprint/profile.js';
26
+ export type { BehaviorProfile, ProfileDiff } from './fingerprint/profile.js';
27
+
28
+ // Dependency diff
29
+ export { diffLockfiles, diffLockfilesByPath } from './diff/dependency.js';
30
+ export type { DependencyDiffReport, PackageDelta, DeltaFlag } from './diff/dependency.js';
31
+
32
+ // Utilities
33
+ export { log } from './utils/logger.js';
34
+ export * as colors from './utils/colors.js';