muaddib-scanner 2.11.89 → 2.11.90

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.89",
3
+ "version": "2.11.90",
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-06-11T08:17:41.614Z",
3
+ "timestamp": "2026-06-11T08:32:51.994Z",
4
4
  "threats": [
5
5
  {
6
6
  "type": "string_mutation_obfuscation",
@@ -217,6 +217,10 @@ async function getPackageMetadata(packageName) {
217
217
  // / pinned-old / vendored versions bypass the cap so we don't mask attacks
218
218
  // captured in static fixtures (e.g. eslint-scope 3.7.2, chalk 5.6.1).
219
219
  latest_version: latestVersion || null,
220
+ // P4 : full dist-tags map ({ latest, next, canary, ... }) so scoring can tell a
221
+ // maintainer-controlled pre-release channel version (inherits partial reputation)
222
+ // from a historical / pinned-old version (no reputation).
223
+ dist_tags: (meta['dist-tags'] && typeof meta['dist-tags'] === 'object') ? meta['dist-tags'] : null,
220
224
  // F3 : list of maintainer email addresses (lowercased, unique) for DNS
221
225
  // MX / RDAP downstream checks. Empty array if no emails published.
222
226
  maintainer_emails: maintainerEmails,
package/src/scoring.js CHANGED
@@ -1874,6 +1874,23 @@ function _hasMaliceSignal(threats) {
1874
1874
  return threats.some(t => t.severity === 'HIGH' || t.severity === 'CRITICAL');
1875
1875
  }
1876
1876
 
1877
+ // P4 (pre-release reputation): known maintainer-controlled pre-release dist-tag names.
1878
+ const _PRERELEASE_TAG_RE = /^(next|canary|nightly|rc|beta|alpha|dev|experimental|preview|snapshot|edge|insider|insiders)$/i;
1879
+
1880
+ // True when the scanned version is published on a NON-latest pre-release dist-tag
1881
+ // (e.g. dist-tags = { latest: "3.19.1", next: "3.19.0-nightly-..." } and we scan the
1882
+ // nightly). Maintainer-controlled, so an attacker cannot put a payload on `next`
1883
+ // without the account — and Track R still floors confirmed malice regardless.
1884
+ function _isPrereleaseChannelVersion(metadata) {
1885
+ const dt = metadata && metadata.dist_tags;
1886
+ const sv = metadata && metadata.scan_version;
1887
+ if (!dt || typeof dt !== 'object' || typeof sv !== 'string') return false;
1888
+ for (const [tag, ver] of Object.entries(dt)) {
1889
+ if (ver === sv && tag !== 'latest' && _PRERELEASE_TAG_RE.test(tag)) return true;
1890
+ }
1891
+ return false;
1892
+ }
1893
+
1877
1894
  function applyReputationFactor(result, metadata) {
1878
1895
  if (!result || !result.summary || !metadata) return null;
1879
1896
  // FPR plan : the reputation factor describes "how trustworthy this package
@@ -1884,12 +1901,21 @@ function applyReputationFactor(result, metadata) {
1884
1901
  // scan version is unknown (CLI scanning a directory without version field),
1885
1902
  // we fail open : skip the factor entirely rather than apply a multiplier
1886
1903
  // we cannot situate in time.
1904
+ // P4 (pre-release reputation): a canary/nightly/rc of an established package is a NEW
1905
+ // (not historical) version on a maintainer-controlled pre-release dist-tag, so it should
1906
+ // inherit the package's reputation — but only PARTIALLY (it is not the verified latest).
1907
+ // Any OTHER non-latest version (historical / pinned-old / vendored) keeps the full skip.
1908
+ let _prereleaseAttenuation = 1.0;
1887
1909
  if (
1888
1910
  typeof metadata.latest_version === 'string' &&
1889
1911
  typeof metadata.scan_version === 'string' &&
1890
1912
  metadata.latest_version !== metadata.scan_version
1891
1913
  ) {
1892
- return null;
1914
+ if (_isPrereleaseChannelVersion(metadata)) {
1915
+ _prereleaseAttenuation = 0.85;
1916
+ } else {
1917
+ return null;
1918
+ }
1893
1919
  }
1894
1920
  if (
1895
1921
  typeof metadata.latest_version === 'string' &&
@@ -1900,9 +1926,14 @@ function applyReputationFactor(result, metadata) {
1900
1926
  // P3 hardening: a valid attestation must NOT earn a trust bonus on a package that
1901
1927
  // also shows malice (TeamPCP attested-malware scenario). Withhold it here, where
1902
1928
  // the threat list is available.
1903
- const factor = _factorFromMetadata(metadata, {
1929
+ let factor = _factorFromMetadata(metadata, {
1904
1930
  allowProvenanceBonus: !_hasMaliceSignal(result.threats)
1905
1931
  });
1932
+ // P4: blend the factor toward neutral (1.0) for pre-release channel versions — they
1933
+ // get most of the reputation suppression but not 100% (they are not the verified latest).
1934
+ if (_prereleaseAttenuation !== 1.0) {
1935
+ factor = 1.0 + (factor - 1.0) * _prereleaseAttenuation;
1936
+ }
1906
1937
  if (factor === 1.0) {
1907
1938
  result.summary.reputationFactor = 1.0;
1908
1939
  return null;