muaddib-scanner 2.11.147 → 2.11.148

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": "muaddib-scanner",
3
- "version": "2.11.147",
3
+ "version": "2.11.148",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "target": "node_modules",
3
- "timestamp": "2026-07-02T09:19:55.784Z",
3
+ "timestamp": "2026-07-02T10:15:47.871Z",
4
4
  "threats": [
5
5
  {
6
6
  "type": "string_mutation_obfuscation",
@@ -195,13 +195,51 @@ function imageExists() {
195
195
 
196
196
  // ── gVisor availability check ──
197
197
 
198
+ let _gvisorAvailableCache; // memoized host capability (runsc does not appear mid-process)
199
+ let _sandboxRuntimeWarned = false; // throttle the runtime warning/refusal log to once per process
200
+
198
201
  function isGvisorAvailable() {
202
+ if (_gvisorAvailableCache !== undefined) return _gvisorAvailableCache;
199
203
  try {
200
204
  const info = execSync('docker info', { encoding: 'utf8', stdio: 'pipe', timeout: 10000 });
201
- return /\brunsc\b/.test(info);
205
+ _gvisorAvailableCache = /\brunsc\b/.test(info);
202
206
  } catch {
203
- return false;
207
+ _gvisorAvailableCache = false;
204
208
  }
209
+ return _gvisorAvailableCache;
210
+ }
211
+
212
+ // Pure runtime-selection decision (no I/O) — unit-testable without Docker, unlike
213
+ // runSandbox() which early-returns before this point when Docker is absent.
214
+ // Inputs: the env object + whether runsc is actually available (isGvisorAvailable()).
215
+ // Returns { useGvisor, refuse, warn, reason }:
216
+ // - useGvisor: run under runsc (stronger isolation)
217
+ // - refuse: caller MUST NOT run — gVisor was REQUIRED but runsc is unavailable
218
+ // (fail CLOSED, opt-in via MUADDIB_REQUIRE_GVISOR). Do not downgrade.
219
+ // - warn: caller should emit a prominent isolation-downgrade WARNING because
220
+ // gVisor was requested, is unavailable, but was not required (fail OPEN).
221
+ // Default (neither env var set) → runc, no warn, no refuse (unchanged behavior).
222
+ // MUADDIB_REQUIRE_GVISOR implies wanting gVisor (you cannot require what you don't want).
223
+ function resolveSandboxRuntime(env, gvisorAvailable) {
224
+ const wantGvisor = env.MUADDIB_SANDBOX_RUNTIME === 'gvisor';
225
+ const requireGvisor = env.MUADDIB_REQUIRE_GVISOR === '1' || env.MUADDIB_REQUIRE_GVISOR === 'true';
226
+ if (!wantGvisor && !requireGvisor) {
227
+ // gVisor not requested. If runsc IS available on this host, still warn: the operator
228
+ // installed gVisor but this scan is running on weaker runc (the exact VPS gap — runsc
229
+ // present, Docker default runtime is runc, env var never set). Detectable in prod.
230
+ if (gvisorAvailable) {
231
+ return { useGvisor: false, refuse: false, warn: true,
232
+ reason: 'sandbox running under runc although gVisor (runsc) IS available on this host — set MUADDIB_SANDBOX_RUNTIME=gvisor to use the stronger isolation' };
233
+ }
234
+ return { useGvisor: false, refuse: false, warn: false, reason: 'runc (default — gVisor neither requested nor available)' };
235
+ }
236
+ if (gvisorAvailable) {
237
+ return { useGvisor: true, refuse: false, warn: false, reason: 'gvisor (runsc)' };
238
+ }
239
+ if (requireGvisor) {
240
+ return { useGvisor: false, refuse: true, warn: false, reason: 'gVisor REQUIRED (MUADDIB_REQUIRE_GVISOR) but runsc is not available' };
241
+ }
242
+ return { useGvisor: false, refuse: false, warn: true, reason: 'gVisor requested but runsc unavailable — falling back to weaker runc isolation' };
205
243
  }
206
244
 
207
245
  // ── Build image (with cache) ──
@@ -725,15 +763,37 @@ async function runSandbox(packageName, options = {}) {
725
763
  return cleanResult;
726
764
  }
727
765
 
728
- // Detect sandbox runtime (gVisor or default Docker/runc)
729
- let useGvisor = process.env.MUADDIB_SANDBOX_RUNTIME === 'gvisor';
730
- if (useGvisor) {
731
- if (isGvisorAvailable()) {
732
- console.log('[SANDBOX] Runtime: gvisor (runsc)');
733
- } else {
734
- console.log('[SANDBOX] Runtime: gvisor requested but runsc not configured in Docker. Falling back to Docker standard.');
735
- useGvisor = false;
766
+ // Detect sandbox runtime (gVisor or default Docker/runc). The decision is a pure,
767
+ // unit-tested function (resolveSandboxRuntime); here we act on it. isGvisorAvailable()
768
+ // is memoized, so this is a per-process `docker info`, not a per-scan one.
769
+ const rt = resolveSandboxRuntime(process.env, isGvisorAvailable());
770
+ const useGvisor = rt.useGvisor;
771
+ if (rt.refuse) {
772
+ // Fail CLOSED: operator set MUADDIB_REQUIRE_GVISOR but runsc is unavailable. Do NOT
773
+ // silently downgrade to weaker runc isolation — return INCONCLUSIVE (score -1) so the
774
+ // package is never cleared on weaker isolation than was demanded. Each blocked scan is
775
+ // ledgered (sandbox_inconclusive); the console warning is throttled to once/process.
776
+ if (!_sandboxRuntimeWarned) {
777
+ _sandboxRuntimeWarned = true;
778
+ console.warn(`[SANDBOX] SECURITY: refusing to run — ${rt.reason}. Install gVisor (scripts/install-gvisor.sh) or unset MUADDIB_REQUIRE_GVISOR to allow runc.`);
736
779
  }
780
+ return {
781
+ score: -1,
782
+ severity: 'INCONCLUSIVE',
783
+ findings: [{ type: 'gvisor_required_unavailable', severity: 'MEDIUM', detail: rt.reason, evidence: rt.reason }],
784
+ raw_report: null,
785
+ suspicious: false,
786
+ inconclusive: true
787
+ };
788
+ }
789
+ if (rt.warn && !_sandboxRuntimeWarned) {
790
+ // Fail OPEN but LOUD (was a plain console.log before, or nothing when the env var was
791
+ // simply unset on a gVisor-capable host): surface the weaker-than-possible isolation
792
+ // once per process so it is detectable in production logs.
793
+ _sandboxRuntimeWarned = true;
794
+ console.warn(`[SANDBOX] WARNING: ${rt.reason}. Set MUADDIB_REQUIRE_GVISOR=1 to fail closed instead of running on runc.`);
795
+ } else if (useGvisor) {
796
+ console.log('[SANDBOX] Runtime: gvisor (runsc)');
737
797
  }
738
798
 
739
799
  // Generate canary tokens for this sandbox session
@@ -1177,4 +1237,4 @@ function displayResults(result) {
1177
1237
  }
1178
1238
  }
1179
1239
 
1180
- module.exports = { buildSandboxImage, runSandbox, runSingleSandbox, buildDockerArgs, generateFakeHostname, scoreFindings, generateNetworkReport, EXFIL_PATTERNS, SAFE_DOMAINS, getSeverity, displayResults, isDockerAvailable, imageExists, isGvisorAvailable, STATIC_CANARY_TOKENS, detectStaticCanaryExfiltration, analyzePreloadLog, TIME_OFFSETS, SAFE_SANDBOX_CMDS, SANDBOX_CONCURRENCY_MAX, acquireSandboxSlot, tryAcquireSandboxSlot, releaseSandboxSlot, resetSandboxLimiter, getSandboxSemaphore, killAllSandboxContainers };
1240
+ module.exports = { buildSandboxImage, runSandbox, runSingleSandbox, buildDockerArgs, generateFakeHostname, scoreFindings, generateNetworkReport, EXFIL_PATTERNS, SAFE_DOMAINS, getSeverity, displayResults, isDockerAvailable, imageExists, isGvisorAvailable, resolveSandboxRuntime, STATIC_CANARY_TOKENS, detectStaticCanaryExfiltration, analyzePreloadLog, TIME_OFFSETS, SAFE_SANDBOX_CMDS, SANDBOX_CONCURRENCY_MAX, acquireSandboxSlot, tryAcquireSandboxSlot, releaseSandboxSlot, resetSandboxLimiter, getSandboxSemaphore, killAllSandboxContainers };