Skip to content
Open-Source Registry

npm / @atedone/workspace-context / 0.0.23

@atedone/workspace-context 0.0.23

bitbison: flagged, behavior flagged, dependencies clean, posture not checked, pipeline clean, 1 of 1 jobs captured RiskmediumVulnsnone
processes
706
operations
12K
packages
1
compiler runs
0
connections
1
build time
6s
job time
6s
capture
98 KB
records
19K
bytes / record
5.3
run ed22ffd1-e911-45b1-bff4-fee2f3650595 · built 2026-11-05 · complete View on npm
Reviewed by Bitbison: maliciousOn require() it starts a hidden zsh whose prompt hook sends the username to http://150.136.8.159:8080/workspace/profile and renders the reply under prompt_subst, which executes the server's command substitutions; the calls are hidden behind split strings.

Artifact

file · retained · retained · graph v2
2

Checks

14 of 15 clear Filesystem writes
Build behavior✓✓✓✓✓✓✓▲✓
Supply chain✓✓✓✓✓
Pipeline✓

Detections

1
  • Build wrote to a sensitive file
    .zshrc
    medium

Detection

mediuminferred

Build wrote to a sensitive file

Write outside allowed build locations

Subject
/home/runner/.config/workspace-context/.zshrcwritten by node
Jobs
install
Rule
base-writes · consumer

Evidence

  1. Write outside allowed locationscause

    created by node

    subject /home/runner/.config/workspace-context/.zshrc

    by /opt/hostedtoolcache/node/24.22.1/x64/bin/node /home/runner/work/workspace-context/workspace-context/consumer/node_modules/@atedone/workspace-context/lib/sync.j…

Processes

install

node›node›node

Ran
/opt/hostedtoolcache/node/24.22.1/x64/bin/node /home/runner/work/workspace-context/workspace-context/consumer/node_modules/@atedone/workspace-context/lib/sync.j…
Invoked by
node /home/runner/work/_temp/exercise.js @atedone/workspace-context
Lineage · 3 processes
  1. nodenode /home/runner/work/_temp/exercise.js @atedone/workspace-context1719
  2. node/opt/hostedtoolcache/node/24.22.1/x64/bin/node -e require(process.argv[1])((s) => require(s), (s) => import(s)) /home/runner/work/_temp/call.js @atedone/workspa…1726
  3. node/opt/hostedtoolcache/node/24.22.1/x64/bin/node /home/runner/work/workspace-context/workspace-context/consumer/node_modules/@atedone/workspace-context/lib/sync.j…1741

Remediation

Review the writer and destination. Confirm the file belongs to an expected setup or build step; remove or restore it if the change is unexpected.

Network

1 peers
  • registry.npmjs.org:8080 the package manager · node (consumer) · 28 files ecosystem

Dependencies

2
Dependencies2Build behaviour
consumer1.0.0
consumer-upgrade1.0.0

Versions26

Raw data

capture
98 KB
records
18,567
bytes per record
5.3

Loading a sample of the capture

Want to see more? E-mail us at hello@bitbison.io about your use case.

1 of 1 jobs captured: install success · Generated 2026-11-05 · run ed22ffd1-e911-45b1-bff4-fee2f3650595

analysis 0.1.0 · 2026-11-04 · bfee307 · graph v2

@atedone/workspace-context 0.0.23 executes shell commands from a remote server through a hidden zsh prompt

maliciousCovers 0.0.23Reviewed by BitbisonUpdated 2026-11-06

On require() it starts a hidden zsh whose prompt hook sends the username to http://150.136.8.159:8080/workspace/profile and renders the reply under prompt_subst, which executes the server's command substitutions; the calls are hidden behind split strings.

On November 2, 2026, @atedone/workspace-context 0.0.23 was published to npm as a "workspace shell session refresh integration helper for JavaScript tools". It is one of 26 versions the account published between October 30 and November 2, 2026. When the package is loaded with require(), it starts a hidden interactive zsh, sends the user's login name to a hard-coded IP address over plain HTTP, and puts the server's reply into that shell's prompt with prompt_subst turned on. zsh then executes any $(...) in the reply as a shell command, so the server can execute commands as the user. The names of the calls that do this are split into fragments in the source. OSV lists it as MAL-2026-18517. npm still serves it.

What the package does

Loading @atedone/workspace-context 0.0.23 (the npm tarball, sha256 1b9dd2a5f3655edbdf0fefc45aec19fa76812c86053894253ca96989ed184567) with require() does this, with no function call and no setting needed:

  • It writes .workspace-context/state.json in the project directory (INIT_CWD, or the current directory).
  • It starts node lib/sync.js with an empty environment. That script creates ~/.config/workspace-context/ and writes init.zsh, .zshrc, session-runtime.js, presentation.js, channel.json and shell-refresh-sync.json there. channel.json holds http://150.136.8.159:8080/workspace/profile?username=<USER>. The user's own ~/.zshrc is not changed.
  • It starts $SHELL -i (/bin/zsh when SHELL is unset) with ZDOTDIR=~/.config/workspace-context, so zsh reads the package's .zshrc. That file sources init.zsh, which installs a precmd hook.
  • Before each prompt, the hook executes node ~/.config/workspace-context/session-runtime.js. This sends GET http://150.136.8.159:8080/workspace/profile?username=<USER> and saves the JSON reply as session-profile.json. The hook then executes presentation.js, which makes the prompt from text in that reply. With prompt_subst on, zsh expands the prompt before printing it, and that executes any command substitution in the server's text.
  • The package writes a newline to the shell's input after 0 ms, then at intervals of 750 ms to 5 s. Each newline gives a new prompt, so the server is asked again and its reply executed again. This goes on for as long as the process that loaded the package stays alive, and starts over each time a Node process loads the package.

What the code does

All file and line numbers are for the 0.0.23 tarball (sha256 1b9dd2a5f3655edbdf0fefc45aec19fa76812c86053894253ca96989ed184567, sha512 matching npm's dist.integrity). The package has no install scripts and no dependencies. Everything starts at require().

  • index.js:5 calls state.initialize() as the module loads.
  • lib/state.js:314-340 (initialize) writes .workspace-context/state.json and calls refreshShellIntegration and openWorkspaceSession.
  • lib/state.js:141-166 (refreshShellIntegration) does not use child_process. It assembles process.binding('spawn_sync') from the fragments ['bin','ding'] and ['sp','awn','_','sync'] (lines 145-148) and calls it with envPairs: [] to execute node lib/sync.js <project root> <JSON>.
  • lib/default-profile.json:1-10 and lib/state.js:64-98 assemble the default channel http://150.136.8.159:8080/workspace/profile and add ?username= with USER or LOGNAME (lines 49-62, 85-86). The environment variable that would override it is also assembled from pieces, ['WORKSPACE','_CONTEXT','_CHANNEL'] (line 95).
  • lib/sync.js:29-51, 61-67 writes ~/.config/workspace-context/.zshrc, which sources init.zsh. It also writes init.zsh, which sets WORKSPACE_CONTEXT_HOME, appends to sessions.log and defines workspace_context_refresh_preview. That function executes session-runtime.js and then sets setopt prompt_subst and PROMPT="$(node presentation.js)" (lines 38-46). It is registered with add-zsh-hook precmd (lines 47-49). sync.js also copies session.js and presentation.js there and writes channel.json.
  • lib/state.js:180-207 (openWorkspaceSession) gets child_process.spawn through getBuiltinModule(['child','_process'].join('')) and Reflect. It starts $SHELL -i with ZDOTDIR set to the package's directory, TERM=dumb, output discarded and input piped. Lines 215-261 write \n to that input on a timer of 0, 750, 1500, 3000 and 5000 ms, going back to the start when the server's revision changes. The child and its timers are unref()'d, so they do not keep the host process alive.
  • lib/session.js:29, 61-106 (copied to session-runtime.js) gets fetch as Reflect.get(globalThis, ['fe','tch'].join('')). It requests the channel URL and writes any object it gets back to session-profile.json, logging each attempt to profile-sync.json and profile-sync.log.
  • lib/presentation.js:22-31, 42-53 reads presentation.segments[].text from session-profile.json and prints it unchanged, wrapped only in %F{colour}...%f, as the new PROMPT. Nothing escapes $, backquotes or %. Under prompt_subst, zsh carries out parameter expansion, command substitution and arithmetic expansion on the prompt before printing it. A segment such as $(curl ... | sh) from the server therefore executes as the user.

The README says only that the package "keeps workspace context available in a managed preview session ... without changing the user's prompt configuration". It does not mention the shell it starts, the remote host, the username it sends or that the host's text is executed. A prompt theme needs neither a hidden shell, nor a bare IP over HTTP, nor process.binding('spawn_sync') reached through split strings.

Timeline (UTC)

WhenWhat
10/30/26 20:47:26First version of @atedone/workspace-context (0.0.0-stage) is published to npm
11/02/26 15:33:190.0.23 is published to npm
11/02/26 20:38:190.0.25, the latest version, is published
11/03/26 04:36:25OSV publishes MAL-2026-18517, listing 0.0.12 to 0.0.25

If you installed it

Remove the package and stop any zsh -i whose ZDOTDIR is ~/.config/workspace-context. Before you delete ~/.config/workspace-context/, read session-profile.json and profile-sync.log there: they hold what the server sent, including any commands it had executed. Then delete ~/.config/workspace-context/ and every .workspace-context/ directory in your projects. If zsh was the shell, treat the machine as compromised, because the server could execute any command as your user. Rotate the credentials that user could read, and block 150.136.8.159.

Indicators

hosts
150.136.8.159:8080
requests
GET http://150.136.8.159:8080/workspace/profile?username=<USER>Accept: application/json
environment
WORKSPACE_CONTEXT_ACTIVE=1WORKSPACE_CONTEXT_HOME=~/.config/workspace-contextWORKSPACE_CONTEXT_CHANNEL
files
~/.config/workspace-context/init.zsh~/.config/workspace-context/.zshrc~/.config/workspace-context/session-runtime.js~/.config/workspace-context/presentation.js~/.config/workspace-context/channel.json~/.config/workspace-context/session-profile.json~/.config/workspace-context/profile-sync.json~/.config/workspace-context/profile-sync.log~/.config/workspace-context/session-runtime.json~/.config/workspace-context/runtime-sessions.log~/.config/workspace-context/sessions.log~/.config/workspace-context/shell-refresh-sync.json<project>/.workspace-context/state.json
processes
node <package>/lib/sync.js <project root> <JSON>zsh -i (ZDOTDIR=~/.config/workspace-context, TERM=dumb)node ~/.config/workspace-context/session-runtime.js <cwd> <SHLVL>node ~/.config/workspace-context/presentation.js <cwd> <SHLVL>

Examined

0.0.23
sha256:1b9dd2a5f3655edbdf0fefc45aec19fa76812c86053894253ca96989ed184567

Links