@7nsane/zift 4.3.1 → 4.4.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/package.json +1 -1
- package/src/collector.js +13 -1
- package/src/engine.js +41 -2
- package/src/rules/definitions.js +11 -1
- package/src/scanner.js +33 -23
- package/src/shield.js +35 -6
- package/zift.json +35 -0
package/package.json
CHANGED
package/src/collector.js
CHANGED
|
@@ -41,12 +41,24 @@ class ASTCollector {
|
|
|
41
41
|
MODULE_TAMPER: [],
|
|
42
42
|
REVERSE_SHELL_BEHAVIOR: [],
|
|
43
43
|
FINGERPRINT_SIGNAL: [],
|
|
44
|
-
PUBLISH_SINK: []
|
|
44
|
+
PUBLISH_SINK: [],
|
|
45
|
+
MANIFEST_MISMATCH: []
|
|
45
46
|
};
|
|
46
47
|
const flows = [];
|
|
47
48
|
const sourceCode = code;
|
|
48
49
|
let envAccessCount = 0;
|
|
49
50
|
|
|
51
|
+
// v6.0 Heuristic De-packer (obfuscator.io)
|
|
52
|
+
const obfuscatorPattern = /var\s+(_0x[a-f0-9]+)\s*=\s*\[.*\];/;
|
|
53
|
+
if (obfuscatorPattern.test(code)) {
|
|
54
|
+
facts.OBFUSCATION.push({
|
|
55
|
+
file: filePath,
|
|
56
|
+
line: 1,
|
|
57
|
+
reason: 'Standard obfuscator.io pattern detected (String Array)',
|
|
58
|
+
heuristic: 'obfuscator-io'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
let ast;
|
|
51
63
|
try {
|
|
52
64
|
ast = acorn.parse(code, { ecmaVersion: 2020, sourceType: 'module', locations: true });
|
package/src/engine.js
CHANGED
|
@@ -5,10 +5,18 @@ class SafetyEngine {
|
|
|
5
5
|
this.results = [];
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
evaluate(packageFacts, lifecycleFiles) {
|
|
8
|
+
evaluate(packageFacts, lifecycleFiles, manifest = null) {
|
|
9
9
|
let findings = [];
|
|
10
10
|
|
|
11
|
-
// Process
|
|
11
|
+
// 1. Process Manifest Violations (if manifest exists)
|
|
12
|
+
if (manifest) {
|
|
13
|
+
const manifestFails = this.validateManifest(packageFacts, manifest);
|
|
14
|
+
if (manifestFails.length > 0) {
|
|
15
|
+
packageFacts.facts.MANIFEST_MISMATCH = manifestFails;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2. Process each rule
|
|
12
20
|
for (const rule of RULES) {
|
|
13
21
|
const match = this.matchRule(rule, packageFacts, lifecycleFiles);
|
|
14
22
|
if (match) {
|
|
@@ -22,6 +30,37 @@ class SafetyEngine {
|
|
|
22
30
|
return findings;
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
validateManifest(packageFacts, manifest) {
|
|
34
|
+
const { facts } = packageFacts;
|
|
35
|
+
const violations = [];
|
|
36
|
+
|
|
37
|
+
// Check Network
|
|
38
|
+
if (manifest.capabilities && manifest.capabilities.network) {
|
|
39
|
+
const networkFacts = facts.NETWORK_SINK || [];
|
|
40
|
+
if (!manifest.capabilities.network.enabled && networkFacts.length > 0) {
|
|
41
|
+
networkFacts.forEach(f => violations.push({ ...f, context: 'UNAUTHORIZED_NETWORK_SINK' }));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check Shell
|
|
46
|
+
if (manifest.capabilities && manifest.capabilities.shell) {
|
|
47
|
+
const shellFacts = facts.SHELL_EXECUTION || [];
|
|
48
|
+
if (!manifest.capabilities.shell.enabled && shellFacts.length > 0) {
|
|
49
|
+
shellFacts.forEach(f => violations.push({ ...f, context: 'UNAUTHORIZED_SHELL_EXECUTION' }));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check Filesystem (Write)
|
|
54
|
+
if (manifest.capabilities && manifest.capabilities.filesystem) {
|
|
55
|
+
const writeFacts = facts.FILE_WRITE_STARTUP || [];
|
|
56
|
+
if (!manifest.capabilities.filesystem.write && writeFacts.length > 0) {
|
|
57
|
+
writeFacts.forEach(f => violations.push({ ...f, context: 'UNAUTHORIZED_FILE_WRITE' }));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return violations;
|
|
62
|
+
}
|
|
63
|
+
|
|
25
64
|
matchRule(rule, packageFacts, lifecycleFiles) {
|
|
26
65
|
const { facts } = packageFacts;
|
|
27
66
|
const triggers = [];
|
package/src/rules/definitions.js
CHANGED
|
@@ -267,13 +267,23 @@ const RULES = [
|
|
|
267
267
|
priority: 1,
|
|
268
268
|
baseScore: 90,
|
|
269
269
|
description: 'Detection of remote payload fetching specifically during package install/lifecycle scripts.'
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: 'ZFT-030',
|
|
273
|
+
alias: 'MANIFEST_VIOLATION',
|
|
274
|
+
name: 'Behavioral Manifest Violation',
|
|
275
|
+
requires: ['MANIFEST_MISMATCH'],
|
|
276
|
+
priority: 1,
|
|
277
|
+
baseScore: 100,
|
|
278
|
+
description: 'Critical detection where a package performs an action NOT AUTHORIZED in its zift.json manifest.'
|
|
270
279
|
}
|
|
271
280
|
];
|
|
272
281
|
|
|
282
|
+
// v4.3.0 update: Added categories and sequence metadata
|
|
273
283
|
const CATEGORIES = {
|
|
274
284
|
SOURCES: ['ENV_READ', 'FILE_READ_SENSITIVE', 'MASS_ENV_ACCESS', 'CREDENTIAL_FILE_ACCESS', 'DISCORD_STORAGE_ACCESS', 'CICD_SECRET_ACCESS'],
|
|
275
285
|
SINKS: ['NETWORK_SINK', 'DNS_SINK', 'RAW_SOCKET_SINK', 'DYNAMIC_EXECUTION', 'SHELL_EXECUTION', 'DYNAMIC_REQUIRE', 'WEBHOOK_SINK', 'WIPER_OPERATION', 'REGISTRY_TAMPER', 'MODULE_TAMPER', 'REVERSE_SHELL_BEHAVIOR', 'PUBLISH_SINK'],
|
|
276
|
-
SIGNALS: ['OBFUSCATION', 'ENCODER_USE', 'REMOTE_FETCH_SIGNAL', 'PIPE_TO_SHELL_SIGNAL', 'NATIVE_BINARY_DETECTED', 'OPAQUE_STRING_SKIP', 'NON_DETERMINISTIC_SINK', 'EVASION_ENVIRONMENT_CHECK', 'WALLET_HOOK', 'FINGERPRINT_SIGNAL'],
|
|
286
|
+
SIGNALS: ['OBFUSCATION', 'ENCODER_USE', 'REMOTE_FETCH_SIGNAL', 'PIPE_TO_SHELL_SIGNAL', 'NATIVE_BINARY_DETECTED', 'OPAQUE_STRING_SKIP', 'NON_DETERMINISTIC_SINK', 'EVASION_ENVIRONMENT_CHECK', 'WALLET_HOOK', 'FINGERPRINT_SIGNAL', 'MANIFEST_MISMATCH'],
|
|
277
287
|
PERSISTENCE: ['FILE_WRITE_STARTUP'],
|
|
278
288
|
CONTEXT: ['LIFECYCLE_CONTEXT']
|
|
279
289
|
};
|
package/src/scanner.js
CHANGED
|
@@ -57,7 +57,8 @@ class PackageScanner {
|
|
|
57
57
|
MODULE_TAMPER: [],
|
|
58
58
|
REVERSE_SHELL_BEHAVIOR: [],
|
|
59
59
|
FINGERPRINT_SIGNAL: [],
|
|
60
|
-
PUBLISH_SINK: []
|
|
60
|
+
PUBLISH_SINK: [],
|
|
61
|
+
MANIFEST_MISMATCH: []
|
|
61
62
|
},
|
|
62
63
|
flows: []
|
|
63
64
|
};
|
|
@@ -117,8 +118,20 @@ class PackageScanner {
|
|
|
117
118
|
// Pass 2: Cross-File Taint Resolution
|
|
118
119
|
this.resolveCrossFileTaint(allFacts);
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
// Load Manifest
|
|
122
|
+
let manifest = null;
|
|
123
|
+
try {
|
|
124
|
+
const manifestPath = path.join(this.packageDir, 'zift.json');
|
|
125
|
+
if (fs.existsSync(manifestPath)) {
|
|
126
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
127
|
+
}
|
|
128
|
+
} catch (e) { }
|
|
129
|
+
|
|
130
|
+
const findings = this.engine.evaluate(allFacts, lifecycleFiles, manifest);
|
|
131
|
+
return {
|
|
132
|
+
results: this.formatFindings(findings),
|
|
133
|
+
lifecycleScripts: this.detectedLifecycleScripts
|
|
134
|
+
};
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
resolveCrossFileTaint(allFacts) {
|
|
@@ -234,26 +247,23 @@ class PackageScanner {
|
|
|
234
247
|
formatFindings(findings) {
|
|
235
248
|
const sorted = findings.sort((a, b) => b.score - a.score);
|
|
236
249
|
|
|
237
|
-
return {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}),
|
|
255
|
-
lifecycleScripts: this.detectedLifecycleScripts
|
|
256
|
-
};
|
|
250
|
+
return sorted.map(f => {
|
|
251
|
+
let classification = 'Low';
|
|
252
|
+
if (f.score >= 90) classification = 'Critical';
|
|
253
|
+
else if (f.score >= 70) classification = 'High';
|
|
254
|
+
else if (f.score >= 50) classification = 'Medium';
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
...f,
|
|
258
|
+
classification,
|
|
259
|
+
triggers: f.triggers.map(t => ({
|
|
260
|
+
type: t.type,
|
|
261
|
+
file: path.relative(this.packageDir, t.file),
|
|
262
|
+
line: t.line,
|
|
263
|
+
context: t.reason || t.callee || t.variable || t.path || t.url || t.context
|
|
264
|
+
}))
|
|
265
|
+
};
|
|
266
|
+
});
|
|
257
267
|
}
|
|
258
268
|
}
|
|
259
269
|
|
package/src/shield.js
CHANGED
|
@@ -6,7 +6,18 @@ const diagnostics = require('node:diagnostics_channel');
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
function setupShield() {
|
|
9
|
-
|
|
9
|
+
let manifest = null;
|
|
10
|
+
try {
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const fs = require('node:fs');
|
|
13
|
+
const manifestPath = path.join(process.cwd(), 'zift.json');
|
|
14
|
+
if (fs.existsSync(manifestPath)) {
|
|
15
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
16
|
+
console.warn(`[ZIFT-SHIELD] 📜 Zero-Trust Manifest Loaded: ${manifest.name}@${manifest.version}`);
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.error('[ZIFT-SHIELD] ⚠️ Error loading manifest:', e.message);
|
|
20
|
+
}
|
|
10
21
|
|
|
11
22
|
// 1. Monitor Network Activity via diagnostics_channel
|
|
12
23
|
const netChannel = diagnostics.channel('net.client.socket.request.start');
|
|
@@ -16,7 +27,15 @@ function setupShield() {
|
|
|
16
27
|
|
|
17
28
|
// 2. Wrap Child Process (for shell command execution) - ACTIVE BLOCKING
|
|
18
29
|
const cp = require('node:child_process');
|
|
19
|
-
|
|
30
|
+
let ALLOWED_COMMANDS = ['npm install', 'npm audit', 'ls', 'dir', 'whoami', 'node -v'];
|
|
31
|
+
|
|
32
|
+
if (manifest && manifest.capabilities && manifest.capabilities.shell) {
|
|
33
|
+
if (manifest.capabilities.shell.enabled === false) {
|
|
34
|
+
ALLOWED_COMMANDS = [];
|
|
35
|
+
} else if (Array.isArray(manifest.capabilities.shell.allowList)) {
|
|
36
|
+
ALLOWED_COMMANDS = manifest.capabilities.shell.allowList;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
20
39
|
|
|
21
40
|
['exec', 'spawn', 'execSync', 'spawnSync'].forEach(method => {
|
|
22
41
|
const original = cp[method];
|
|
@@ -51,7 +70,17 @@ function setupShield() {
|
|
|
51
70
|
|
|
52
71
|
// 2.5 Filesystem Protection
|
|
53
72
|
const fs = require('node:fs');
|
|
54
|
-
|
|
73
|
+
let PROTECTED_FILES = ['.env', '.npmrc', 'shadow', 'id_rsa', 'id_ed25519'];
|
|
74
|
+
let blockAllFiles = false;
|
|
75
|
+
|
|
76
|
+
if (manifest && manifest.capabilities && manifest.capabilities.filesystem) {
|
|
77
|
+
if (manifest.capabilities.filesystem.read === false) {
|
|
78
|
+
blockAllFiles = true;
|
|
79
|
+
} else if (Array.isArray(manifest.capabilities.filesystem.read)) {
|
|
80
|
+
// Remove allowed paths from PROTECTED_FILES if they match exactly
|
|
81
|
+
PROTECTED_FILES = PROTECTED_FILES.filter(f => !manifest.capabilities.filesystem.read.includes(f));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
55
84
|
|
|
56
85
|
const fsMethods = ['readFile', 'readFileSync', 'promises.readFile', 'createReadStream'];
|
|
57
86
|
fsMethods.forEach(methodPath => {
|
|
@@ -69,10 +98,10 @@ function setupShield() {
|
|
|
69
98
|
const pathArg = args[0];
|
|
70
99
|
const pathStr = typeof pathArg === 'string' ? pathArg : (pathArg instanceof Buffer ? pathArg.toString() : String(pathArg));
|
|
71
100
|
|
|
72
|
-
if (PROTECTED_FILES.some(f => pathStr.includes(f))) {
|
|
73
|
-
console.error(`[ZIFT-SHIELD] ❌ BLOCKED: Access to
|
|
101
|
+
if (blockAllFiles || PROTECTED_FILES.some(f => pathStr.includes(f))) {
|
|
102
|
+
console.error(`[ZIFT-SHIELD] ❌ BLOCKED: Access to restricted file: "${pathStr}"`);
|
|
74
103
|
if (process.env.ZIFT_ENFORCE === 'true') {
|
|
75
|
-
throw new Error(`[ZIFT-SHIELD] Access Denied:
|
|
104
|
+
throw new Error(`[ZIFT-SHIELD] Access Denied: File path "${pathStr}" is restricted by Zero-Trust policy.`);
|
|
76
105
|
}
|
|
77
106
|
}
|
|
78
107
|
return original.apply(this, args);
|
package/zift.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@7nsane/zift",
|
|
3
|
+
"version": "4.3.1",
|
|
4
|
+
"capabilities": {
|
|
5
|
+
"network": {
|
|
6
|
+
"allowed": [
|
|
7
|
+
"registry.npmjs.org"
|
|
8
|
+
],
|
|
9
|
+
"description": "Required for remote audits and publishing"
|
|
10
|
+
},
|
|
11
|
+
"filesystem": {
|
|
12
|
+
"read": [
|
|
13
|
+
"."
|
|
14
|
+
],
|
|
15
|
+
"write": [
|
|
16
|
+
".zift.json",
|
|
17
|
+
".ziftignore",
|
|
18
|
+
"package.json"
|
|
19
|
+
],
|
|
20
|
+
"sensitive": false
|
|
21
|
+
},
|
|
22
|
+
"shell": {
|
|
23
|
+
"enabled": true,
|
|
24
|
+
"allowList": [
|
|
25
|
+
"npm",
|
|
26
|
+
"tar",
|
|
27
|
+
"ls",
|
|
28
|
+
"dir",
|
|
29
|
+
"whoami",
|
|
30
|
+
"node -v"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"policy": "strict"
|
|
35
|
+
}
|