@7nsane/zift 2.0.0 → 2.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.
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/collector.js +23 -3
- package/src/engine.js +20 -0
- package/src/rules/definitions.js +51 -1
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# 🛡️ Zift (v2.0.0)
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@7nsane/zift)
|
|
4
|
+
[](https://www.npmjs.com/package/@7nsane/zift)
|
|
5
|
+
[](https://github.com/7nsane/zift)
|
|
6
|
+
|
|
3
7
|
**The Deterministic Pre-install Security Gate for JavaScript Projects.** By using deterministic AST analysis and lightweight variable propagation, Zift identifies potential credential exfiltration, malicious persistence, and obfuscated execution with extreme precision.
|
|
4
8
|
|
|
5
9
|
## Installation
|
package/package.json
CHANGED
package/src/collector.js
CHANGED
|
@@ -100,6 +100,14 @@ class ASTCollector {
|
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
if (this.isStartupFileWrite(calleeCode, node, sourceCode)) {
|
|
104
|
+
facts.FILE_WRITE_STARTUP.push({
|
|
105
|
+
file: filePath,
|
|
106
|
+
line: node.loc.start.line,
|
|
107
|
+
path: node.arguments[0] ? sourceCode.substring(node.arguments[0].start, node.arguments[0].end) : 'unknown'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
103
111
|
node.arguments.forEach((arg, index) => {
|
|
104
112
|
const argCode = sourceCode.substring(arg.start, arg.end);
|
|
105
113
|
// Improved check: Does the expression contain any variable we know is tainted?
|
|
@@ -186,7 +194,7 @@ class ASTCollector {
|
|
|
186
194
|
isNetworkSink(calleeCode) {
|
|
187
195
|
const methodSinks = [
|
|
188
196
|
'http.request', 'https.request', 'http.get', 'https.get',
|
|
189
|
-
'net.connect', 'dns.lookup', 'dns.resolve', 'dns.resolve4', 'dns.resolve6',
|
|
197
|
+
'net.connect', 'net.createConnection', 'dns.lookup', 'dns.resolve', 'dns.resolve4', 'dns.resolve6',
|
|
190
198
|
'fetch', 'axios', 'request'
|
|
191
199
|
];
|
|
192
200
|
// Improved matching for require('https').get patterns
|
|
@@ -210,7 +218,7 @@ class ASTCollector {
|
|
|
210
218
|
}
|
|
211
219
|
|
|
212
220
|
isEncoder(calleeCode) {
|
|
213
|
-
const encoders = ['Buffer.from', 'btoa', 'atob'];
|
|
221
|
+
const encoders = ['Buffer.from', 'btoa', 'atob', 'zlib.deflate', 'zlib.gzip', 'crypto.createCipheriv'];
|
|
214
222
|
return encoders.some(enc => calleeCode === enc || calleeCode.endsWith('.' + enc));
|
|
215
223
|
}
|
|
216
224
|
|
|
@@ -235,12 +243,24 @@ class ASTCollector {
|
|
|
235
243
|
|
|
236
244
|
if (node.arguments.length > 0 && node.arguments[0].type === 'Literal') {
|
|
237
245
|
const pathValue = String(node.arguments[0].value);
|
|
238
|
-
const sensitive = ['.ssh', '.env', 'shadow', 'passwd', 'credentials', 'token'];
|
|
246
|
+
const sensitive = ['.ssh', '.env', 'shadow', 'passwd', 'credentials', 'token', '_netrc', 'aws_access_key'];
|
|
239
247
|
return sensitive.some((s) => pathValue.toLowerCase().includes(s));
|
|
240
248
|
}
|
|
241
249
|
return false;
|
|
242
250
|
}
|
|
243
251
|
|
|
252
|
+
isStartupFileWrite(calleeCode, node, sourceCode) {
|
|
253
|
+
if (!calleeCode.includes('fs.writeFile') && !calleeCode.includes('fs.writeFileSync') &&
|
|
254
|
+
!calleeCode.includes('fs.appendFile')) return false;
|
|
255
|
+
|
|
256
|
+
if (node.arguments.length > 0 && node.arguments[0].type === 'Literal') {
|
|
257
|
+
const pathValue = String(node.arguments[0].value);
|
|
258
|
+
const startup = ['package.json', '.npmrc', '.bashrc', '.zshrc', 'crontab', 'init.d', 'systemd'];
|
|
259
|
+
return startup.some((s) => pathValue.toLowerCase().includes(s));
|
|
260
|
+
}
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
244
264
|
getSourceCode(node) {
|
|
245
265
|
return this.sourceCode.substring(node.start, node.end);
|
|
246
266
|
}
|
package/src/engine.js
CHANGED
|
@@ -37,6 +37,26 @@ class SafetyEngine {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (matchedFacts.length === 0) return null; // Rule not matched
|
|
40
|
+
|
|
41
|
+
// Specialist Rule: DNS Exfiltration (ZFT-007) requires a DNS-specific sink
|
|
42
|
+
if (rule.alias === 'DNS_EXFILTRATION' && req === 'NETWORK_SINK') {
|
|
43
|
+
matchedFacts = matchedFacts.filter(f => f.callee && f.callee.includes('dns'));
|
|
44
|
+
if (matchedFacts.length === 0) return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Specialist Rule: Raw Socket Tunnel (ZFT-011) requires net.connect or similar
|
|
48
|
+
if (rule.alias === 'RAW_SOCKET_TUNNEL' && req === 'NETWORK_SINK') {
|
|
49
|
+
matchedFacts = matchedFacts.filter(f => f.callee && (f.callee.includes('net.connect') || f.callee.includes('net.createConnection')));
|
|
50
|
+
if (matchedFacts.length === 0) return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Specialist Rule: Startup Mod (ZFT-012) requires specific file paths
|
|
54
|
+
if (rule.alias === 'STARTUP_SCRIPT_MOD' && req === 'FILE_WRITE_STARTUP') {
|
|
55
|
+
const startupFiles = ['package.json', '.npmrc', '.bashrc', '.zshrc'];
|
|
56
|
+
matchedFacts = matchedFacts.filter(f => f.path && startupFiles.some(s => f.path.includes(s)));
|
|
57
|
+
if (matchedFacts.length === 0) return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
40
60
|
triggers.push(...matchedFacts.map(f => ({ ...f, type: req })));
|
|
41
61
|
}
|
|
42
62
|
|
package/src/rules/definitions.js
CHANGED
|
@@ -45,9 +45,59 @@ const RULES = [
|
|
|
45
45
|
id: 'ZFT-006',
|
|
46
46
|
alias: 'DYNAMIC_REQUIRE_DEPENDENCY',
|
|
47
47
|
name: 'Dynamic Require Dependency',
|
|
48
|
-
requires: ['DYNAMIC_EXECUTION'],
|
|
48
|
+
requires: ['DYNAMIC_EXECUTION'],
|
|
49
49
|
baseScore: 30,
|
|
50
50
|
description: 'Detection of dynamic require calls where the dependency name is a variable.'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'ZFT-007',
|
|
54
|
+
alias: 'DNS_EXFILTRATION',
|
|
55
|
+
name: 'DNS-Based Exfiltration',
|
|
56
|
+
requires: ['ENV_READ', 'NETWORK_SINK'], // Engine will check for dns.resolve in callee
|
|
57
|
+
baseScore: 45,
|
|
58
|
+
description: 'Stealthy environment variable exfiltration via DNS lookups.'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'ZFT-008',
|
|
62
|
+
alias: 'SUSPICIOUS_COLLECTION',
|
|
63
|
+
name: 'Suspicious Information Collection',
|
|
64
|
+
requires: ['ENV_READ'],
|
|
65
|
+
optional: ['FILE_READ_SENSITIVE'],
|
|
66
|
+
baseScore: 20,
|
|
67
|
+
description: 'Massive environment or file reading without immediate network activity (potential harvesting).'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'ZFT-009',
|
|
71
|
+
alias: 'REMOTE_DROPPER_PATTERN',
|
|
72
|
+
name: 'Remote Script Dropper',
|
|
73
|
+
requires: ['SHELL_EXECUTION'],
|
|
74
|
+
optional: ['OBFUSCATION'],
|
|
75
|
+
baseScore: 55,
|
|
76
|
+
description: 'Detection of remote script download and execution (curl | sh) patterns.'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'ZFT-010',
|
|
80
|
+
alias: 'ENCRYPTED_EXFILTRATION',
|
|
81
|
+
name: 'Encrypted Data Exfiltration',
|
|
82
|
+
requires: ['ENCODER_USE', 'NETWORK_SINK'],
|
|
83
|
+
baseScore: 50,
|
|
84
|
+
description: 'Data being encoded/encrypted before being sent over the network.'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'ZFT-011',
|
|
88
|
+
alias: 'RAW_SOCKET_TUNNEL',
|
|
89
|
+
name: 'Raw Socket Tunneling',
|
|
90
|
+
requires: ['NETWORK_SINK'], // Engine will check for net.connect/net.createConnection
|
|
91
|
+
baseScore: 45,
|
|
92
|
+
description: 'Use of raw network sockets instead of http/dns, often used for reverse shells.'
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'ZFT-012',
|
|
96
|
+
alias: 'STARTUP_SCRIPT_MOD',
|
|
97
|
+
name: 'Startup Script Modification',
|
|
98
|
+
requires: ['FILE_WRITE_STARTUP'], // Will check for package.json or .npmrc
|
|
99
|
+
baseScore: 60,
|
|
100
|
+
description: 'Detection of attempts to modify package.json scripts or npm configuration.'
|
|
51
101
|
}
|
|
52
102
|
];
|
|
53
103
|
|