@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 +9 -8
- package/package.json +1 -1
- package/src/collector.js +29 -2
- package/src/rules/definitions.js +28 -1
- package/src/scanner.js +11 -2
- package/src/shield.js +48 -7
package/README.md
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
# ๐ก๏ธ Zift (
|
|
1
|
+
# ๐ก๏ธ Zift (v4.0.0)
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@7nsane/zift)
|
|
4
4
|
[](https://www.npmjs.com/package/@7nsane/zift)
|
|
5
5
|
[](https://github.com/7nsane/zift)
|
|
6
6
|
|
|
7
|
-
**The
|
|
7
|
+
**The Deeply Hardened Ecosystem Security Engine for JavaScript.**
|
|
8
8
|
|
|
9
|
-
Zift
|
|
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
|
-
## ๐
|
|
11
|
+
## ๐ Key Advancements (v4.0.0)
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
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
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
|
|
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) => {
|
package/src/rules/definitions.js
CHANGED
|
@@ -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
|
-
|
|
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(' ') :
|
|
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
|
-
|
|
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
|
-
|
|
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
|