@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7nsane/zift",
3
- "version": "4.3.1",
3
+ "version": "4.4.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
@@ -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 each rule
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 = [];
@@ -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
- const findings = this.engine.evaluate(allFacts, lifecycleFiles);
121
- return this.formatFindings(findings);
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
- results: sorted.map(f => {
239
- let classification = 'Low';
240
- if (f.score >= 90) classification = 'Critical';
241
- else if (f.score >= 70) classification = 'High';
242
- else if (f.score >= 50) classification = 'Medium';
243
-
244
- return {
245
- ...f,
246
- classification,
247
- triggers: f.triggers.map(t => ({
248
- type: t.type,
249
- file: path.relative(this.packageDir, t.file),
250
- line: t.line,
251
- context: t.reason || t.callee || t.variable || t.path || t.url || t.context
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
- console.warn('🛡️ ZIFT SHIELD ACTIVE: Monitoring suspicious runtime activity...');
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
- const ALLOWED_COMMANDS = ['npm install', 'npm audit', 'ls', 'dir', 'whoami', 'node -v'];
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
- const PROTECTED_FILES = ['.env', '.npmrc', 'shadow', 'id_rsa', 'id_ed25519'];
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 protected file: "${pathStr}"`);
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: Protected file "${pathStr}" cannot be read.`);
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
+ }