registry  /  nori-skillsets  /  0.28.0

nori-skillsets@0.28.0

Nori Skillsets plugin for Claude Code

AI Security Review

scanned 3h ago · by lpm-firewall-ai

LPM treats this as warn-only first-party agent extension lifecycle risk. Install-time behavior mutates Claude Code settings, but only to remove stale package-owned hooks. User-invoked install can configure package-owned Claude hooks and statusline, which is agent extension lifecycle risk rather than confirmed malware.

Static reason
High-risk behavior combination matched malicious policy.
Trigger
npm postinstall; explicit nori-skillsets install/CLI commands
Impact
May remove obsolete nori-skillsets hook entries or configure Nori hooks/statusline under ~/.claude
Mechanism
scoped Claude Code settings cleanup and first-party agent extension setup
Rationale
This should not be publish-blocked because the postinstall mutation is scoped cleanup of package-owned stale hooks, with no observed exfiltration, remote payload execution, or broad unconsented control hijack. It still merits a warning because install-time/user-command agent configuration touches Claude Code control surfaces.
Evidence
package.jsonbuild/src/cli/features/claude-code/hooks/cleanupLegacyHooks.jsbuild/src/cli/features/claude-code/hooks/loader.jsbuild/src/cli/features/claude-code/statusline/loader.jsbuild/src/cli/installTracking.jsbuild/src/providers/firebase.js~/.claude/settings.json~/.claude/nori-statusline.sh~/.nori/profiles/.nori-install.json~/.nori-install-in-progress
Network endpoints4
noriskillsets.devnoriskillsets.dev/api/analytics/tracksecuretoken.googleapis.com/v1/tokendemo.tilework.tech

Decision evidence

public snapshot
AI called this Suspicious at 82.0% confidence as Dangerous Capability with medium false-positive risk.
Evidence for warning
  • package.json defines postinstall running build/src/cli/features/claude-code/hooks/cleanupLegacyHooks.js
  • cleanupLegacyHooks.js auto-executes and rewrites ~/.claude/settings.json if stale nori-skillsets hooks are present
  • hooks/loader.js can overwrite settings.hooks with package Claude Code hooks during user-run install
  • statusline/loader.js can copy nori-statusline.sh into ~/.claude and set settings.statusLine during user-run install
  • installTracking.js sends opt-out analytics to noriskillsets.dev on CLI launch/install flows
Evidence against
  • Postinstall cleanup only removes commands containing nori-skillsets and fixed removed hook filenames
  • No credential harvesting or arbitrary file exfiltration found in inspected lifecycle script
  • Network endpoints are package-aligned registry/auth/analytics services
  • child_process findings were in tests or explicit watch command daemon setup, not install-time payload execution
  • Firebase apiKey appears to be standard public client config, not a private secret
Behavioral surface
Source
ChildProcessCryptoDynamicRequireEnvironmentVarsFilesystemNetworkShell
Supply chain
HighEntropyStringsUrlStrings
ManifestNo manifest risk signals triggered.
scanned 282 file(s), 4.33 MB of source, external domains: accounts.google.com, acme.noriskillsets.dev, central.nori.ai, claude.com, custom.example.com, custom.registry, custom.registry.com, demo.tilework.tech, dev.noriskillsets.dev, example.com, example.tilework.tech, foo.noriskillsets.dev, github.com, gitlab.com, json.schemastore.org, lantern.noriskillsets.dev, mcp.linear.app, mcp.stripe.com, my-company.nori-registry.ai, my-company.noriskillsets.dev, my-company.tilework.tech, my-org.nori-registry.ai, mycompany.tilework.tech, myorg.nori-registry.ai, myorg.noriskillsets.dev, myorg.tilework.tech, new.example.com, noriagentic.com, noriskillsets.dev, oauth2.googleapis.com, old.example.com, old.registry.com, org.example.com, other.registry.com, otherorg.nori-registry.ai, otherorg.tilework.tech, pangram.noriskillsets.dev, private-registry.example.com, private.registry.com, project.nori.ai, public.registry.com, public.tilework.tech, registrar.tilework.tech, registry.npmjs.org, registry1.example.com, registry2.example.com, securetoken.googleapis.com, someorg.tilework.tech, test.nori.ai, tilework.tilework.tech

Source & flagged code

38 flagged · loading source
package.jsonView file
scripts.postinstall = node build/src/cli/features/claude-code/hooks/cleanupLegacyHooks.js 2>/dev/null || true
High
Install Time Lifecycle Scripts

Package defines install-time lifecycle scripts.

package.jsonView on unpkg
scripts.postinstall = node build/src/cli/features/claude-code/hooks/cleanupLegacyHooks.js 2>/dev/null || true
Medium
Ambiguous Install Lifecycle Script

Install-time lifecycle script is not statically allowlisted and needs review.

package.jsonView on unpkg
build/src/providers/firebase.jsView file
4patternName = google_api_key severity = high line = 4 matchedText = apiKey: ...fk",
High
High Secret

Package contains a high-severity secret pattern.

build/src/providers/firebase.jsView on unpkg · L4
4patternName = google_api_key severity = high line = 4 matchedText = apiKey: ...fk",
High
Secret Pattern

Google API key in build/src/providers/firebase.js

build/src/providers/firebase.jsView on unpkg · L4
build/src/cli/features/claude-code/statusline/loader.test.jsView file
47// Clean up temp directory and session tracking files scoped to this test's tempDir L48: const { execSync } = await import("child_process"); L49: try {
High
Child Process

Package source references child process execution.

build/src/cli/features/claude-code/statusline/loader.test.jsView on unpkg · L47
280// Execute the statusline script with mock input L281: const { execSync } = await import("child_process"); L282: const mockInput = JSON.stringify({ ... L295: // Verify output contains the single promotional tip L296: expect(output).toContain("npm install -g nori-skillsets"); L297: // Verify it does NOT have the old "Nori Tip:" prefix
High
Runtime Package Install

Package source invokes a package manager install command at runtime.

build/src/cli/features/claude-code/statusline/loader.test.jsView on unpkg · L280
build/src/cli/features/claude-code/hooks/config/context-usage-warning.jsView file
1#!/usr/bin/env node L2: import { createRequire } from 'module'; const require = createRequire(import.meta.url); L3: var __create = Object.create;
Medium
Dynamic Require

Package source references dynamic require/import behavior.

build/src/cli/features/claude-code/hooks/config/context-usage-warning.jsView on unpkg · L1
build/src/cli/commands/watch/watch.jsView file
6*/ L7: import { spawn } from "child_process"; L8: import { createHash } from "crypto"; ... L92: try { L93: await logStream.write(logMessage); L94: } ... L99: else { L100: process.stdout.write(logMessage); L101: } ... L194: // Extract sessionId from content as string L195: const contentStr = content.toString("utf-8"); L196: const regex = /"sessionId"\s*:\s*"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"/;
Low
Weak Crypto

Package source references weak cryptographic algorithms.

build/src/cli/commands/watch/watch.jsView on unpkg · L6
build/src/cli/features/claude-code/hooks/cleanupLegacyHooks.jsView file
3Install-time AI-agent control hijack evidence: L3: * L4: * Removes stale hook entries from ~/.claude/settings.json that reference L5: * nori-skillsets scripts that no longer exist in the package. ... L36: /** L37: * Remove stale hook entries from ~/.claude/settings.json. L38: * Only removes hooks that reference nori-skillsets AND match a known removed filename. ... L41: export const cleanupLegacyHooks = async () => { L42: const settingsPath = path.join(getHomeDir(), ".claude", "settings.json"); L43: let content; ... L95: if (modified) { L96: await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2)); L97: } Payload evidence from [redacted].test.js: L21: transport: "http", L22: url: "https://mcp.stripe.com", L23: auth: { type: "bearer", tokenEnv: "STRIPE_API_KEY" }, ... L36: }); L37: const parsed = JSON.parse(out); L38: expect(parsed.mcpServers.github.command).toBe("npx");
Critical
Ai Agent Control Hijack

Install-time source drops package-supplied AI-agent/MCP control files or instructions.

build/src/cli/features/claude-code/hooks/cleanupLegacyHooks.jsView on unpkg · L3
build/src/cli/features/claude-code/statusline/config/nori-statusline.shView file
path = build/src/cli/features/claude-code/statusline/config/nori-statusline.sh kind = build_helper sizeBytes = 9298 magicHex = [redacted]
Medium
Ships Build Helper

Package ships non-JavaScript build or shell helper files.

build/src/cli/features/claude-code/statusline/config/nori-statusline.shView on unpkg
build/src/cli/features/configLoader.test.jsView file
179patternName = generic_password severity = medium line = 179 matchedText = password...ss",
Medium
Secret Pattern

Hardcoded password in build/src/cli/features/configLoader.test.js

build/src/cli/features/configLoader.test.jsView on unpkg · L179
264patternName = generic_password severity = medium line = 264 matchedText = password...ss",
Medium
Secret Pattern

Hardcoded password in build/src/cli/features/configLoader.test.js

build/src/cli/features/configLoader.test.jsView on unpkg · L264
build/src/cli/config.test.jsView file
71patternName = generic_password severity = medium line = 71 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/config.test.js

build/src/cli/config.test.jsView on unpkg · L71
79patternName = generic_password severity = medium line = 79 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/config.test.js

build/src/cli/config.test.jsView on unpkg · L79
91patternName = generic_password severity = medium line = 91 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/config.test.js

build/src/cli/config.test.jsView on unpkg · L91
98patternName = generic_password severity = medium line = 98 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/config.test.js

build/src/cli/config.test.jsView on unpkg · L98
185patternName = generic_password severity = medium line = 185 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/config.test.js

build/src/cli/config.test.jsView on unpkg · L185
323patternName = generic_password severity = medium line = 323 matchedText = password...ed",
Medium
Secret Pattern

Hardcoded password in build/src/cli/config.test.js

build/src/cli/config.test.jsView on unpkg · L323
350patternName = generic_password severity = medium line = 350 matchedText = password...rd",
Medium
Secret Pattern

Hardcoded password in build/src/cli/config.test.js

build/src/cli/config.test.jsView on unpkg · L350
625patternName = generic_password severity = medium line = 625 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/config.test.js

build/src/cli/config.test.jsView on unpkg · L625
build/src/cli/prompts/flows/login.test.jsView file
58patternName = generic_password severity = medium line = 58 matchedText = password...rd",
Medium
Secret Pattern

Hardcoded password in build/src/cli/prompts/flows/login.test.js

build/src/cli/prompts/flows/login.test.jsView on unpkg · L58
build/src/cli/commands/login/login.test.jsView file
125patternName = generic_password severity = medium line = 125 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/commands/login/login.test.js

build/src/cli/commands/login/login.test.jsView on unpkg · L125
189patternName = generic_password severity = medium line = 189 matchedText = password...ss",
Medium
Secret Pattern

Hardcoded password in build/src/cli/commands/login/login.test.js

build/src/cli/commands/login/login.test.jsView on unpkg · L189
211patternName = generic_password severity = medium line = 211 matchedText = password...rd",
Medium
Secret Pattern

Hardcoded password in build/src/cli/commands/login/login.test.js

build/src/cli/commands/login/login.test.jsView on unpkg · L211
250patternName = generic_password severity = medium line = 250 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/commands/login/login.test.js

build/src/cli/commands/login/login.test.jsView on unpkg · L250
300patternName = generic_password severity = medium line = 300 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/commands/login/login.test.js

build/src/cli/commands/login/login.test.jsView on unpkg · L300
345patternName = generic_password severity = medium line = 345 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/commands/login/login.test.js

build/src/cli/commands/login/login.test.jsView on unpkg · L345
388patternName = generic_password severity = medium line = 388 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/commands/login/login.test.js

build/src/cli/commands/login/login.test.jsView on unpkg · L388
449patternName = generic_password severity = medium line = 449 matchedText = password...23",
Medium
Secret Pattern

Hardcoded password in build/src/cli/commands/login/login.test.js

build/src/cli/commands/login/login.test.jsView on unpkg · L449
build/src/api/refreshToken.jsView file
14patternName = google_api_key severity = high line = 14 matchedText = const FI...fk";
High
Secret Pattern

Google API key in build/src/api/refreshToken.js

build/src/api/refreshToken.jsView on unpkg · L14
build/src/api/base.test.jsView file
38patternName = generic_password severity = medium line = 38 matchedText = password...ss",
Medium
Secret Pattern

Hardcoded password in build/src/api/base.test.js

build/src/api/base.test.jsView on unpkg · L38
56patternName = generic_password severity = medium line = 56 matchedText = password...ss",
Medium
Secret Pattern

Hardcoded password in build/src/api/base.test.js

build/src/api/base.test.jsView on unpkg · L56
98patternName = generic_password severity = medium line = 98 matchedText = password...ss",
Medium
Secret Pattern

Hardcoded password in build/src/api/base.test.js

build/src/api/base.test.jsView on unpkg · L98
105patternName = generic_password severity = medium line = 105 matchedText = password...ss",
Medium
Secret Pattern

Hardcoded password in build/src/api/base.test.js

build/src/api/base.test.jsView on unpkg · L105
170patternName = generic_password severity = medium line = 170 matchedText = password...rd",
Medium
Secret Pattern

Hardcoded password in build/src/api/base.test.js

build/src/api/base.test.jsView on unpkg · L170
182patternName = generic_password severity = medium line = 182 matchedText = password...rd",
Medium
Secret Pattern

Hardcoded password in build/src/api/base.test.js

build/src/api/base.test.jsView on unpkg · L182
226patternName = generic_password severity = medium line = 226 matchedText = password...rd",
Medium
Secret Pattern

Hardcoded password in build/src/api/base.test.js

build/src/api/base.test.jsView on unpkg · L226
240patternName = generic_password severity = medium line = 240 matchedText = password...rd",
Medium
Secret Pattern

Hardcoded password in build/src/api/base.test.js

build/src/api/base.test.jsView on unpkg · L240

Findings

1 Critical7 High33 Medium5 Low
CriticalAi Agent Control Hijackbuild/src/cli/features/claude-code/hooks/cleanupLegacyHooks.js
HighInstall Time Lifecycle Scriptspackage.json
HighHigh Secretbuild/src/providers/firebase.js
HighChild Processbuild/src/cli/features/claude-code/statusline/loader.test.js
HighShell
HighRuntime Package Installbuild/src/cli/features/claude-code/statusline/loader.test.js
HighSecret Patternbuild/src/providers/firebase.js
HighSecret Patternbuild/src/api/refreshToken.js
MediumAmbiguous Install Lifecycle Scriptpackage.json
MediumDynamic Requirebuild/src/cli/features/claude-code/hooks/config/context-usage-warning.js
MediumNetwork
MediumEnvironment Vars
MediumShips Build Helperbuild/src/cli/features/claude-code/statusline/config/nori-statusline.sh
MediumStructural Risk Force Deep Review
MediumSecret Patternbuild/src/cli/features/configLoader.test.js
MediumSecret Patternbuild/src/cli/features/configLoader.test.js
MediumSecret Patternbuild/src/cli/config.test.js
MediumSecret Patternbuild/src/cli/config.test.js
MediumSecret Patternbuild/src/cli/config.test.js
MediumSecret Patternbuild/src/cli/config.test.js
MediumSecret Patternbuild/src/cli/config.test.js
MediumSecret Patternbuild/src/cli/config.test.js
MediumSecret Patternbuild/src/cli/config.test.js
MediumSecret Patternbuild/src/cli/config.test.js
MediumSecret Patternbuild/src/cli/prompts/flows/login.test.js
MediumSecret Patternbuild/src/cli/commands/login/login.test.js
MediumSecret Patternbuild/src/cli/commands/login/login.test.js
MediumSecret Patternbuild/src/cli/commands/login/login.test.js
MediumSecret Patternbuild/src/cli/commands/login/login.test.js
MediumSecret Patternbuild/src/cli/commands/login/login.test.js
MediumSecret Patternbuild/src/cli/commands/login/login.test.js
MediumSecret Patternbuild/src/cli/commands/login/login.test.js
MediumSecret Patternbuild/src/cli/commands/login/login.test.js
MediumSecret Patternbuild/src/api/base.test.js
MediumSecret Patternbuild/src/api/base.test.js
MediumSecret Patternbuild/src/api/base.test.js
MediumSecret Patternbuild/src/api/base.test.js
MediumSecret Patternbuild/src/api/base.test.js
MediumSecret Patternbuild/src/api/base.test.js
MediumSecret Patternbuild/src/api/base.test.js
MediumSecret Patternbuild/src/api/base.test.js
LowScripts Present
LowWeak Cryptobuild/src/cli/commands/watch/watch.js
LowFilesystem
LowHigh Entropy Strings
LowUrl Strings