@automagik/genie 4.260424.13 → 4.260424.14
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": "genie",
|
|
3
|
-
"version": "4.260424.
|
|
3
|
+
"version": "4.260424.14",
|
|
4
4
|
"description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Namastex Labs"
|
package/scripts/sec-scan.cjs
CHANGED
|
@@ -1379,6 +1379,34 @@ function findVersionsInText(text) {
|
|
|
1379
1379
|
return found;
|
|
1380
1380
|
}
|
|
1381
1381
|
|
|
1382
|
+
// Name-version correlated match. A tracked version string (e.g. "1.0.3")
|
|
1383
|
+
// may belong to MANY unrelated packages (tweetnacl@1.0.3, escape-html@1.0.3,
|
|
1384
|
+
// @inquirer/external-editor@1.0.3). findVersionsInText matches the bare
|
|
1385
|
+
// version and produces false positives on every unrelated package sharing
|
|
1386
|
+
// the same number. This variant only returns tuples `name@version` when
|
|
1387
|
+
// BOTH appear correlated in the text:
|
|
1388
|
+
// - Form 1: the literal "name@version" substring (bun.lock, yarn.lock,
|
|
1389
|
+
// pnpm-lock.yaml all use this).
|
|
1390
|
+
// - Form 2: JSON-style '"name" ... "version": "X.Y.Z"' within 500 chars
|
|
1391
|
+
// (package-lock.json, npm manifest).
|
|
1392
|
+
function findTrackedPackageVersionTuples(text) {
|
|
1393
|
+
if (!text) return [];
|
|
1394
|
+
const matches = new Set();
|
|
1395
|
+
for (const pkg of TRACKED_PACKAGES) {
|
|
1396
|
+
const escName = pkg.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1397
|
+
for (const version of pkg.versions) {
|
|
1398
|
+
if (text.includes(`${pkg.name}@${version}`)) {
|
|
1399
|
+
matches.add(`${pkg.name}@${version}`);
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
const escVersion = version.replace(/\./g, '\\.');
|
|
1403
|
+
const pattern = new RegExp(`"${escName}"[^{]{0,50}\\{[^{}]{0,500}"version"\\s*:\\s*"${escVersion}"`, 's');
|
|
1404
|
+
if (pattern.test(text)) matches.add(`${pkg.name}@${version}`);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return [...matches];
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1382
1410
|
function collectTextIndicators(text) {
|
|
1383
1411
|
const indicators = {
|
|
1384
1412
|
versions: findVersionsInText(text),
|
|
@@ -1881,13 +1909,26 @@ function scanNpmCache(homePath, report) {
|
|
|
1881
1909
|
}
|
|
1882
1910
|
}
|
|
1883
1911
|
|
|
1884
|
-
|
|
1912
|
+
// Only flag when the cached npm manifest's dist-tag points at a
|
|
1913
|
+
// compromised version. The manifest ALWAYS lists every historical
|
|
1914
|
+
// version by design; that list alone is not evidence of compromise —
|
|
1915
|
+
// only "latest" or "next" resolving to a bad version means the user's
|
|
1916
|
+
// next `npm install <pkg>` would pull the malware. If a tarball
|
|
1917
|
+
// fetch for a compromised version is separately recorded, that goes
|
|
1918
|
+
// through npmTarballFetches as 'affected' severity regardless.
|
|
1919
|
+
const distTagsCompromised = metadata.distTags
|
|
1920
|
+
? Object.entries(metadata.distTags).filter(([, v]) => TRACKED_VERSION_SET.has(v))
|
|
1921
|
+
: [];
|
|
1922
|
+
if (metadata.observedVersions.length > 0 && distTagsCompromised.length > 0) {
|
|
1923
|
+
metadata.compromisedDistTags = distTagsCompromised.map(([tag, version]) => ({ tag, version }));
|
|
1885
1924
|
report.npmCacheMetadata.push(metadata);
|
|
1886
1925
|
addTimeline(report, {
|
|
1887
1926
|
time: metadata.observedAt || metadata.cacheRecordTime,
|
|
1888
1927
|
category: 'npm-cache-metadata',
|
|
1889
1928
|
severity: 'observed',
|
|
1890
|
-
summary: `npm cache
|
|
1929
|
+
summary: `npm cache dist-tag points at compromised version: ${distTagsCompromised
|
|
1930
|
+
.map(([tag, version]) => `${tag}=${version}`)
|
|
1931
|
+
.join(', ')}`,
|
|
1891
1932
|
path: cacheRoot,
|
|
1892
1933
|
});
|
|
1893
1934
|
}
|
|
@@ -1928,20 +1969,20 @@ function scanNpmCache(homePath, report) {
|
|
|
1928
1969
|
const text = safeReadText(fullPath);
|
|
1929
1970
|
if (!text || !TRACKED_PACKAGES.some(({ name }) => text.includes(name))) continue;
|
|
1930
1971
|
|
|
1931
|
-
|
|
1972
|
+
// Name-version correlated match — the log may contain `1.0.3` because
|
|
1973
|
+
// an unrelated transitive dep (escape-html, tweetnacl) resolved to that
|
|
1974
|
+
// version during the same install. Only flag when the tracked name
|
|
1975
|
+
// and a tracked version appear as a correlated tuple.
|
|
1976
|
+
const versionTuples = findTrackedPackageVersionTuples(text);
|
|
1932
1977
|
const indicators = collectTextIndicators(text);
|
|
1933
|
-
|
|
1934
|
-
// or IOC network pattern. Name-only install/exec entries happen every
|
|
1935
|
-
// time anyone runs `npx @automagik/genie ...` and are NOT evidence of
|
|
1936
|
-
// compromise — they just record that the package was interacted with.
|
|
1937
|
-
const hardEvidence = versions.length > 0 || indicators.iocMatches.length > 0;
|
|
1978
|
+
const hardEvidence = versionTuples.length > 0 || indicators.iocMatches.length > 0;
|
|
1938
1979
|
if (!hardEvidence) continue;
|
|
1939
1980
|
|
|
1940
1981
|
report.npmLogHits.push({
|
|
1941
1982
|
home: homePath,
|
|
1942
1983
|
path: fullPath,
|
|
1943
1984
|
modifiedAt: isoTime(safeStat(fullPath)?.mtimeMs),
|
|
1944
|
-
versions,
|
|
1985
|
+
versions: versionTuples,
|
|
1945
1986
|
installCommands: indicators.installCommands,
|
|
1946
1987
|
executionCommands: indicators.executionCommands,
|
|
1947
1988
|
iocMatches: indicators.iocMatches,
|
|
@@ -2231,21 +2272,23 @@ function scanProjectRoots(roots, report, runtime) {
|
|
|
2231
2272
|
|
|
2232
2273
|
const text = safeReadText(lockfilePath);
|
|
2233
2274
|
if (!text) return;
|
|
2234
|
-
if (!TRACKED_PACKAGES.some(({ name }) => text.includes(name))) return;
|
|
2235
2275
|
|
|
2236
|
-
|
|
2237
|
-
|
|
2276
|
+
// Correlated tuples only — raw version match produced false positives
|
|
2277
|
+
// whenever an unrelated dep shared a version string with a tracked
|
|
2278
|
+
// compromised version (tweetnacl@1.0.3 vs @openwebconcept/design-tokens@1.0.3).
|
|
2279
|
+
const tuples = findTrackedPackageVersionTuples(text);
|
|
2280
|
+
if (tuples.length === 0) return;
|
|
2238
2281
|
|
|
2239
2282
|
report.lockfileFindings.push({
|
|
2240
2283
|
path: lockfilePath,
|
|
2241
2284
|
modifiedAt: isoTime(stat.mtimeMs),
|
|
2242
|
-
versions,
|
|
2285
|
+
versions: tuples,
|
|
2243
2286
|
});
|
|
2244
2287
|
addTimeline(report, {
|
|
2245
2288
|
time: isoTime(stat.mtimeMs),
|
|
2246
2289
|
category: 'lockfile',
|
|
2247
2290
|
severity: 'observed',
|
|
2248
|
-
summary: `lockfile references compromised
|
|
2291
|
+
summary: `lockfile references compromised ${tuples.join(', ')}`,
|
|
2249
2292
|
path: lockfilePath,
|
|
2250
2293
|
});
|
|
2251
2294
|
},
|
|
@@ -3023,9 +3066,14 @@ function summarize(report) {
|
|
|
3023
3066
|
if (installHistoryEvidence > 0) {
|
|
3024
3067
|
affectedReasons.push('shell history shows package installation commands');
|
|
3025
3068
|
}
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3069
|
+
// Weak temp findings (name-only matches on /tmp text files like Claude
|
|
3070
|
+
// session logs and bun task-output dumps) are NOT affected-grade evidence
|
|
3071
|
+
// — they're just text files that happen to contain the string "pgserve"
|
|
3072
|
+
// or "@automagik/genie". They still appear in the report for transparency
|
|
3073
|
+
// but must not elevate status from OBSERVED ONLY to LIKELY AFFECTED.
|
|
3074
|
+
// Only `strongTempEvidence` (IOC string, malware hash, env-compat name)
|
|
3075
|
+
// pushes a compromise/affected reason, via the `compromiseReasons` branch
|
|
3076
|
+
// above.
|
|
3029
3077
|
|
|
3030
3078
|
const likelyCompromised = compromiseReasons.length > 0;
|
|
3031
3079
|
const likelyAffected =
|