This document captures the dev-workstation security baseline for everyone
contributing to Allora Network projects. It applies to every active repo
under the allora-network org (TypeScript/JS, Python, Go, Rust, Solidity —
the package-manager parts are language-specific; the credential and laptop
hygiene parts apply to everyone).
If you're triaging a real or suspected compromise on your machine, stop
reading this file and follow
SECURITY-RUNBOOK.md → Scenario A.
Cross-doc link status. This document links to
SECURITY-RUNBOOK.md(DEVOP-571) and the IOC seed files under.github/security/(DEVOP-561). Those land in sibling PRs and may 404 onmainuntil the related PRs merge. If a link is broken at the time you're reading this, check the open PRs onallora-network/.githubor grep the repo (git grep -l SECURITY-RUNBOOK).
Do these once on every machine you'll use for Allora work, before you clone your first repo:
- Drop the user-level
~/.npmrctemplate (see §1). - Enable Corepack:
corepack enable. - Install Socket CLI:
npm install -g socketand add the shell alias from §2. - Install
uv:curl -LsSf https://astral.sh/uv/install.sh | sh, then add the alias from §3. - Move every long-lived
NPM_TOKEN/PYPI_API_TOKENout of your shell rc — see §4. - Migrate any GitHub PATs in 1Password to fine-grained (§5).
- Bookmark the
SECURITY-RUNBOOK.mdlink above.
The rest of this document explains why and provides the configuration templates.
The PyPI variant of Shai-Hulud abused setup.py; the npm variants abuse
postinstall, preinstall, and other lifecycle scripts. Disabling
lifecycle scripts on local installs is the single highest-impact
mitigation a dev can apply.
Put this in ~/.npmrc (your user-level config, not in any repo):
# Allora Network dev-workstation security baseline (CONTRIBUTING.md §1).
#
# ignore-scripts=true: prevents postinstall/preinstall/install lifecycle
# scripts from running on `npm install` / `pnpm install` / `yarn install`.
# This is the Shai-Hulud npm-side mitigation. Some legitimate packages
# need a postinstall (esbuild, sharp, native modules) — for those, run
# the per-repo install with `npm install --ignore-scripts=false` after
# verifying the package is on the org's allowlist.
ignore-scripts=true
# fund=false: silence "consider funding" output. Cosmetic, but the
# install transcripts are noisy enough as it is.
fund=false
# audit-level=high: when you do run `npm audit`, only show high+critical.
# The low/moderate noise crowds out the signal.
audit-level=high
# save-exact=true: when you `npm install <pkg>`, pin the exact version
# in package.json rather than the default caret range. Reduces the
# blast radius of a published-but-not-yet-yanked compromised version.
save-exact=trueIf a per-repo install genuinely needs lifecycle scripts (native modules, build steps that aren't replicable in CI):
# Audit what the postinstall actually does:
npm pack <pkg>
tar -xzf <pkg>-<version>.tgz
cat package/package.json | jq '.scripts'
# Or for a transitive: pnpm why <pkg>; pnpm install --frozen-lockfile --ignore-scripts=false --include=optional <pkg>
# Then run the targeted install with scripts re-enabled for ONE command:
npm install --ignore-scripts=falseDo not flip ignore-scripts back to false in ~/.npmrc as a default.
Corepack pins the package manager version to whatever packageManager
field is set in the repo's package.json. This eliminates "works on my
machine" issues and prevents drift onto a compromised package-manager
release.
Once, on every machine:
corepack enableThe first time you cd into a repo with "packageManager": "pnpm@9.15.0" in its package.json, Corepack will fetch that exact
version on demand, verify the embedded signature, and use it.
Subsequent invocations are cached.
If you maintain a repo and packageManager isn't pinned in
package.json, fix that in a follow-up PR — see DEVOP-554.
Socket maintains a real-time supply-chain risk
feed for npm and PyPI. Their free CLI wraps npm install (and pnpm,
yarn, pip) and refuses to install a package that matches a known
malware indicator. It's not a substitute for ignore-scripts=true —
it's a complement: Socket catches packages that haven't been pulled
from npm yet but have been flagged in their feed.
Install:
npm install -g socketAdd to your shell rc (~/.zshrc / ~/.bashrc):
# Allora Network dev-workstation security baseline (CONTRIBUTING.md §2).
# Wrap interactive npm installs through Socket. This blocks installs of
# packages on Socket's risk feed before the malicious postinstall ever
# runs. The wrapper aliases only the interactive cases; CI installs are
# unaffected (CI uses --ignore-scripts and hashed lockfiles already).
alias npm-i='socket npm install'
alias pnpm-i='socket pnpm install'When adding a new dependency to a repo:
npm-i <package> # instead of: npm install <package>
pnpm-i <package> # instead of: pnpm install <package>Socket exits non-zero if the package or any transitive is flagged. If
you hit a flag, do not override; bring the alert to
#security-alerts first.
uv is a drop-in replacement for pip and pip-tools written in Rust.
Two relevant properties for our threat model:
- It refuses to install from an sdist by default in
--only-binarymode — same defense as the Dockerfile install inrobonet-backend(DEVOP-556). - It's much faster, so the "I'll just
pip installreal quick" muscle-memory becomes "I'll justuv pip installreal quick" without the slowdown excuse.
Install:
curl -LsSf https://astral.sh/uv/install.sh | shAdd to your shell rc:
# Allora Network dev-workstation security baseline (CONTRIBUTING.md §3).
# Prefer uv pip with hash-pinned + binary-only as the default for any
# local Python install. Mirrors the Docker production install hardening
# (DEVOP-556). For one-off "throwaway" venvs you can drop the
# --require-hashes flag; never drop --only-binary=:all:.
alias pip-i='uv pip install --require-hashes --only-binary=:all:'
# For repos that haven't migrated to hashed requirements.txt yet, this
# fallback gives you the binary-only protection without the hash check:
alias pip-i-binary='uv pip install --only-binary=:all:'When working on a repo with a hashed requirements.txt (e.g.
robonet-backend post-DEVOP-556):
pip-i -r requirements.txtFor an exception (e.g. jsonrpc-base that only ships sdist on PyPI),
add --no-binary=<pkg> for that one package and document it in the
repo's requirements.in.
Do not put NPM_TOKEN, PYPI_API_TOKEN, or any registry-publish
credential in:
- Your shell rc (
~/.zshrc,~/.bashrc). - A user-level
~/.npmrcor~/.pypirc. - A
.envfile in any repo. - Any 1Password entry that isn't in the "Break-glass" vault (which requires founder approval to access).
Publishing happens from CI, period. The CI workflows are configured per
DEVOP-545 (token written to .npmrc after install, with
--ignore-scripts, and deleted in the same step). The longer-term plan
is to migrate publish flows to OIDC Trusted Publishers (npm + PyPI both
support this) — see DEVOP-578. Once that lands, the only way to publish
to our packages will be via CI on main; even an admin can't override
from their laptop.
If you absolutely need to publish from a local machine for an emergency release:
- Coordinate in
#security-alertsfirst (the same channel that runs incident response — this is deliberate; emergency publishes are incident-adjacent). - Use the runbook's Scenario C step 4 procedure (clean environment, token kept out of disk, deleted immediately after publish).
- Rotate the token immediately after publish.
- Do not create classic PATs. GitHub still allows it; we don't. The audit log shows "classic PAT used by user X" as a distinct event, and the on-call should flag any new classic PAT issuance in the weekly security review.
- Use fine-grained personal access tokens for anything you actually
need (most workflows can be done with a logged-in
gh auth loginsession and don't need a token at all). - When you create a fine-grained token, pin:
- Expiration: 90 days max. No "no expiration" tokens.
- Resource owner: the specific org or your user, never both.
- Repository access: list each repo explicitly; do not select "all repositories" unless the use case actually requires it.
- Permissions: minimum viable. The default offered scope is almost always wider than what the use case needs.
The DevOps on-call walks the credential rotation calendar (SECURITY-RUNBOOK.md §7) on the 1st of every quarter. If you have a fine-grained token issued more than 90 days ago, you'll get a Slack DM. Rotate within the week.
- ed25519 keys only (
ssh-keygen -t ed25519). RSA <3072 is rejected; 3072+ works but is legacy. - Hardware-backed if possible (SE on macOS via
ssh-keychain, or YubiKey viagpg-agent/ssh-keygen -t ed25519-sk). Hardware backing turns a stolen laptop into a stolen-key-stays-on-the-device scenario.
- Commit your lockfile.
package-lock.json,pnpm-lock.yaml,yarn.lock,bun.lock,requirements.txt(hashed),Cargo.lock,Pipfile.lock,uv.lock— all in version control. - Never edit a lockfile by hand. Re-run the package manager.
- Never delete a lockfile to "fix" an install error. The lockfile is the source of truth for which exact version got installed. If you delete it, regenerate it from a clean state and review the diff carefully before committing.
- In CI we run with
--frozen-lockfile(npm/pnpm/yarn) or--require-hashes(pip) — failing the build rather than auto- updating the lockfile means a malicious mid-PR lockfile change can't slip through.
STOP. Do not finish this PR. Do not commit.
Open SECURITY-RUNBOOK.md on a different
device and follow Scenario A — Developer workstation suspected
infected, which starts with disconnecting the machine from the
network.
Symptoms that warrant Scenario A:
- An unexpected
postinstalllog line duringnpm install. - An unfamiliar process accessing
~/.npmrc,~/.aws/,~/.ssh/, or~/.gnupg/(check Console.app → "Privacy & Security" log on macOS). - Outbound network connections from your shell to a non-allowlisted
host immediately after a package install (use Little Snitch or
lsof -ito verify). - Pre-commit hooks in
.git/hooks/that you didn't write. - A package install that "hung" and then "succeeded" with no visible output.
Erring toward Scenario A is free. We will not retroactively second- guess anyone who triggered the runbook on a suspicion that turned out to be a false alarm.
- Every PR that adds a new dependency must be reviewed by a second
engineer. The reviewer is responsible for cross-referencing the
package against Socket (
socket package score <name>) and the GitHub Advisory Database before approving. Yes, even for one-line PRs. - PRs that modify any
package.json,pnpm-lock.yaml,requirements.in,requirements.txt,Cargo.toml,Cargo.lock,Pipfile,Pipfile.lock, oruv.lockget an automaticdependencieslabel via the org's labeler workflow. Treat that label as a hint to spend the extra 5 minutes. - Lockfile-only changes in a PR that doesn't claim to be a dependency bump are suspicious by default (this was the original Shai-Hulud worm propagation pattern). Reject and request a clean PR.
- SECURITY-RUNBOOK.md — incident response (Shai-Hulud-class supply-chain compromise).
.github/security/ioc-packages.txt— current known-bad package@version pairs..github/security/ioc-hashes.txt— current known-bad SHA-256 hashes.- Socket free advisory feed — refresh source for the IOC lists.
- GitHub Advisory Database — search by package name before approving a new dependency PR.
Last updated: 2026-05-13 (initial publication, DEVOP-572). For
runbook-level changes, edit SECURITY-RUNBOOK.md directly; this file
should stay focused on dev-workstation guidance.