// npm 패키지
@trackunit/iris-app
The `@trackunit/iris-app` package is a plugin for [NX by @nrwl](https://nx.dev/). This plugin adds some helpful generators used to set up a Trackunit Iris App project.
// exfil path
what is read → where it ships- ● npm token
- ● MCP config
- ⤳ identity.iris.trackunit.com(identity.iris.trackunit.com (via hostname var))
- ⤳ identity.stage.iris.trackunit.com(identity.stage.iris.trackunit.com (via hostname var))
- ⤳ identity.dev.iris.trackunit.com(identity.dev.iris.trackunit.com (via hostname var))
→ Worm self-propagation: package reads .npmrc _authToken AND invokes npm publish in install-path code. Shai-Hulud-class shape — no legitimate package re-publishes OTHER packages from the user's machine.
- @2.0.6··AUTO-PUBLISHED·publisher: tu-publisherheuristic 64/100static flags 5llm malicious (0.96) via fast-tracknew-publisher:20dmature-packagehas-source-repopublisher-multi-name-burst:55publisher-version-pump:93reads-npmrcreads-env-varsreads-mcp-configinstall-path-npm-publishdest-via-hostname-var
→ Worm self-propagation: package reads .npmrc _authToken AND invokes npm publish in install-path code. Shai-Hulud-class shape — no legitimate package re-publishes OTHER packages from the user's machine.
// offending code· 3 files flaggedpatterns: 5
--- package/src/generators/create/generator.js (excerpt) --- "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IrisAppGenerator = void 0; const tslib_1 = require("tslib"); const devkit_1 = require("@nx/devkit"); const get_npm_scope_1 = require("@nx/js/src/utils/package-json/get-npm-scope"); const pacote = tslib_1.__importStar(require("pacote")); const path = tslib_1.__importStar(require("path")); function normalizeOptions(tree, options) { const name = (0, devkit_1.names)(options.name).fileName; const projectDirectory = options.directory ? path.join(options.directory, name) : name; const toNxRoot = `../../${projectDirectory.indexOf("/") !== -1 ? "../" : ""}`; const projectName = name.replace(new RegExp("/", "g"), "_"); const npmScope = (0, get_npm_scope_1.getNpmScope)(tree); if (!npmScope) { throw new Error("Could not find npm scope in package.json"); } const projectRoot = `${(0, devkit_1.getWorkspaceLayout)(tree).appsDir}/${projectDirectory}`; return { ...options, projectName, projectRoot, projectDirectory, toNxRoot, npmScope, }; } function addFiles(tree, options, packageJson) { const nodeVersion = packageJson?.engines?.node ?? ">=20.x"; const templateOptions = { ...options, ...(0, devkit_1.names)(options.name), offsetFromRoot: (0, devkit_1.offsetFromRoot)(options.projectRoot), template: "", nodeVersion, }; (0, devkit_1.generateFiles)(tree, path.join(_ --- package/src/generators/ai-agent-sync/generator.js (excerpt) --- "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.aiAgentSyncGenerator = aiAgentSyncGenerator; const tslib_1 = require("tslib"); const devkit_1 = require("@nx/devkit"); const devkit_exports_1 = require("nx/src/devkit-exports"); const pacote = tslib_1.__importStar(require("pacote")); const path = tslib_1.__importStar(require("path")); const semver = tslib_1.__importStar(require("semver")); const link_claude_skills_1 = require("../utils/link-claude-skills"); const IRIS_APP_PACKAGE = "@trackunit/iris-app"; const UPDATE_SCRIPT_NAME = "update:trackunit"; const UPDATE_SCRIPT_CMD = 'npx npm-check-updates "/@trackunit/" -u'; function getStringValue(obj, key) { if (typeof obj === "object" && obj !== null && key in obj) { const value = obj[key]; return typeof value === "string" ? value : undefined; } return undefined; } /** * Gets the installed version of @trackunit/iris-app from workspace package.json */ function getInstalledSdkVersion(tree) { const pkgJson = JSON.parse(tree.read("package.json", "utf-8") ?? "{}"); if (typeof pkgJson !== "object" || pkgJson === null) { return undefined; } const pkg = pkgJson; return getStringValue(pkg.dependencies, IRIS_APP_PACKAGE) ?? getStringValue(pkg.devDependencies, IRIS_APP_PACKAGE); } /** * Fetches the latest version of @trackunit/iris-app from npm */ async function getLatestSdkVersion() { const manifest = await pacote.manifest(IRIS_APP_PACKAGE); --- package/src/executors/utils/authentication.js (excerpt) --- "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAccessToken = getAccessToken; const tslib_1 = require("tslib"); /* eslint-disable no-console */ const open_1 = tslib_1.__importDefault(require("open")); /** * Authenticate and get access token * * @param {AuthSettings} env Settings used for authentication * @returns {*} {Promise<TokenData>} */ async function getAccessToken(env) { const settings = getAuthSettings(env); const clientId = process.env[`TU_CLIENT_ID_${settings.env}`]; const clientSecret = process.env[`TU_CLIENT_SECRET_${settings.env}`]; // use token provided as environment variable if available if (process.env.TU_TOKEN) { return { token_type: "Bearer", id_token: process.env.TU_TOKEN, access_token: process.env.TU_TOKEN, expires_in: 0, scope: "unknown", }; // use client id and secret provided as environment variables to fetch a token directly // This is used for CI/CD pipelines } else if (clientId !== undefined) { if (clientId.trim() === "") { throw new Error(`Missing environment value: TU_CLIENT_ID_${settings.env}`); } if (clientSecret === undefined || clientSecret.trim() === "") { throw new Error(`Missing environment value: TU_CLIENT_SECRET_${settings.env}`); } const tokenParams = new URLSearchParams(); tokenParams.append("client_id" --- dynamic destinations --- → identity.iris.trackunit.com (via hostname-var) → identity.stage.iris.trackunit.com (via hostname-var) → identity.dev.iris.trackunit.com (via hostname-var)
