Ossprey raises $2.65m to fight AI-era software supply chain attacks.

Ossprey raises $2.65m to fight AI-era software supply chain attacks. Learn More

Ossprey

Ossprey raises $2.65m to fight AI-era software supply chain attacks.

Ossprey

BACK

Real World Attacks

How Ossprey protects from novel AI-accelerated malware development, and cross-ecosystem supply-chain attacks.

Ossprey Research Team

Ossprey Research Team

24 Sept 2026

Real World Attacks

How Ossprey protects from novel AI-accelerated malware development, and cross-ecosystem supply-chain attacks.

Ossprey Research Team

Ossprey Research Team

24 Sept 2026

No headings found in content selector: .toc-content

How Ossprey protects from novel AI-accelerated malware development, and cross-ecosystem supply-chain attacks.

This morning, Ossprey Security detected malicious versions of two MemTensor packages. @memtensor/memos-cloud-openclaw-plugin on npm (0.1.21, 0.1.23, 0.1.25) and memoryos on PyPI (2.0.34). Each of these malicious versions carried a novel Golang malware known as sckit -- a credential-stealing worm that scans environments for keys, tokens and secrets and then re-publishes itself wherever possible - not unlike other token stealers that we have covered before, like the Shai-Hulud attack, and the TeamPCP campaign.

Having covered similar attacks before, we instead decided to explore the risks this category of attack create. As always you'll be able to find IOCs for threat-hunting and securing your own environments at the bottom of this page.

Many supply chain attacks begin with fake profiles, typo-squatted libraries; lures and luck to draw victims into downloading and installing malware. Others begin with account compromises and takeovers, where known and trusted maintainer accounts are used to publish malware. In this case, the initial publish was caused by a mis-configured Github action.

The chain started with a forged commit in the MemTensor/MemOS repository, parented directly on the main branch but never merged into it, authored by an identity that doesn't exist:

MemTensor CI Review <ci-review@memtensor.invalid>

MemTensor CI Review <ci-review@memtensor.invalid>

MemTensor CI Review <ci-review@memtensor.invalid>

An initial indicator of malicious activity is the email. The .invalid TLD is the one the standards reserve for things that are, by definition, not real. A second forged commit followed under a fake `release-maintenance` identity (no such GitHub account exists), and the two together added the implant and swapped the build backend.


on:
  release:
    types: [published]
    ...
    - name: Publish package
      uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
      with:
        user: __token__

on:
  release:
    types: [published]
    ...
    - name: Publish package
      uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
      with:
        user: __token__

on:
  release:
    types: [published]
    ...
    - name: Publish package
      uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
      with:
        user: __token__

An otherwise normal Github action, pinned to a legitimate pypa commit, handed the maintainer's real PyPi token to attacker code running in the same job, which used it to publish and upload a malicious version of the package to PyPi. On the NPM side the attacker was also able to push malicious updates, however we have been unable to confirm the initial source of the NPM publish permissions.


attack_chain.png

In fact we have seen many prominent supply-chain attacks begin like this, with this attack class being known as poisoned pipeline execution (PPE).

The lesson generalises past PyPI and past MemTensor. A workflow triggered by release: published which publishes with a stored secret is a machine where the ability to create a release is the ability to publish.

Most organisations that would never hand a developer the production publishing token have a CI job that grants exactly that power to anything that can tag a release. Audit it the way you audit credential access: which humans, which tokens, which other workflows can cause this job to run, and what can it publish?

By taking a brief look now at the malware, and we see a focus on cross-platform operability. The malware was compiled to be executable on every major platform and distribution, ensuring the widest possible reach, from a developer's macbook or thinkpad, to CI/CD and linux systems. During our analysis of the malware we found that it identifies itself as supplychain.local/campaign with 2 separate campaign identifiers cloud-openclaw-semi-nuclear, and memos-semi-nuclear.


campaign_map.png

In the last year or so the malware coverage and the defenders have spent considerable time defending against a particular attack vector: npm install scripts . We have seen extensive abuse of this and Microsoft, who owns Github and NPM have put significant resource into trying to mitigate this issue, it hasn't completely solved the problem. You can find more of our coverage on Shai-Hulud hitting 2000 npm packages and npm v12 blocking install scripts by default. As defenders have evolved, so too have attackers.

This specific malware used 3 execution channels.

Import-time execution means installing nothing and running nothing is not enough, merely importing the package launches the implant. The swapped build backend means the malware executes inside the CI job that's building the release, where it can append BASH_ENV to the job's environment file, poisoning every subsequent shell in the job.

The bridge it installs then lies in wait for the exact step that uploads to PyPI, grabs the token intended for that upload, and delivers a second-stage payload.


execution_channels.png

The key point to recognise: every lifecycle event that implicitly executes code, build backends, environment files, shell init hooks, plugin loaders, logging configuration acts as an equivalent to install hook scripts. Ossprey Security is able to protect from these by not solely relying on known attack vectors but detecting malicious and risky behaviour wherever it may be configured, at the package, repo or pull-request level.

Alongside stealing secrets and tokens, the affected package is an AI agent gateway.

The plugin's dropper runs at gateway startup and again on every memory recall, which happens with each prompt, handing the raw prompt text to the implant. Everything users typed into their AI agent from source code, credentials pasted in frustration to internal plans was sent to the attacker.


prompt_flow.png

AI tooling has been a significant part of the engineer toolset and is placed in a position where inference providers have visibility of the most sensitive data in your organisation, and code harnesses are perfectly positioned to have access to code, tooling and the ability to run and install arbitrary scripts.

IOCs — @memtensor/memos-cloud-openclaw-plugin 0.1.21 / 0.1.23 / 0.1.25 and memoryos 2.0.34

Domains and URLs defanged ([.]hxxps); strip brackets for tooling. All hashes verified against registry-published artifacts on 2026-09-23.

Affected versions

  • npm: @memtensor/memos-cloud-openclaw-plugin 0.1.21, 0.1.23, 0.1.25 (0.1.25 is latest at time of writing)

  • PyPI: memoryos 2.0.34

  • Verified clean pins: plugin 0.1.20 (0.1.22 and 0.1.24 are byte-equivalent rebuilds of it), memoryos 2.0.33

Domains

skyleen[.]fr                        apex: no A record; drop-caught 2026-09-15
8a8acaf167b3[.]skyleen[.]fr         npm C2 front (parked 127.0.0.1 at time of writing)
0b48fafd6fbe[.]skyleen[.]fr         npm C2 front
266297c6df27[.]skyleen[.]fr         npm C2 front
c747d139e7e9[.]skyleen[.]fr         PyPI C2 front
73376a079d87[.]skyleen[.]fr         PyPI C2 front
d4f77a3a8cb0[.]skyleen[.]fr         PyPI C2 front
10729e014d0e[.]skyleen[.]

skyleen[.]fr                        apex: no A record; drop-caught 2026-09-15
8a8acaf167b3[.]skyleen[.]fr         npm C2 front (parked 127.0.0.1 at time of writing)
0b48fafd6fbe[.]skyleen[.]fr         npm C2 front
266297c6df27[.]skyleen[.]fr         npm C2 front
c747d139e7e9[.]skyleen[.]fr         PyPI C2 front
73376a079d87[.]skyleen[.]fr         PyPI C2 front
d4f77a3a8cb0[.]skyleen[.]fr         PyPI C2 front
10729e014d0e[.]skyleen[.]

skyleen[.]fr                        apex: no A record; drop-caught 2026-09-15
8a8acaf167b3[.]skyleen[.]fr         npm C2 front (parked 127.0.0.1 at time of writing)
0b48fafd6fbe[.]skyleen[.]fr         npm C2 front
266297c6df27[.]skyleen[.]fr         npm C2 front
c747d139e7e9[.]skyleen[.]fr         PyPI C2 front
73376a079d87[.]skyleen[.]fr         PyPI C2 front
d4f77a3a8cb0[.]skyleen[.]fr         PyPI C2 front
10729e014d0e[.]skyleen[.]

URLs (C2 paths)

hxxps://8a8acaf167b3[.]skyleen[.]fr/6110ea0c63c61803b1232685/{config,status,batch}
hxxps://0b48fafd6fbe[.]skyleen[.]fr/68b93a6c00a233c93dfbf8d2/{config,status,batch}
hxxps://266297c6df27[.]skyleen[.]fr/acc57f34d89299a4ae50b846/{config,status,batch}
hxxps://c747d139e7e9[.]skyleen[.]fr/24ffe6fe9644e7fc6ec8abd3/{config,status,batch}
hxxps://73376a079d87[.]skyleen[.]fr/44faf0ab4d0c4c03b655b20d/{config,status,batch}
hxxps://d4f77a3a8cb0[.]skyleen[.]fr/a3d76a511b4ebe799d71f1d4/{config,status,batch}
hxxps://10729e014d0e[.]skyleen[.]

hxxps://8a8acaf167b3[.]skyleen[.]fr/6110ea0c63c61803b1232685/{config,status,batch}
hxxps://0b48fafd6fbe[.]skyleen[.]fr/68b93a6c00a233c93dfbf8d2/{config,status,batch}
hxxps://266297c6df27[.]skyleen[.]fr/acc57f34d89299a4ae50b846/{config,status,batch}
hxxps://c747d139e7e9[.]skyleen[.]fr/24ffe6fe9644e7fc6ec8abd3/{config,status,batch}
hxxps://73376a079d87[.]skyleen[.]fr/44faf0ab4d0c4c03b655b20d/{config,status,batch}
hxxps://d4f77a3a8cb0[.]skyleen[.]fr/a3d76a511b4ebe799d71f1d4/{config,status,batch}
hxxps://10729e014d0e[.]skyleen[.]

hxxps://8a8acaf167b3[.]skyleen[.]fr/6110ea0c63c61803b1232685/{config,status,batch}
hxxps://0b48fafd6fbe[.]skyleen[.]fr/68b93a6c00a233c93dfbf8d2/{config,status,batch}
hxxps://266297c6df27[.]skyleen[.]fr/acc57f34d89299a4ae50b846/{config,status,batch}
hxxps://c747d139e7e9[.]skyleen[.]fr/24ffe6fe9644e7fc6ec8abd3/{config,status,batch}
hxxps://73376a079d87[.]skyleen[.]fr/44faf0ab4d0c4c03b655b20d/{config,status,batch}
hxxps://d4f77a3a8cb0[.]skyleen[.]fr/a3d76a511b4ebe799d71f1d4/{config,status,batch}
hxxps://10729e014d0e[.]skyleen[.]

Implant binaries (Go 1.27.1, module supplychain.local/campaign/cmd/implant)

The six npm binaries are byte-identical across 0.1.21 / 0.1.23 / 0.1.25 — only the launcher changed between versions.

# npm campaign (.sckit/<platform>/sckit)
381ac6dc1715d9298fe81b2a53a11f7b7d78e361ee3a6619ad54f8c4b062cc18  linux-amd64
e077c387b223811064b7bbc5a55a0182fca9bf50894f949ff284d4be87d44b26  linux-arm64
65faf8ccbcf5b34eb4f72c71bf82815fa9c1e2f947b9c898491540e866132c31  darwin-amd64
f8ccdd1da7dff1aef16377a2842bc7acf7c516e32122dd6e42dc4a4e57653fce  darwin-arm64
56cd3416d2ec2aa7e7cec2a06010cf0b58eb09c0a5486809df52afeaca8f14be  windows-amd64
d6b3e77c36ee8017c9bf30d1da7218ec0ea843768d313eb8e35845c8a9b38a26  windows-arm64
# PyPI campaign (memos/.sckit/<platform>

# npm campaign (.sckit/<platform>/sckit)
381ac6dc1715d9298fe81b2a53a11f7b7d78e361ee3a6619ad54f8c4b062cc18  linux-amd64
e077c387b223811064b7bbc5a55a0182fca9bf50894f949ff284d4be87d44b26  linux-arm64
65faf8ccbcf5b34eb4f72c71bf82815fa9c1e2f947b9c898491540e866132c31  darwin-amd64
f8ccdd1da7dff1aef16377a2842bc7acf7c516e32122dd6e42dc4a4e57653fce  darwin-arm64
56cd3416d2ec2aa7e7cec2a06010cf0b58eb09c0a5486809df52afeaca8f14be  windows-amd64
d6b3e77c36ee8017c9bf30d1da7218ec0ea843768d313eb8e35845c8a9b38a26  windows-arm64
# PyPI campaign (memos/.sckit/<platform>

# npm campaign (.sckit/<platform>/sckit)
381ac6dc1715d9298fe81b2a53a11f7b7d78e361ee3a6619ad54f8c4b062cc18  linux-amd64
e077c387b223811064b7bbc5a55a0182fca9bf50894f949ff284d4be87d44b26  linux-arm64
65faf8ccbcf5b34eb4f72c71bf82815fa9c1e2f947b9c898491540e866132c31  darwin-amd64
f8ccdd1da7dff1aef16377a2842bc7acf7c516e32122dd6e42dc4a4e57653fce  darwin-arm64
56cd3416d2ec2aa7e7cec2a06010cf0b58eb09c0a5486809df52afeaca8f14be  windows-amd64
d6b3e77c36ee8017c9bf30d1da7218ec0ea843768d313eb8e35845c8a9b38a26  windows-arm64
# PyPI campaign (memos/.sckit/<platform>

Distribution and component files




Forged GitHub commits

b52958fd  "MemTensor CI Review <ci-review@memtensor.invalid>"  2026-09-23T03:17:07Z  full payload
41bf5c7f  "release-maintenance <release-maintenance@users.noreply.github.com>

b52958fd  "MemTensor CI Review <ci-review@memtensor.invalid>"  2026-09-23T03:17:07Z  full payload
41bf5c7f  "release-maintenance <release-maintenance@users.noreply.github.com>

b52958fd  "MemTensor CI Review <ci-review@memtensor.invalid>"  2026-09-23T03:17:07Z  full payload
41bf5c7f  "release-maintenance <release-maintenance@users.noreply.github.com>

Embedded key material (Ed25519 public keys, base64)




C2-side pins (from _pypi_bridge.sh)




Hunting strings




Filesystem paths and environment




Kill-switch timestamps




MITRE ATT&CK

Built from confirmed behaviors in the published artifacts and recovered binary symbols.

Tactic

ID

Technique

Where found

Resource Development

T1583.001

Acquire Infrastructure: Domains

skyleen[.]fr drop-caught 2026-09-15, eight days pre-attack — anonymized registrant, Namecheap NS, ProtonMail verification

Initial Access

T1195.002

Supply Chain Compromise: Compromise Software Supply Chain

Trojanized @memtensor/memos-cloud-openclaw-plugin 0.1.21/0.1.23/0.1.25 and memoryos 2.0.34 published under the maintainer's own identity

Initial Access / Defense Evasion

T1078

Valid Accounts

Stolen npm automation token for direct registry publishes; GitHub token for forged commits and the v2.0.34 release

Execution

T1059

Command and Scripting Interpreter

Implant executes C2-issued commands (credential-collectorauthority-classifiercommandWithAuthority); worm drives npmgittwinepython3

Persistence

T1574

Hijack Execution Flow

Poetry build-backend swapped to sckit_poetry_build; agent-plugin hooks (launchStageZero at startup and per-prompt) redirect legitimate execution paths

Persistence

T1546.004

Event Triggered Execution: Unix Shell Configuration Modification

Build hook appends BASH_ENV=src/memos/_pypi_bridge.sh to GITHUB_ENV; every later bash step in the CI job — including the twine upload — sources it

Defence Evasion

T1036

Masquerading

Forged git author identities (ci-review@memtensor.invalid, nonexistent release-maintenance account); clean/malicious version ladder with clean-inverse-* dist-tags

Defence Evasion

T1070.004

Indicator Removal: File Deletion

scheduleSelfDelete removes the implant's own binary; StateStore Cleanup/Tombstone manage remaining traces

Defence Evasion

1497

Virtualisation/Sandbox Evasion

ja4_deny TLS-fingerprint deny-list and deny_iso geo filtering steer analysis environments away from live C2

Credential Access

T1552.001

Unsecured Credentials: Credentials In Files

.netrc.pypirc.npmrc.vault-token.git-credentialscredentials.dbaccess_tokens.db, plus credential environment variables (NPM_TOKENNODE_AUTH_TOKENPYPI_API_TOKENINPUT_PASSWORD)

Discovery

T1082

System Information Discovery

Host profiling: OS, arch, hostname, interfaces, IPs, timezone, languages

Discovery

T1083

File and Directory Discovery

inventory_roots: [$HOME]; repository discovery by glob; read_globs over victim trees

Collection

T1005

Data from Local System

Credential files, repo contents, git author fingerprints — and every user prompt handed over as SCKIT_EVENT_TEXT

Command and Control

T1105

Ingress Tool Transfer

Signed second stage fetched from 10729e014d0e[.]skyleen[.]fr/.../initial-ci-v2 — hash-pinned, single-use per CI job, five-second window

Exfiltration

T1041

Exfiltration Over C2 Channel

Chunked, AEAD-sealed result uploads to the /{...}/batch endpoints on the campaign fronts


SHARE

Subscribe Now

Subscribe Now

Subscribe Now

Ossprey helps you understand what code is trying to do,  before you trust it.

Ossprey helps you understand what code is trying to do,  before you trust it.

Related articles.

Related articles.

Related articles.