@automagik/genie e pgserve (publicados pela Namastex Labs) foram carregadas no registro público como parte da campanha CanisterWorm — um worm de supply-chain auto-propagante atribuído ao ator TeamPCP que afetou múltiplos publishers npm. Assumimos responsabilidade por este incidente. Este manual existe para ajudar qualquer pessoa ou organização que tenha instalado as versões afetadas a verificar se foi comprometida e, em caso afirmativo, remediar de forma estruturada.genie sec scan. Os checks manuais abaixo continuam válidos como confirmação adicional, fallback, ou triagem em hosts onde o CLI não está disponível.Nota v6: o Genie não tem mais o comandogenie sec, e o CLI atual não traz scanner de host. Onde este documento manda rodargenie sec, use os checks manuais da seção 2 e o veredicto da tabela da seção 3. Se o host foi afetado, isole a máquina, preserve as evidências e rotacione as credenciais a partir de outro host confiável. Para verificar uma release do Genie, veja Security and releases.
postinstall roda no momento da instalação do pacote, coleta credenciais locais e exfiltra para infraestrutura de comando e controle via HTTPS.| Pacote | Versões comprometidas | Status no npm |
@automagik/genie | 4.260421.33 até 4.260421.40 | Depreciadas e removidas |
pgserve | 1.1.11, 1.1.12, 1.1.13, 1.1.14 | Depreciadas e removidas |
| Pacote | Versão segura |
@automagik/genie | 4.260422.4 ou posterior |
pgserve | 1.1.10 |
⚠️ A distribuição vianpmjs.comfoi descontinuada após este incidente. Pacotes Namastex agora são shipados via Aegis — GitHub Releases assinados com cosign keyless (OIDC) + atestação SLSA L3 de proveniência. Veja §5.6 para o trust model completo e §4.3 para o caminho de install.
~/.npmrc~/.ssh/id_* (privadas e públicas)~/.config/gh/hosts.yml~/.aws/, ~/.azure/, ~/.config/gcloud/~/.kube/config, tokens de service account /var/run/secrets/kubernetes.io/.env — qualquer .env acessível no workspace~/.bash_history, ~/.zsh_historyANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY em .env ou variável de ambiente/proc/<pid>/environ~/.docker/config.json⚠️ Leitura crítica: o roubo aconteceu no momento da instalação. Rotacionar chaves no GitHub não desfaz o que já foi exfiltrado — você precisa rotacionar todos os itens listados acima.
genie sec scan para confirmar quais tipos de material estavam presentes no host. A seção at-risk local material present on host não mostra segredos, mas lista os caminhos e artefatos locais que o malware provavelmente teria tentado ler.Nota v6: sem ogenie sec scan, use a lista acima como checklist do material que estava no host.
genie sec scan (recomendado)Nota v6: esta subseção descreve ogenie sec scan, que o Genie não tem mais. No v6, siga direto para os checks manuais a partir da seção 2.1.
1genie sec scan --all-homes --root "$PWD"
1genie sec scan --all-homes --root /srv/app --root /opt/service --root "$PWD"
--json quando quiser arquivar ou automatizar a triagem:1genie sec scan --json --all-homes --root "$PWD"
LIKELY COMPROMISED — há sinais de execução, persistência, .pth, artefatos de drop, ou processo ativo. Vá direto para o Passo 3.LIKELY AFFECTED — há versões comprometidas instaladas ou em cache. Trate o host como exposto e siga para o Passo 3.OBSERVED ONLY — só foram encontradas referências em cache, lockfile, ou log. Ainda assim revise os checks manuais abaixo antes de declarar o host limpo.NO FINDINGS — não houve evidência específica do incidente dentro do escopo escaneado.systemd, cron, launchd, .pthLIKELY COMPROMISED ou LIKELY AFFECTED, continue com os passos manuais abaixo apenas para coleta complementar e preservação de evidência.1234567# Caminho padrão cat ~/.bun/install/global/node_modules/@automagik/genie/package.json 2>/dev/null | grep '"version"' cat ~/.bun/install/global/node_modules/pgserve/package.json 2>/dev/null | grep '"version"' # Caminho alternativo (bun mais recente) cat ~/.cache/.bun/install/global/node_modules/@automagik/genie/package.json 2>/dev/null | grep '"version"' cat ~/.cache/.bun/install/global/node_modules/pgserve/package.json 2>/dev/null | grep '"version"'
1npm ls -g @automagik/genie pgserve 2>&1
1234ls ~/.bun/install/cache/@automagik/ 2>/dev/null | grep -E "genie@4\.260421\.(3[3-9]|40)" ls ~/.bun/install/cache/ 2>/dev/null | grep -E "pgserve@1\.1\.(1[1-4])" ls ~/.cache/.bun/install/cache/@automagik/ 2>/dev/null | grep -E "genie@4\.260421\.(3[3-9]|40)" ls ~/.cache/.bun/install/cache/ 2>/dev/null | grep -E "pgserve@1\.1\.(1[1-4])"
12genie@4.260421.36@@@1 pgserve@1.1.14@@@1
12345678910# genie — harvester de credenciais + chave RSA pública do atacante ls ~/.bun/install/cache/@automagik/genie@4.260421.*@@@1/dist/env-compat.cjs 2>/dev/null ls ~/.bun/install/cache/@automagik/genie@4.260421.*@@@1/dist/public.pem 2>/dev/null # pgserve — payload TeamPCP ls ~/.bun/install/cache/pgserve@1.1.1*@@@1/scripts/check-env.js 2>/dev/null # Caminhos alternativos ls ~/.cache/.bun/install/cache/@automagik/genie@4.260421.*@@@1/dist/env-compat.cjs 2>/dev/null ls ~/.cache/.bun/install/cache/.bun/install/cache/pgserve@1.1.1*@@@1/scripts/check-env.js 2>/dev/null
🚨 Se qualquer um desses arquivos existir: VOCÊ FOI INFECTADO. Vá direto para o Passo 3.
123systemctl --user status pgmon 2>&1 | head -5 ls -la ~/.config/systemd/user/pgmon.service 2>&1 ls -la /tmp/pglog /tmp/pg_log /tmp/.pg* 2>&1
pgmon.service existir ou /tmp/pglog existir: há persistência ativa na máquina.distutils-precedence.pth, _virtualenv.pth e easy-install.pth. Qualquer outro .pth é suspeito:12345678python3 -c "import site; print('\n'.join(site.getsitepackages()))" 2>/dev/null | while read d; do ls "$d"/*.pth 2>/dev/null done # Procurar strings associadas ao TeamPCP python3 -c "import site; print('\n'.join(site.getsitepackages()))" 2>/dev/null | while read d; do grep -l -E "pgserve|canister|icp0|api-monitor" "$d"/*.pth 2>/dev/null done
1ss -tnp 2>/dev/null | grep -iE "api-monitor|icp0|tdtqy|cjn37|143\.198\.237\.25"
🚨 Se aparecer qualquer resultado: a máquina está exfiltrando agora. Desconecte da rede antes de seguir.
| Situação | Veredicto | Ação |
genie sec scan retorna LIKELY COMPROMISED | INFECTADO | Desconecte da rede se possível e execute o Passo 3 completo |
genie sec scan retorna LIKELY AFFECTED | INFECTADO | Execute o Passo 3 completo |
genie sec scan retorna OBSERVED ONLY | OBSERVADO | Continue nos checks manuais; se houver dúvida operacional, trate como infectado |
genie sec scan retorna NO FINDINGS | CLEAN provisório | Se o escopo cobriu todos os homes e roots relevantes, siga para o Passo 4 |
| Nenhuma versão maliciosa no cache, nenhum IoC | CLEAN | Vá direto para o Passo 4 (prevenção) |
Versão maliciosa no cache, mas env-compat.cjs/check-env.js ausentes | OBSERVADO | Cache presente mas postinstall pode não ter rodado — trate como INFECTADO por precaução (Passo 3) |
env-compat.cjs, public.pem ou check-env.js presentes | INFECTADO | Execute o Passo 3 completo |
pgmon.service ativo ou conexão C2 detectada | INFECTADO ATIVO | Desconecte a rede imediatamente, depois Passo 3 |
Nota v6: as linhas que citamgenie sec scandependem de um comando que o Genie não tem mais. Decida pelas demais linhas, que usam os checks manuais da seção 2.
123456789# Systemd systemctl --user stop pgmon 2>/dev/null systemctl --user disable pgmon 2>/dev/null rm -f ~/.config/systemd/user/pgmon.service rm -f /tmp/pglog /tmp/pg_log systemctl --user daemon-reload # .pth suspeitos (se encontrados no 2.6) # Remover manualmente os arquivos suspeitos identificados
1234567891011121314151617# genie — versões maliciosas 4.260421.33 a 4.260421.40 for v in 33 34 35 36 37 38 39 40; do rm -rf ~/.bun/install/cache/@automagik/genie@4.260421.${v}@@@1 rm -rf ~/.cache/.bun/install/cache/@automagik/genie@4.260421.${v}@@@1 done # pgserve — versões maliciosas 1.1.11 a 1.1.14 for v in 11 12 13 14; do rm -rf ~/.bun/install/cache/pgserve@1.1.${v}@@@1 rm -rf ~/.cache/.bun/install/cache/pgserve@1.1.${v}@@@1 done # Verificar que limpou ls ~/.bun/install/cache/@automagik/ 2>/dev/null | grep -E "genie@4\.260421" ls ~/.bun/install/cache/ 2>/dev/null | grep -E "pgserve@1\.1\.1[1-4]" ls ~/.cache/.bun/install/cache/@automagik/ 2>/dev/null | grep -E "genie@4\.260421" ls ~/.cache/.bun/install/cache/ 2>/dev/null | grep -E "pgserve@1\.1\.1[1-4]"
@automagik/genie sec após este incidente. Veja §5.6 para o trust model.1234567891011# 1. Instale a Aegis (o instalador verifica cosign + SLSA L3 antes de tocar qualquer arquivo) curl -fsSL https://raw.githubusercontent.com/automagik-dev/aegis/main/install.sh | bash # 2. Confirme que o binário instalado é genuíno antes de confiar nele aegis verify-install # 3. Scan read-only do host aegis scan --all-homes --root "$PWD" # 4. Remediação interativa — aplica a limpeza pedindo confirmação por ação (use --yes em CI) aegis fix
env-compat.cjs, public.pem, check-env.js). Remove persistência (pgmon.service, /tmp/pglog) e produz audit log append-only. Se algo der errado, aegis rollback <scan-id> desfaz a operação inteira via sha256-verified restore.@automagik/genie e pgserve seguindo o guia de install atual de cada projeto (pós-saída do npm — veja §5.6). A Aegis cuida da detecção e limpeza, não da reinstalação dos pacotes.-s -- --auto-install-deps. Para air-gapped, veja docs/install/ no repositório da Aegis.🔥 Este é o passo mais importante. Qualquer credencial presente na máquina no momento da instalação foi exfiltrada. Rotacionar = revogar a existente e emitir uma nova.
genie sec scan, use a seção at-risk local material present on host como checklist para não esquecer nenhuma classe de credencial, carteira, perfil de navegador, ou .env local presente no host comprometido.Nota v6: sem ogenie sec scan, use a lista da seção 1.4 como checklist.
123cat ~/.npmrc # ver token atual # Rotacionar em: https://www.npmjs.com/settings/<user>/tokens # Atualizar ~/.npmrc com o novo token (escopo mínimo, 2FA obrigatório)
123cat ~/.config/gh/hosts.yml # ver contas logadas # Rotacionar em: https://github.com/settings/tokens gh auth login # re-autenticar após revogar
123456ls -la ~/.ssh/id_* # Gerar nova chave ssh-keygen -t ed25519 -C "seu@email.com" -f ~/.ssh/id_ed25519_new # Adicionar nova chave pública em todos os servidores/GitHub # Remover chave comprometida de todos os authorized_keys # Substituir ~/.ssh/id_ed25519 pela nova
💡 Lição aprendida da nossa investigação: hosts clonados a partir de um mesmo template podem compartilhar o mesmo par de chaves. Se esse for o seu caso, rotacionar em um host não basta — precisa gerar chaves únicas em cada host e atualizarauthorized_keysem toda a frota.
123ls ~/.aws/credentials # Rotacionar em: AWS Console → IAM → Access Keys aws sts get-caller-identity # verificar que novas creds estão ativas
123ls ~/.config/gcloud/ gcloud auth revoke --all gcloud auth login
1234ls ~/.azure/ az logout az login # Rotacionar service principals em: portal.azure.com → App registrations → Certificates & secrets
123# Rotacionar tokens de service account # Rotacionar kubeconfig e contextos com acesso a produção cat ~/.kube/config | grep -E "certificate-authority-data|token"
12cat ~/.docker/config.json # Rotacionar credenciais de cada registry (Docker Hub, ECR, GCR, GHCR, OCIR, etc.)
.env123# Encontrar todos os .env no workspace find ~/workspace ~ -maxdepth 5 -name ".env*" -not -path "*/node_modules/*" 2>/dev/null | head -50 # Para cada um: rotacionar todas as chaves/tokens que ele contém
123# Listar versões publicadas recentemente de cada pacote seu npm view <seu-pacote> versions --json | tail -10 npm view <seu-pacote> time --json | tail -20
12npm unpublish <pacote>@<versao> # remove a versão contaminada (se dentro da janela de 72h) npm deprecate <pacote>@<versao> "Malicious — CanisterWorm supply-chain attack"
security@npmjs.com e, se possível, abra um Security Advisory no GitHub (GHSA).env-compat.cjs, public.pem, check-env.js)ss -tnp, journalctl --user -u pgmon, last, whopackage.jsonlatest para pacotes de supply-chain sensível. Use versões explícitas:123456{ "dependencies": { "@automagik/genie": "4.260422.4", "pgserve": "1.1.10" } }
postinstall em CI/CD123npm config set ignore-scripts true # ou bun install --ignore-scripts
@lavamoat/allow-scripts para controle granular.genie-update-check.sh, cron jobs, tmux status hooks etc.). Auto-update foi o vetor primário de infecção em larga escala — a máquina instalou sozinha sem ação do usuário. Verifique:123crontab -l | grep -E "update|genie|pgserve" systemctl --user list-timers | grep -E "update|genie" tmux show-options -g status-right 2>/dev/null | grep update
npm publish --provenance (GitHub Actions com OIDC).npmjs.com: tokens de publish com escopo amplo, sem verificação de identidade do publisher no install-time, propagação self-spread via dependências. Concluímos que registries com publish-by-token não são uma base de trust segura para shipping de software crítico. Por isso, os pacotes @automagik/genie e pgserve foram movidos para distribuição própria. Agora usamos GitHub Releases assinados, sem dependência do registro npm para o caminho default de install.@automagik/genie sec, dedicado a shipping seguro de AI. Cada release é assinada com cosign keyless (OIDC via GitHub Actions) e atestada com SLSA L3 provenance. O instalador (curl -fsSL .../install.sh | bash) verifica assinatura e proveniência antes de tocar qualquer arquivo. ~/.npmrc do usuário nunca é tocado. Verificação em runtime via aegis verify-install.aegis scan. Scanner read-only que roda em workspaces de desenvolvimento e em CI antes de operações sensíveis. Detecta versões comprometidas instaladas/cacheadas, payloads conhecidos, persistência (pgmon.service, .pth), conexões a C2 conhecido, e enumera material local em risco. Roda como gate contínuo, não só em resposta a incidente.@automagik/aegis-signatures) traz definições de incidentes adicionais via package separado com release cadence independente — operadores rodam aegis signatures update em cron, novas worm definitions chegam em minutos. Detalhes técnicos completos: github.com/automagik-dev/aegis.123~/.bun/install/cache/@automagik/genie@4.260421.<33-40>@@@1/dist/env-compat.cjs ~/.bun/install/cache/@automagik/genie@4.260421.<33-40>@@@1/dist/public.pem ~/.bun/install/cache/pgserve@1.1.<11-14>@@@1/scripts/check-env.js
| Indicador | Tipo | Observação |
telemetry.api-monitor.com | Webhook de exfiltração | Principal endpoint de coleta |
143.198.237.25 | IP | Resolução atual do webhook |
cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io | ICP canister | C2 secundário |
tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io | ICP canister | C2 secundário |
| Indicador | Tipo |
~/.config/systemd/user/pgmon.service | Serviço systemd |
/tmp/pglog, /tmp/pg_log | Binários auxiliares |
.pth suspeito em site-packages Python | Persistência TeamPCP |
123iptables -I OUTPUT -d 143.198.237.25 -j DROP iptables -I OUTPUT -p tcp --dport 443 -m string --algo bm --string "api-monitor.com" -j DROP iptables -I OUTPUT -p tcp --dport 443 -m string --algo bm --string "icp0.io" -j LOG --log-prefix "[CANISTERWORM-C2] "
CanisterWorm-C2 com os IPs/hosts acima e uma regra de bloqueio em posição topo, com logging habilitado.genie sec scan --all-homes --root <repo> e revisei o veredictoat-risk local material present on host para priorizar rotaçãoenv-compat.cjs, public.pem, check-env.js — ausentespgmon.service e /tmp/pglog — ausentes.pth Python — apenas legítimospackage.jsonNota v6: os itens que citamgenie sec scaneat-risk local material present on hostdependem de um comando que o Genie não tem mais. No v6, marque esses itens pelos checks manuais da seção 2 e pela lista da seção 1.4.
| Canal | Para quê | |
| DPO Namastex (Cezar Vasconcelos) | dpo@namastex.ai | Questões de privacidade e LGPD |
| Canal de segurança e incidentes | privacidade@namastex.ai | Relatar impacto, pedir ajuda, compartilhar IoCs |
| Canal direto CTO | cezar@namastex.ai | Questões técnicas críticas |
privacidade@namastex.ai. Apoiamos com orientação sem custo, dentro do razoável.privacidade@namastex.ai (PGP disponível sob solicitação).| Data | Versão | Mudança |
| 2026-04-23 | 1.0 | Publicação inicial consolidada pós-investigação |
| 2026-04-24 | 1.1 | Adicionado operator playbook em inglês (§11) com árvore de decisão de três ramos, escalations e template de post-mortem |
| 2026-10-02 | 1.2 | Notas v6 junto a cada recomendação do scanner que o Genie não tem mais; link do SECURITY.md corrigido no §11.7 |
genie sec scan status bands. Use it when you already have @automagik/genie on the host and you need a step-by-step remediation recipe that matches exactly what the CLI is about to ask of you.v6 note: Genie no longer hasgenie sec, and the current CLI has no host scanner. This playbook still shows its commands as they were when it was written. On a v6 host, pick the branch from the manual checks in §2 (the mapping is under §11.2), do each step by hand, verify a Genie release withscripts/verify-release.shas Security and releases describes, and rotate credentials from a separate trusted host.
genie sec scan returned LIKELY COMPROMISED, LIKELY AFFECTED, or OBSERVED ONLY.@automagik/genie versions 4.260421.33 through 4.260421.40, or pgserve 1.1.11–1.1.14, between 2026-04-21 and 2026-04-22.pgmon.service, /tmp/pglog, or suspicious .pth file appeared on disk.telemetry.api-monitor.com, 143.198.237.25, or any *.raw.icp0.io was logged.genie sec scan emits a status band at the top of its report. That band picks the branch below.| Scanner status | Branch | Severity |
LIKELY COMPROMISED | §11.3 LIKELY COMPROMISED | Active compromise evidence (execution, persistence, live process) |
LIKELY AFFECTED | §11.4 LIKELY AFFECTED | Malicious versions installed or cached; postinstall may have run |
OBSERVED ONLY | §11.5 OBSERVED ONLY | Cache/lockfile/log references; no execution evidence |
NO FINDINGS | Stop — pin versions, enable ignore-scripts, keep monitoring | None in scope |
env-compat.cjs, public.pem, check-env.js), persistence (pgmon.service, /tmp/pglog, a suspicious .pth) or a live C2 connection means §11.3. A malicious version installed or cached means §11.4. Only passive references (cache index, lockfile, shell history) mean §11.5.Non-negotiable first step for any branch: verify the CLI you are about to trust is genuine. Rungenie sec verify-installbefore anyremediate,restore, orrollbackinvocation. If that call cannot return exit0, read §11.7 Escalation —--unsafe-unverifiedbefore touching the host.
genie sec verify-install and --unsafe-unverified do not exist in v6, and no supported flag bypasses a failed verification. To check a Genie release, run scripts/verify-release.sh from a clone of the genie repository. If it fails, do not run that build.genie sec scan returned LIKELY COMPROMISED. A live process, persistence unit, or dropped payload was detected. Assume the host is exfiltrating or about to exfiltrate.12345678# Replace <pid-from-findings> with each PID from the scan JSON report: # scan_id=$(genie sec scan --json --all-homes --root "$PWD" | jq -r '.scan_id') # jq -r '.findings[] | select(.category=="live_process") | .pid' \ # "$GENIE_HOME/sec-scan/$scan_id/report.json" ps -o pid=,comm=,args= -p <pid-from-findings> # Archive the full process table too, in case the scanner missed a child: ps -eo pid,ppid,user,etime,comm,args > /tmp/ps-snapshot-$(date -u +%Y%m%dT%H%M%SZ).txt
telemetry.api-monitor.com, 143.198.237.25, and multiple *.raw.icp0.io ICP canisters. Block at the host level before scanning.1234iptables -I OUTPUT -d 143.198.237.25 -j DROP iptables -I OUTPUT -p tcp --dport 443 -m string --algo bm --string "api-monitor.com" -j DROP iptables -I OUTPUT -p tcp --dport 443 -m string --algo bm --string "icp0.io" -j LOG --log-prefix "[CANISTERWORM-C2] " iptables -I OUTPUT -p tcp --dport 443 -m string --algo bm --string "icp0.io" -j DROP
123456789cat >/etc/pf.anchors/canisterworm <<'EOF' block drop out quick to 143.198.237.25 block drop out quick proto tcp to any port 443 \ host { "telemetry.api-monitor.com", "cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io", \ "tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io" } EOF echo 'anchor "canisterworm"' >> /etc/pf.conf echo 'load anchor "canisterworm" from "/etc/pf.anchors/canisterworm"' >> /etc/pf.conf pfctl -f /etc/pf.conf -e
1234netsh advfirewall firewall add rule name="CanisterWorm-C2-IP" dir=out action=block remoteip=143.198.237.25 # Domain-based blocking on Windows requires a WFP filter or upstream DNS sinkhole — # if your fleet uses a DNS RPZ, push telemetry.api-monitor.com, cjn37-uyaaa-aaaac-qgnva-cai.raw.icp0.io, # and tdtqy-oyaaa-aaaae-af2dq-cai.raw.icp0.io there.
1234567# Exit 0 = signature + provenance both pass against the pinned identity. genie sec verify-install # Full scan, persisted JSON report, all home directories, filesystem root. # GENIE_SEC_SCAN_DISABLED must be unset for this call. unset GENIE_SEC_SCAN_DISABLED genie sec scan --all-homes --root / --json
v6 note: in v6, verify the release withscripts/verify-release.shand replace the scan with the manual checks in §2, keeping their output as your evidence.
scan_id from the output — every subsequent step references it.genie sec remediate is dry-run by default. Generate the plan, read it, then apply.v6 note: v6 has nogenie sec remediate. Remove persistence and purge the caches by hand as §4.1 and §4.2 describe, and copy every file before you delete it.
12345678910SCAN_ID=<paste-scan-id-from-step-3> # Dry run — materializes a frozen plan manifest. genie sec remediate --dry-run --scan-id "$SCAN_ID" # Review the plan: every action class is listed with the target and exit criteria. cat "$GENIE_HOME/sec-scan/$SCAN_ID/plan.json" | jq '.actions[] | {type, target, reason}' # Apply — typed per-action consent is required interactively. genie sec remediate --apply --plan "$GENIE_HOME/sec-scan/$SCAN_ID/plan.json"
--apply aborts partway through, resume with:1genie sec remediate --resume "$GENIE_HOME/sec-scan/$SCAN_ID/resume.json"
--apply completed but broke something, see §11.6 Escalation — rollback.| # | Target | Rotate URL | Verification |
| 1 | npm token | https://www.npmjs.com/settings/~/tokens | npm whoami with new token |
| 2 | GitHub PAT + gh CLI | https://github.com/settings/tokens | gh auth status |
| 3 | AWS access keys | AWS IAM Console | aws sts get-caller-identity |
| 4 | GCP | gcloud auth revoke --all && gcloud auth login | gcloud auth list |
| 5 | Azure | az logout && az login + rotate service principals | az account show |
| 6 | Kubernetes | Rotate service-account tokens + kubeconfig contexts | kubectl auth whoami |
| 7 | Docker registries | https://hub.docker.com/settings/security (and ECR/GCR/GHCR) | docker login <registry> |
| 8 | AI provider keys (Anthropic / OpenAI / Google) | Provider console | Audit billing last 72h |
| 9 | Crypto wallets | New seed on a clean device; move funds | revoke.cash approvals audit |
| 10 | TLS private keys on host | Re-issue via your CA | Verify cert chain on endpoint |
genie sec remediate --apply emits a per-host rotation checklist at the end of its run; the table above is the fleet-level order across hosts.v6 note: withoutgenie sec remediate, the table above is the whole rotation checklist.
123# Preferred: restore from a snapshot/image predating 2026-04-21. zfs list -t snapshot | awk '$1 ~ /@2026-04-2[01]/' aws ec2 describe-snapshots --owner-ids self --filters Name=start-time,Values=2026-04-20*
@automagik/genie from the current stable line, run genie sec verify-install, and restore workload state from your backup channel (not from the compromised host).v6 note: to reinstall Genie on the rebuilt host, useinstall.shfrom Installation: it verifies the signed release before it unpacks anything. v6 has nogenie sec verify-install.
scan_id from Step 3; the audit log under $GENIE_SEC_AUDIT_LOG contains the full action trail keyed off that id.v6 note: v6 keeps no scan id and no audit log, so record each action and its time by hand as you go.
genie sec scan returned LIKELY AFFECTED. Malicious versions were installed or cached. Postinstall execution cannot be ruled out, but no live process, persistence unit, or dropped payload was observed.123456789101112131415# bun cache — versions 4.260421.33 through 4.260421.40 for v in 33 34 35 36 37 38 39 40; do rm -rf ~/.bun/install/cache/@automagik/genie@4.260421.${v}@@@1 rm -rf ~/.cache/.bun/install/cache/@automagik/genie@4.260421.${v}@@@1 done # pgserve — 1.1.11 through 1.1.14 for v in 11 12 13 14; do rm -rf ~/.bun/install/cache/pgserve@1.1.${v}@@@1 rm -rf ~/.cache/.bun/install/cache/pgserve@1.1.${v}@@@1 done # Globally installed copies bun pm uninstall -g @automagik/genie pgserve 2>/dev/null || true npm uninstall -g @automagik/genie pgserve 2>/dev/null || true
123456genie sec verify-install genie sec scan --all-homes --root "$PWD" --json > /tmp/rescan.json # Status MUST now be NO FINDINGS. If it still reports LIKELY AFFECTED: # a cache entry was missed. Re-run Step 1. jq -r '.status' /tmp/rescan.json
v6 note: in v6, re-run the cache checks in §2.3 and §2.4 instead. They must print nothing.
12bun install -g @automagik/genie@^4.260422.4 bun install -g pgserve@^1.1.10
v6 note: these commands install4.xpackages. To install Genie v6, useinstall.shfrom Installation, which verifies the signed release before it unpacks anything.
genie sec scan returned OBSERVED ONLY. Only passive references (cache index without unpacked contents, lockfile entries, shell history) were found. No dropped payload, no persistence, no live process.12345678910# bun cache (even if empty manifests, clear the names) rm -rf ~/.bun/install/cache/@automagik/genie@4.260421.*@@@1 rm -rf ~/.bun/install/cache/pgserve@1.1.1[1-4]@@@1 # Shell history entries mentioning compromised versions for h in ~/.bash_history ~/.zsh_history; do [ -f "$h" ] || continue cp -a "$h" "${h}.pre-canisterworm.bak" grep -vE 'genie@4\.260421\.(3[3-9]|40)|pgserve@1\.1\.(1[1-4])' "${h}.pre-canisterworm.bak" > "$h" || true done
123genie sec scan --all-homes --root "$PWD" --json > /tmp/rescan.json # Expected: NO FINDINGS. jq -r '.status' /tmp/rescan.json
v6 note: in v6, re-run the cache checks in §2.3 instead. They must print nothing.
OBSERVED ONLY means we have no evidence the postinstall script executed. Skip credential rotation unless later evidence (shell history showing an install + invoke, auditd execve of node scripts/env-compat.cjs, egress logs to a C2 host) surfaces. If any of those appears, re-classify as LIKELY AFFECTED and run §11.4.genie sec rollback <scan_id> when genie sec remediate --apply completed but broke something on the host. Rollback walks the audit log in reverse, restoring every quarantined item to its original path with sha256-verified content.v6 note: v6 has nogenie sec rollback,quarantineorrestore. Restore a file from the copy you made before you deleted it.
123456# Bulk rollback: walks $GENIE_SEC_AUDIT_LOG in reverse for this scan_id. genie sec rollback "$SCAN_ID" # Per-item: if you only need to restore a specific quarantine id. genie sec quarantine list genie sec restore <quarantine-id>
--unsafe-unverifiedgenie sec remediate --apply refuses to run unless genie sec verify-install returned exit 0. The --unsafe-unverified <INCIDENT_ID> flag is the only documented escape hatch. Every invocation is written to $GENIE_SEC_AUDIT_LOG with the incident id, the typed ack, and the reason.v6 note: v6 has no--unsafe-unverifiedflag, and no supported flag bypasses a failed verification. Ifscripts/verify-release.shfails for a Genie build, do not run that build, and escalate through the contacts in §8.
--unsafe-unverified is legitimate--unsafe-unverified is a correct operator choice.docs/security/key-rotation.md). verify-install returns exit 3 (signer-identity-mismatch). The host needs remediation now; rotation will take hours.12345# Incident id MUST come from the pinned rotation issue; do not invent one. genie sec remediate --apply \ --plan "$GENIE_HOME/sec-scan/$SCAN_ID/plan.json" \ --unsafe-unverified "SIGNING_CERT_IDENTITY_20260423" # Typed ack prompt: I_ACKNOWLEDGE_UNSIGNED_GENIE_SIGNING_CERT_IDENTITY_20260423
123jq -r 'select(.event=="remediate.apply.start" and .unsafe_unverified != null)' \ "$GENIE_SEC_AUDIT_LOG" # Expect a single entry whose incident_id matches the rotation issue number.
genie-supply-chain-signing cutover and does not ship a signed tarball. verify-install returns exit 5 (no signature material found). Common during staged rollouts between 4.260423.x and 4.260424.x.1234genie sec remediate --apply \ --plan "$GENIE_HOME/sec-scan/$SCAN_ID/plan.json" \ --unsafe-unverified "PRE_SIGNING_CHANNEL_260423" # Typed ack prompt: I_ACKNOWLEDGE_UNSIGNED_GENIE_PRE_SIGNING_CHANNEL_260423
scripts/test-runbook.sh and the CI workflow exercise remediate --apply against a fixture on an unsigned development tarball. The INCIDENT_ID is a fixed sentinel recognized by the harness.12345genie sec remediate --apply \ --plan "$FIXTURE_DIR/plan.json" \ --unsafe-unverified "TEST_HARNESS_CANISTERWORM_FIXTURE" \ --auto-confirm-from "$FIXTURE_DIR/consent.json" # Typed ack prompt: I_ACKNOWLEDGE_UNSIGNED_GENIE_TEST_HARNESS_CANISTERWORM_FIXTURE
--unsafe-unverified is NOT legitimateverify-install exits 5, use Context 2.--unsafe-unverified invocation whose incident id does not map to one of the three legitimate contexts is treated as an incident of its own by post-hoc review.scan_id ties it to the persisted scan JSON, the audit log, and the remediation plan manifest.v6 note: on a v6 host, replace the scan id, the plan manifest and the audit log with the output of the manual checks and your own timeline.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152# Post-mortem: CanisterWorm exposure on <host or fleet scope> ## Metadata - Scan id: <paste from `genie sec scan --json`> - Scan bands hit: LIKELY COMPROMISED / LIKELY AFFECTED / OBSERVED ONLY (delete two) - Detection time: <ISO-8601 UTC> - Containment time: <ISO-8601 UTC> - Runbook branch: §11.3 / §11.4 / §11.5 ## Exposure - Affected versions installed: <list @automagik/genie + pgserve versions> - Install method: <bun / npm / CI / auto-update> - Window of exposure: <from when the compromised version landed to when the host was contained> - Material at risk: <paste `at-risk local material present on host` from scan report> ## Actions taken - Scan report: $GENIE_HOME/sec-scan/<scan_id>/report.json - Plan manifest: $GENIE_HOME/sec-scan/<scan_id>/plan.json - Audit log: $GENIE_SEC_AUDIT_LOG (filter on scan_id) - Rollback used? yes / no (if yes, reason) - --unsafe-unverified used? yes / no (if yes, which legitimate context from §11.7) ## Credential rotation - npm: rotated at <time> · verified with `npm whoami` - GitHub: rotated at <time> · verified with `gh auth status` - AWS: rotated at <time> · verified with `aws sts get-caller-identity` - GCP: rotated at <time> - Azure: rotated at <time> - Kubernetes: rotated at <time> - Docker: rotated at <time> - AI keys: rotated at <time> · 72h billing audit clean? yes / no - Crypto: wallets moved to new seed on clean device? yes / no / N/A - TLS: certs re-issued? yes / no / N/A ## Timeline - <ISO> Scanner flagged host. - <ISO> Egress blocked at perimeter. - <ISO> `genie sec remediate --dry-run --scan-id ...` reviewed. - <ISO> `genie sec remediate --apply ...` completed. - <ISO> Credential rotation completed. - <ISO> Host rebuilt / snapshot restored. - <ISO> Re-scan returned NO FINDINGS. ## Lessons learned - <what the compromise window cost> - <what would have shortened detection> - <what policy/tooling change lands this week> ## Attachments - Process snapshot: /tmp/ps-snapshot-*.txt - Re-scan report: /tmp/rescan.json - C2 egress logs: <link>