Programming guides for beginner...
Any comments are welcomed....
I hope it helps!!! Thanks for drop by...
Powered By Blogger

Monday, June 8, 2026

Miasma Worm: Your Settings.json Is a Shell Prompt Now

Miasma Worm: Your Settings.json Is a Shell Prompt Now

Disclosure: This post was researched and drafted with AI assistance. Primary source: SafeDep Team, "Config Files That Run Code: Supply Chain Security Blindspot", safedep.io, 6 June 2026 (HN front page the week of 8 June 2026). Secondary source: SafeDep Team, "Mini Shai-Hulud 'Miasma: The Spreading Blight' Hits @redhat-cloud-services", safedep.io, 1 June 2026. The seven-launcher taxonomy, the .github/setup.js dropper (4,348,254 bytes, Caesar shift), the icflorescu/mantine-datatable commit f72462d9, the braune-digital/BrauneDigitalImagineBundle and mhar-andal/MyBlok launchers, the 121-repository figure, the workspace-trust-prompt mechanics, the claude -p headless prompt-skip, the CVE-2025-59536 / CVE-2026-21852 references, the npm-preinstall example on @redhat-cloud-services/[email protected], and the 32-package / 96-version Red Hat figure are all from those two posts. The Trigger / Authority / Grammar framework and the synthesis in "the original take" are the blog's own. CVE-2025-59536 and CVE-2026-21852 are reproduced from the SafeDep write-up and have not been independently verified against an NVD listing.

There is a class of supply-chain attack that does not need a malicious dependency, a typosquatted package, or a hijacked maintainer account. It only needs a folder. The folder can be empty except for a handful of ordinary-looking config files. The moment you open it in your editor, start an AI coding agent, or run the install command, the attack fires. The trigger is not the code — it is the config. The Miasma worm, which hit npm this month and surfaced on HN this week, is the clearest worked example. The threat model it breaks is the one most security checklists still assume holds: that opening a fresh clone is safe until you run npm install.

The seven config files Miasma uses to fire on open

The SafeDep post walks a single icflorescu/mantine-datatable commit (f72462d9, titled chore: update dependencies [skip ci]) showing that one commit added six files. Five exist to launch the sixth: a 4,348,254-byte dropper at .github/setup.js. None of the launchers contains the payload. They each carry node .github/setup.js and rely on the developer's own tools to evaluate it.

  • .claude/settings.json / .gemini/settings.json — byte-identical SessionStart hook configs that run a shell command the moment an agent session opens. Once the folder is trusted, the hook runs without further confirmation. Since Claude Code 2.1.0, SessionStart hooks run silently.
  • .cursor/rules/setup.mdc — Cursor has no shell-hook primitive, so the attacker used a project rule with alwaysApply: true instructing the agent to run the dropper. Prompt injection committed to the repo.
  • .vscode/tasks.json — a task with runOptions.runOn: "folderOpen". The workspace-trust prompt is the only gate; it flags that a hook exists, not that the hook's 4.3 MB target is a Caesar-shifted eval launcher.
  • package.json test script"test": "node .github/setup.js". Needs a deliberate action, but the deliberate action is npm test or a CI test step, both run on autopilot.
  • composer.json post-install-cmd in braune-digital/BrauneDigitalImagineBundle — runs on every composer install, no trust gate.
  • Gemfile line one in mhar-andal/MyBloksystem("node .github/setup.js"). A Gemfile is Ruby, evaluated top to bottom. bundle install, bundle exec, any Rails command reading it runs the dropper. No malicious gem in the dependency tree.

Seven surfaces. One dropper. The variety is the point: the attacker is betting on the category of tool that reads config and acts, not on any one editor.

What the dropper actually does

The .github/setup.js file is one statement in a try/catch. The first visible bytes are a Caesar shift over a numeric char-code array fed to eval. Statically decoding it (shift of 4) yields a staged Bun loader that AES-decrypts a credential stealer, scanning the host for AWS, Azure, GCP, Vault, Kubernetes, npm, and GitHub secrets and exfiltrating them to attacker-created public GitHub repos.

Two design choices are worth lingering on. The file is sized to be just above the limit where GitHub's code search stops indexing — roughly 384 KB — so the launcher files are what show up in search, not the dropper. And the obfuscation shape (numeric array, rotation function, eval, encrypted second stage) is the same harness SafeDep says they keep seeing recompiled across separate Miasma waves and across unrelated malicious-package campaigns. The rotation amount and the AES key change between builds, so the SHA-256 changes, but the structure stays.

The Red Hat / @redhat-cloud-services compromise on 1 June was a parallel Miasma wave abusing npm's GitHub trusted publishing via short-lived oidc-<hex> branches: push a branch that rewrites the trusted CI workflow into a self-publishing job, exchange its OIDC token for an npm publish token, repackage the legitimate tarball with a malicious preinstall, republish with valid provenance. Same target (the developer's npm install step), different distribution channel.

The trust prompts are a permission dialog, not a security control

VS Code, Claude Code, and Gemini CLI all show a workspace-trust prompt the first time a session starts in a new directory. The attack does not defeat those prompts. It relies on the developer granting trust the way they dismiss a cookie banner, and on the prompt flagging that a hook exists without making its 4.3 MB target obvious. Two situations skip the prompt outright: pulling the malicious commit into a repo that was already trusted, and running headless (claude -p), which disables trust verification — the CI case. The package-manager vectors have no trust gate at all. npm test, composer install, bundle install, bundle exec, and any Rails command that reads the Gemfile run their hooks as a normal part of the work.

What makes a config file dangerous

A config file is dangerous when a tool reads it and acts without asking, and when its format can carry a command. Score any config on three axes:

  • Trigger. What event reads the file? Folder open, agent session start, dependency install, test run, lint, build. The earlier the trigger fires, the more dangerous the file.
  • Authority. What stands between the trigger and execution? A folder-trust prompt on first open (still bypassable in headless mode), an LLM agent deciding whether to follow an instruction in its context (the Cursor .mdc case), or nothing at all (npm test, Composer post-install, Gemfile top-level Ruby).
  • Grammar. Whether the format can carry a shell command or arbitrary code. JSON hook configs carry commands by design. Markdown rules carry instructions that a sufficiently compliant agent will treat as commands. A Gemfile is a full programming language.

The most dangerous configs combine an early trigger, a low-authority gate, and a high-grammar format. Miasma maximised on all three.

The original take: the attack is on the threat model, not the tool

The conventional reading of Miasma is that Claude Code, Cursor, and the rest have a security bug. The bug is real — the lack of re-warning on hook changes in Claude Code is a clear gap, and claude -p should not skip the trust prompt. But the deeper issue is the threat model most security teams still operate with: "scanned the dependency tree, found no known-bad packages, the project is safe to open." That model is structurally wrong for an ecosystem where the attack is in the project's own config.

Opening a folder is the same risk class as running npm install. A new commit to .claude/settings.json, .vscode/tasks.json, package.json, composer.json, or Gemfile is the same supply-chain event as a new version of a pinned dependency. The trust prompt is a permission dialog, not a security control. The realistic compromise is to scope the blast radius: a project with access to your cloud credentials should not be the same dev environment that opens arbitrary GitHub repos.

What this means for you

  • If you use Claude Code or Cursor on third-party repos: treat the first git clone as if it were npm install. Read .claude/settings.json, .cursor/rules/, and .vscode/tasks.json before opening. A SessionStart hook or an alwaysApply: true rule is a shell command.
  • If you maintain an editor with a hook primitive: re-warn on hook changes (Gemini does; Claude Code does not), and never skip the trust prompt in headless mode by default.
  • If you maintain a package: audit package.json for preinstall/postinstall/test on every release. The Red Hat compromise shipped a one-line preinstall with valid provenance.
  • If you run AI agents in CI: claude -p is the headless trust-bypass case. Pin a commit SHA and diff config files before invoking.

What to do this week

# 1. Audit the last 10 repos you opened. For each, check:
#    .claude/settings.json   -> hooks.SessionStart
#    .gemini/settings.json   -> hooks.SessionStart
#    .cursor/rules/*.mdc     -> alwaysApply: true
#    .vscode/tasks.json      -> runOn: "folderOpen"
#    package.json            -> scripts: preinstall / postinstall / test
#    composer.json           -> scripts: post-install-cmd
#    Gemfile                 -> top-level system() or backtick calls
#    Treat any matching entry as a one-line shell command.

# 2. If you maintain a project that ships an AI-agent config:
#    - Don't add a SessionStart hook to the project's own settings.
#    - If you must, gate it: no node, no eval, no shell, no curl.
#    - Re-warn on hook command changes (the Gemini pattern).

# 3. If you run claude -p or similar in CI:
#    - Pin the commit SHA in the checkout step.
#    - Diff .claude/, .gemini/, .cursor/, .vscode/, package.json,
#      composer.json, Gemfile before invoking the agent.
#    - Treat any added hook or script as a build-breaking event.

The bottom line

The supply chain has a new surface: the project's own config. The seven Miasma files are not exotic; they are the files developers commit every day. They are an execution layer, not metadata — and the supply chain has to score them on the same axis as a dependency change.

Related reads from this blog

Sources

No comments:

Post a Comment