@7nsane/zift 3.0.0 โ†’ 4.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/README.md CHANGED
@@ -1,19 +1,20 @@
1
- # ๐Ÿ›ก๏ธ Zift (v3.0.0)
1
+ # ๐Ÿ›ก๏ธ Zift (v4.0.0)
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@7nsane/zift.svg?style=flat-square)](https://www.npmjs.com/package/@7nsane/zift)
4
4
  [![License](https://img.shields.io/npm/l/@7nsane/zift.svg?style=flat-square)](https://www.npmjs.com/package/@7nsane/zift)
5
5
  [![Build Status](https://img.shields.io/badge/CI-passing-brightgreen?style=flat-square)](https://github.com/7nsane/zift)
6
6
 
7
- **The Intelligent Ecosystem Security Engine for JavaScript.**
7
+ **The Deeply Hardened Ecosystem Security Engine for JavaScript.**
8
8
 
9
- Zift v3.0 is a massive leap forward, moving beyond static analysis into **Cross-File Intelligence** and **Runtime Protection**. It is designed to identify and stop advanced supply-chain attacks (credential exfiltration, reverse-shell droppers) before they hit your production environment.
9
+ Zift v4.0 is the "Deep Hardening" release, featuring **Immutable Runtime Guards** and **Opaque Payload Detection**, specifically designed to resist active attacker bypasses.
10
10
 
11
- ## ๐Ÿš€ Major Features (v3.0.0)
11
+ ## ๐Ÿš€ Key Advancements (v4.0.0)
12
12
 
13
- - **๐ŸŒ Cross-File Taint Tracking**: Tracks sensitive data (e.g., `process.env.TOKEN`) across `import/export` and `require` boundaries.
14
- - **๐Ÿง  VM-Based De-obfuscation**: Safe, sandboxed evaluation of string manipulation logic (e.g., character arrays, reverse/join) to reveal hidden malicious signals.
15
- - **๐Ÿ›ก๏ธ Zift Shield (Runtime Guard)**: A real-time audit layer for network and shell activity. Run `zift protect` to monitor your app's dependencies in real-world conditions.
16
- - **๐Ÿ”’ Lockfile Security**: Automatic auditing of `package-lock.json` and `yarn.lock` for registry confusion.
13
+ - **๐Ÿ›ก๏ธ Immutable Zift Shield**: Runtime sinks (`http`, `child_process`) are now immutable. Attackers cannot delete or re-assign them to bypass protection.
14
+ - **๐Ÿงฉ Opaque Payload Detection**: Automatically flags compiled native binaries (`.node`) as high-risk.
15
+ - **๐Ÿงต Universal Protection**: Zift Shield now automatically propagates into `worker_threads`.
16
+ - **๐Ÿ•ต๏ธ Evasion Tracking**: Detects non-deterministic sink construction (e.g., using `Date.now()` or `Math.random()` to hide strings).
17
+ - **๐ŸŒ Cross-File Intelligence**: Full multi-pass taint tracking for ESM and CommonJS.
17
18
 
18
19
  ## ๐Ÿ“ฆ Quick Start
19
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7nsane/zift",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "A high-performance, deterministic security scanner for npm packages.",
5
5
  "main": "src/scanner.js",
6
6
  "bin": {
package/src/collector.js CHANGED
@@ -27,7 +27,9 @@ class ASTCollector {
27
27
  REMOTE_FETCH_SIGNAL: [],
28
28
  PIPE_TO_SHELL_SIGNAL: [],
29
29
  EXPORTS: [],
30
- IMPORTS: []
30
+ IMPORTS: [],
31
+ OPAQUE_STRING_SKIP: [],
32
+ NON_DETERMINISTIC_SINK: []
31
33
  };
32
34
  const flows = [];
33
35
  const sourceCode = code;
@@ -46,7 +48,20 @@ class ASTCollector {
46
48
 
47
49
  walk.ancestor(ast, {
48
50
  Literal: (node) => {
49
- if (typeof node.value === 'string' && node.value.length > 20 && node.value.length < this.maxStringLengthForEntropy) {
51
+ if (typeof node.value === 'string' && node.value.length > 20) {
52
+ if (node.value.length > this.maxStringLengthForEntropy) {
53
+ // High Entropy Skip Warning
54
+ const sample = node.value.substring(0, 100);
55
+ const sampleEntropy = calculateEntropy(sample);
56
+ if (sampleEntropy > this.entropyThreshold) {
57
+ facts.OPAQUE_STRING_SKIP.push({
58
+ file: filePath,
59
+ line: node.loc.start.line,
60
+ reason: `Large string skipped (>2KB) but sample has high entropy (${sampleEntropy.toFixed(2)})`
61
+ });
62
+ }
63
+ return;
64
+ }
50
65
  const entropy = calculateEntropy(node.value);
51
66
  if (entropy > this.entropyThreshold) {
52
67
  facts.OBFUSCATION.push({
@@ -226,6 +241,18 @@ class ASTCollector {
226
241
  });
227
242
  }
228
243
  }
244
+
245
+ // v4.0 Hardening: Non-deterministic constructor
246
+ if (['Math.random', 'Date.now', 'Date()'].some(t => argCode.includes(t))) {
247
+ if (evaluated === 'eval' || evaluated === 'Function' || this.isShellSink(calleeCode)) {
248
+ facts.NON_DETERMINISTIC_SINK.push({
249
+ file: filePath,
250
+ line: node.loc.start.line,
251
+ callee: calleeCode,
252
+ reason: `Sink uses non-deterministic argument (${argCode})`
253
+ });
254
+ }
255
+ }
229
256
  });
230
257
  },
231
258
  MemberExpression: (node) => {
@@ -110,13 +110,40 @@ const RULES = [
110
110
  priority: 2,
111
111
  baseScore: 60,
112
112
  description: 'Detection of attempts to modify package.json scripts or npm configuration.'
113
+ },
114
+ {
115
+ id: 'ZFT-013',
116
+ alias: 'OPAQUE_BINARY_PAYLOAD',
117
+ name: 'Opaque Binary Payload',
118
+ requires: ['NATIVE_BINARY_DETECTED'],
119
+ priority: 2,
120
+ baseScore: 40,
121
+ description: 'Detection of compiled native binaries (.node) which are opaque to static analysis.'
122
+ },
123
+ {
124
+ id: 'ZFT-014',
125
+ alias: 'EVASIVE_SINK_CONSTRUCTION',
126
+ name: 'Evasive Sink Construction',
127
+ requires: ['NON_DETERMINISTIC_SINK'],
128
+ priority: 3,
129
+ baseScore: 50,
130
+ description: 'Detection of dangerous sinks using non-deterministic construction (Date.now, Math.random) to evade analysis.'
131
+ },
132
+ {
133
+ id: 'ZFT-015',
134
+ alias: 'HIGH_ENTROPY_OPAQUE_STRING',
135
+ name: 'High Entropy Opaque String',
136
+ requires: ['OPAQUE_STRING_SKIP'],
137
+ priority: 1,
138
+ baseScore: 25,
139
+ description: 'Detection of very large high-entropy strings that exceed scanning limits.'
113
140
  }
114
141
  ];
115
142
 
116
143
  const CATEGORIES = {
117
144
  SOURCES: ['ENV_READ', 'FILE_READ_SENSITIVE', 'MASS_ENV_ACCESS'],
118
145
  SINKS: ['NETWORK_SINK', 'DNS_SINK', 'RAW_SOCKET_SINK', 'DYNAMIC_EXECUTION', 'SHELL_EXECUTION', 'DYNAMIC_REQUIRE'],
119
- SIGNALS: ['OBFUSCATION', 'ENCODER_USE', 'REMOTE_FETCH_SIGNAL', 'PIPE_TO_SHELL_SIGNAL'],
146
+ SIGNALS: ['OBFUSCATION', 'ENCODER_USE', 'REMOTE_FETCH_SIGNAL', 'PIPE_TO_SHELL_SIGNAL', 'NATIVE_BINARY_DETECTED', 'OPAQUE_STRING_SKIP', 'NON_DETERMINISTIC_SINK'],
120
147
  PERSISTENCE: ['FILE_WRITE_STARTUP'],
121
148
  CONTEXT: ['LIFECYCLE_CONTEXT']
122
149
  };
package/src/scanner.js CHANGED
@@ -43,7 +43,9 @@ class PackageScanner {
43
43
  PIPE_TO_SHELL_SIGNAL: [],
44
44
  LIFECYCLE_CONTEXT: [],
45
45
  EXPORTS: [],
46
- IMPORTS: []
46
+ IMPORTS: [],
47
+ NATIVE_BINARY_DETECTED: [],
48
+ OPAQUE_STRING_SKIP: []
47
49
  },
48
50
  flows: []
49
51
  };
@@ -55,6 +57,13 @@ class PackageScanner {
55
57
  for (let i = 0; i < files.length; i += concurrency) {
56
58
  const chunk = files.slice(i, i + concurrency);
57
59
  await Promise.all(chunk.map(async (file) => {
60
+ if (file.endsWith('.node')) {
61
+ allFacts.facts.NATIVE_BINARY_DETECTED.push({
62
+ file,
63
+ reason: 'Compiled native binary detected (Opaque Payload)'
64
+ });
65
+ return;
66
+ }
58
67
  const stats = fs.statSync(file);
59
68
  if (stats.size > 512 * 1024) return;
60
69
 
@@ -166,7 +175,7 @@ class PackageScanner {
166
175
  const stat = fs.statSync(fullPath);
167
176
  if (stat && stat.isDirectory()) {
168
177
  results.push(...getJsFiles(fullPath));
169
- } else if (file.endsWith('.js')) {
178
+ } else if (file.endsWith('.js') || file.endsWith('.node')) {
170
179
  results.push(fullPath);
171
180
  }
172
181
  }
package/src/shield.js CHANGED
@@ -14,31 +14,38 @@ function setupShield() {
14
14
  console.warn(`[ZIFT-SHIELD] ๐ŸŒ Outbound Connection: ${address}:${port}`);
15
15
  });
16
16
 
17
- // 2. Wrap Child Process (for shell command execution)
17
+ // 2. Wrap Child Process (for shell command execution) - IMMUTABLE
18
18
  const cp = require('node:child_process');
19
19
  ['exec', 'spawn', 'execSync', 'spawnSync'].forEach(method => {
20
20
  const original = cp[method];
21
- cp[method] = function (...args) {
21
+ if (!original) return;
22
+
23
+ const wrapper = function (...args) {
22
24
  const command = args[0];
23
- const cmdStr = typeof command === 'string' ? command : (args[1] ? args[1].join(' ') : 'unknown');
25
+ const cmdStr = typeof command === 'string' ? command : (Array.isArray(args[1]) ? args[1].join(' ') : String(command));
24
26
  console.warn(`[ZIFT-SHIELD] ๐Ÿš Shell Execution: ${cmdStr}`);
25
27
 
26
- // Heuristic Check: Is it a potential dropper?
27
- if (cmdStr.includes('curl') || cmdStr.includes('wget') || cmdStr.includes('| sh')) {
28
+ if (cmdStr.includes('curl') || cmdStr.includes('wget') || cmdStr.includes('| sh') || cmdStr.includes('| bash')) {
28
29
  console.error(`[ZIFT-SHIELD] โš ๏ธ CRITICAL: Potential Remote Dropper detected in shell execution!`);
29
30
  }
30
31
 
31
32
  return original.apply(this, args);
32
33
  };
34
+
35
+ try {
36
+ Object.defineProperty(cp, method, { value: wrapper, writable: false, configurable: false });
37
+ } catch (e) {
38
+ cp[method] = wrapper; // Fallback
39
+ }
33
40
  });
34
41
 
35
- // 3. Monitor HTTP/HTTPS
42
+ // 3. Monitor HTTP/HTTPS - IMMUTABLE
36
43
  const http = require('node:http');
37
44
  const https = require('node:https');
38
45
  [http, https].forEach(mod => {
39
46
  ['request', 'get'].forEach(method => {
40
47
  const original = mod[method];
41
- mod[method] = function (...args) {
48
+ const wrapper = function (...args) {
42
49
  let url = args[0];
43
50
  if (typeof url === 'object' && url.href) url = url.href;
44
51
  else if (typeof url === 'string') url = url;
@@ -47,8 +54,42 @@ function setupShield() {
47
54
  console.warn(`[ZIFT-SHIELD] ๐Ÿ“ก HTTP Request: ${url}`);
48
55
  return original.apply(this, args);
49
56
  };
57
+
58
+ try {
59
+ Object.defineProperty(mod, method, { value: wrapper, writable: false, configurable: false });
60
+ } catch (e) {
61
+ mod[method] = wrapper;
62
+ }
50
63
  });
51
64
  });
65
+
66
+ // 4. Propagate to Worker Threads
67
+ try {
68
+ const { Worker } = require('node:worker_threads');
69
+ const originalWorker = Worker;
70
+ const shieldPath = __filename;
71
+
72
+ const WorkerWrapper = class extends originalWorker {
73
+ constructor(filename, options = {}) {
74
+ options.workerData = options.workerData || {};
75
+ options.execArgv = options.execArgv || [];
76
+ if (!options.execArgv.includes('-r')) {
77
+ options.execArgv.push('-r', shieldPath);
78
+ }
79
+ super(filename, options);
80
+ }
81
+ };
82
+
83
+ Object.defineProperty(require('node:worker_threads'), 'Worker', { value: WorkerWrapper, writable: false, configurable: false });
84
+ } catch (e) { }
85
+
86
+ // 5. Undici (Modern Fetch) support
87
+ try {
88
+ const undiciChannel = diagnostics.channel('undici:request:create');
89
+ undiciChannel.subscribe(({ request }) => {
90
+ console.warn(`[ZIFT-SHIELD] ๐Ÿš€ Undici/Fetch Request: ${request.origin}${request.path}`);
91
+ });
92
+ } catch (e) { }
52
93
  }
53
94
 
54
95
  // Auto-activate if required via node -r