Session log: cPanel CVE-2026-41940 IOC scan + remediation on IX/WebSvr

Both servers were already patched (11.110.0.97 and 11.134.0.20) via
daily auto-update. IOC scan found 16 flagged sessions across both
plus 4 uncommented SSH keys on IX.

Critical remediation:
- Forensic evidence preserved before any deletion
- 4 uncommented SSH keys removed from IX (server-side backup retained)
- 16 flagged sessions purged across both servers
- Root passwords rotated via chpasswd
- New WHM API tokens created; 3 stale transfer-* tokens revoked
- Vault entries + 1Password Infrastructure items updated

Forensic deep-dive verdict: patch held. All 7 actual CVE exploit
attempts (botnet IPs hitting /json-api/version) returned HTTP 403.
The "multi-line pass" IOC hits on user sessions were false positives.
Unidentified 76.18.103.222 root session traced to routine SSL
maintenance (zero sensitive endpoints touched).

Skill hardening:
- Added MANDATORY service-token directive to .claude/commands/1password.md
  enforcing OP_SERVICE_ACCOUNT_TOKEN from SOPS for all op CLI calls
- Per Mike: memory files alone don't reliably bind agent behavior;
  baking governance into skill content loaded at moment of use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 07:22:52 -07:00
parent f20a9628c3
commit 7128b9e57d
4 changed files with 299 additions and 3 deletions

View File

@@ -34,16 +34,61 @@ Never ask users to paste API keys, passwords, or tokens into:
---
## Setup Check
## ⚠️ MANDATORY: Use the SOPS-vaulted service account token, never the desktop session
Always verify the CLI is ready before any operation:
**Every `op` invocation in agent flows must run with `OP_SERVICE_ACCOUNT_TOKEN` set.** The desktop-app integration prompts to unlock the app, which interrupts the agent flow and is unacceptable. The service token is in the SOPS vault at `infrastructure/1password-service-account.sops.yaml` (vault entry kind=`api-key`, name=`1Password Service Account (Agentic-RW)`).
### Load the token at the start of any 1Password work
```bash
# Decrypt the service token from SOPS (uses the machine's age key)
export OP_SERVICE_ACCOUNT_TOKEN=$(sops -d /c/Users/guru/vault/infrastructure/1password-service-account.sops.yaml 2>/dev/null \
| grep -E '^\s*credential:' | sed -E 's/^\s*credential:\s*//' | head -1)
# Verify
op whoami # expect "User Type: SERVICE_ACCOUNT"
```
After `export`, every subsequent `op` call in the same bash invocation inherits the token. For one-off calls without exporting:
```bash
SVC=$(sops -d /c/Users/guru/vault/infrastructure/1password-service-account.sops.yaml 2>/dev/null | grep -E '^\s*credential:' | sed -E 's/^\s*credential:\s*//' | head -1)
OP_SERVICE_ACCOUNT_TOKEN="$SVC" op item get "Item Name" --vault Infrastructure
```
### Vault path resolution
The vault lives wherever `.claude/identity.json` says (`vault_path`). On the current Windows workstation it's `C:/Users/guru/vault`, but other machines (Howard's, future workstations) may differ. Resolve dynamically when needed:
```bash
VAULT_DIR=$(python -c "import json; print(json.load(open('/c/Users/guru/ClaudeTools/.claude/identity.json'))['vault_path'])")
SVC=$(sops -d "$VAULT_DIR/infrastructure/1password-service-account.sops.yaml" 2>/dev/null | grep -E '^\s*credential:' | sed -E 's/^\s*credential:\s*//' | head -1)
export OP_SERVICE_ACCOUNT_TOKEN="$SVC"
```
### Service account scope (verified 2026-04-30)
The Agentic-RW service account has access to: **Clients, Infrastructure, Internal Sites, Managed Websites, MSP Tools, Projects, Sorting**. The Private vault is intentionally NOT shared with the service account — if you need to read from Private, that's a different conversation, not a fallback to desktop session.
### When the token fails
- `op vault list` returns "account is not signed in" with the token set → token is malformed or revoked. Decrypt directly via `sops -d` and inspect.
- `vault.sh get-field` may fail with "PyYAML not installed" — use direct `sops -d` + grep instead until that wrapper bug is fixed.
- Never fall back to the desktop-app session in agent flows. If the service token is unrecoverable, stop and tell Mike.
---
## Setup Check (only for net-new machine onboarding)
For a fresh workstation that doesn't have the service token wired up yet:
```bash
bash scripts/check_setup.sh
```
If not installed: https://developer.1password.com/docs/cli/get-started/
If not signed in: unlock the **1Password desktop app** (after Mac restart, the app must be unlocked before the CLI works)
The desktop-app sign-in flow is for **interactive human use**, not agent flows — those go through the service account above.
---

View File

@@ -27,6 +27,7 @@
- [Ollama Tier-0 Routing](feedback_ollama_tier0_routing.md) - Route drafts/summaries/classifications through Ollama (qwen3:14b). Mike designed ClaudeTools this way — not optional.
- [Syncro Emergency Billing](feedback_syncro_emergency_billing.md) — Emergency = 1.5× multiplier, not additive. Branch by `customer.prepay_hours`: no-prepaid → `26184` at actual hrs; prepaid → `26118` at hrs×1.5. Never stack. Always set `price_retail`.
- [Identity precedence](feedback_identity_precedence.md) — Trust `.claude/identity.json` over the system-reminder `userEmail` hint when they disagree (shared-login machines).
- [1Password — always use service token](feedback_1password_service_token.md) — Source OP_SERVICE_ACCOUNT_TOKEN from SOPS for every `op` call. Desktop-app integration prompts are unacceptable in agent flows.
## Machine
- [ACG-5070 Workstation Setup](reference_workstation_setup.md) - Windows 11 Pro clean install 2026-03-30, replaced CachyOS. All tools installed.

View File

@@ -0,0 +1,26 @@
---
name: 1Password — always use service account token
description: Use the SOPS-vaulted OP_SERVICE_ACCOUNT_TOKEN for all op CLI calls; the desktop-app integration prompts are unacceptable in agent flows
type: feedback
---
For every `op` CLI invocation, source `OP_SERVICE_ACCOUNT_TOKEN` from `infrastructure/1password-service-account.sops.yaml` first. Without it, `op` falls back to the desktop-app integration which interrupts the workflow with "unlock the app" prompts.
**Why:** Mike confirmed 2026-04-30 — "the prompts are infuriating." Service account auth is the standard CI/agent pattern documented in the 1password skill but I had been defaulting to the desktop session.
**How to apply:**
```bash
SVC_TOKEN=$(sops -d /c/Users/guru/vault/infrastructure/1password-service-account.sops.yaml 2>/dev/null \
| grep -E '^\s*credential:' | sed -E 's/^\s*credential:\s*//' | head -1)
# Pass through env var to every op call
OP_SERVICE_ACCOUNT_TOKEN="$SVC_TOKEN" op item get ...
# Or export once at the top of a script
export OP_SERVICE_ACCOUNT_TOKEN="$SVC_TOKEN"
```
The `vault.sh get-field` wrapper currently fails on this entry due to a missing PyYAML dependency in the wrapper's fallback parser — use direct `sops -d` + grep until that's fixed.
**Vaults the service account can see** (per 2026-04-30 test): Clients, Infrastructure, Internal Sites, Managed Websites, MSP Tools, Projects, Sorting. (The Private vault is intentionally not shared with the service account.)
**When to skip:** Never. If the desktop session also happens to be authed, that's fine, but the service token path must be the one the agent reaches for.

View File

@@ -0,0 +1,224 @@
# 2026-04-30 — cPanel CVE-2026-41940 incident response on IX + WebSvr + 1Password skill hardening
## User
- **User:** Mike Swanson (mike)
- **Machine:** GURU-BEAST-ROG
- **Role:** admin
- **Session span:** 2026-04-29 ~14:30 PT rolling into 2026-04-30 ~07:15 PT (~6 hours of active engagement)
## Session Summary
The session began with a follow-up email to Michelle Sora, pitching a migration from GoDaddy-resold M365 to direct billing with consolidation of `pro-techservices.co` and `pro-techhelps.com` under one tenant. The tone was adjusted to avoid technical jargon and emphasize minimal user impact, with a soft close and call-to-action for a quick call. Final draft was saved to a temp file and opened in Notepad for Mike's review and send.
The bulk of the session focused on responding to **cPanel CVE-2026-41940**, a CRLF injection authentication bypass with CVSS 9.8 actively exploited in the wild since approximately 2026-02-23 per public security research. After verifying both ACG cPanel servers (WebSvr on CentOS 7 + cPanel 11.110.0.97; IX on CloudLinux 9 + cPanel 11.134.0.20) were already patched via daily auto-update, the cPanel-provided IOC detection script was run on both servers. Initial findings showed 7 of 7 flagged sessions on WebSvr and 11 of 16 on IX, including root sessions, plus four uncommented RSA keys in IX's `/root/.ssh/authorized_keys` — the classic attacker-persistence fingerprint.
Critical remediation followed Mike's authorization: forensic preservation of all flagged session files + access logs + last/wtmp output, removal of the four uncommented SSH keys with server-side backup, purge of all 16 flagged session files, root password rotation via `chpasswd`, and creation of new WHM API tokens. SOPS vault entries were updated with the new credentials, committed and pushed (`abfa955`). Per Mike's directive, three `transfer-*` tokens (leftover from past account migrations) were revoked from WebSvr; clustering tokens (`reverse_trust_*`, `NS2DNS`, `IX_DNS_Token`, `WEBSVR_DNS`, `ConfigCluster`, `PARENT-DO_NOT_DELETE-*`) and undocumented `Claude`/`ClaudeToken` tokens were kept.
A subsequent forensic deep-dive cleared the picture significantly. **All seven actual CVE exploit attempts across both servers returned HTTP 403** (the source IPs were DigitalOcean and similar cloud-VPS botnet scanners hitting `/json-api/version` with the injected token) — the patch is working as designed. The "multi-line pass" IOC hits on user sessions turned out to be false positives — those sessions had `method=handle_form_login` origins with normal cPanel UI traffic flagged by an IOC check that has poor specificity on cPanel 134. The unidentified `76.18.103.222` root session on IX (1203 hits Apr 29) was traced to routine SSL maintenance work — 1113 dashboard auto-refresh polls plus one `installssl` call, zero sensitive endpoints touched. Verdict: **patch held, all CVE attempts blocked at the HTTP layer; credential rotation served as defense-in-depth, not breach response.**
The session closed with two pieces of process improvement. First, the new credentials were synced to 1Password's Infrastructure vault — but Mike pushed back on my use of the desktop-app-integrated `op` session, which prompts to unlock the app in agent flows. He pointed out the SOPS-vaulted service account token (`infrastructure/1password-service-account.sops.yaml`) that should be used. After verifying the service token works prompt-free, I saved a feedback memory entry — and Mike pushed back again that memory files alone don't bind agent behavior reliably ("you ignore memory files"). The directive was then baked directly into `.claude/commands/1password.md` as a MANDATORY section at the top of the skill, with exact commands, vault path resolution from `identity.json`, scope details, and failure-mode guidance. Skill is in the synced ClaudeTools repo so when Howard syncs, his workstation gets the same enforcement.
A late report from Mike that the new IX password "doesn't seem to work" was investigated and confirmed to be a copy error on his end — server-side SSH and WHM web login both succeed with the rotated password.
## Key Decisions
- **WHM Transfer Tool migration over in-place ELevate** for any future WebSvr CentOS 7 → AlmaLinux move. Lower risk profile, parallel testing, easy rollback. ELevate makes more sense for AL8→AL9 single-hop later.
- **Preserve forensic evidence before purging sessions** — downloaded raw session files locally before any rm operation. Without that, the deep-dive analysis (which proved exploits were blocked) wouldn't have been possible.
- **Remove uncommented SSH keys despite uncertainty about Rob's identity.** Mike accepted the risk of accidentally cutting Rob off SSH on the basis that re-adding a known key is trivial, while leaving an attacker-style persistence vector in place is not.
- **Keep clustering tokens (reverse_trust_*, NS2DNS, IX_DNS_Token, WEBSVR_DNS, ConfigCluster, PARENT-DO_NOT_DELETE-*).** These are load-bearing for inter-server DNS clustering and account transfers between IX and WebSvr. The initial JSON parse error that turned the bulk-revoke into a no-op was, on reflection, the correct outcome.
- **Kill transfer-\* tokens** (transfer-1749689378, transfer-1765466491, transfer-1766779535) on WebSvr per Mike's directive. These are leftover from past T2T account migrations and serve no current purpose.
- **Howard granted Owner on ACG Azure subscription** (carried forward from prior session, but the rationale stands): matches CLAUDE.md trust model; one-time grant with `gururmm-signing-rg` resource lock + cost alert as guardrails removes Mike as a permanent bottleneck.
- **Use the SOPS-vaulted 1Password service account token for all `op` invocations**, never the desktop-app session. The desktop integration's unlock prompts are unacceptable in agent flows.
- **Bake directives that govern agent behavior into the SKILL files, not memory.** Memory entries are advisory; skill content is loaded at the moment of use and harder to ignore. Confirmed by Mike — "you ignore memory files."
## Problems Encountered
- **`whmapi1 api_token_list_v2` returned "Unknown app" error.** cPanel's API method name was different from what the skill docs implied. Worked around by reading `/var/cpanel/authn/api_tokens_v2/whostmgr/root.json` directly via SFTP+Python.
- **JSON parse error in initial token-revocation script.** I iterated the outer `tokens` key as if it were an item rather than a container of hashed-key items. The result was a no-op revoke (sent the literal name "tokens" to `api_token_revoke`, which silently succeeded as nothing-matched). On reflection this was the safe outcome — a correct parse would have revoked the legitimate clustering tokens and broken the IX↔WebSvr cluster. Caught and re-implemented correctly later.
- **`sops set` flags `--value-stdin` and `--value-file` are not implemented in sops 3.12.2 on Windows** despite being documented in `--help`. Worked around by using the `EDITOR` env var pattern with a small Python script that performs the YAML field replacement, then sops re-encrypts on close.
- **EDITOR path mangling in Git Bash.** Both backslash (`C:\path\script.py`) and forward-slash (`C:/path/script.py`) had different failure modes; forward slashes ultimately worked because Python on Windows accepts them and Git Bash didn't translate them mid-argument.
- **Git Bash MSYS path translation of `/cpsess...` arguments.** When passing a cPanel session token (which begins with `/`) as a command-line argument, Git Bash interpreted it as a path needing translation. Fixed by passing the token without leading slash.
- **`vault.sh get-field` requires PyYAML** which is missing in the wrapper's Python fallback path. Worked around by direct `sops -d` + grep + sed for the rest of the session. Filed mentally as a follow-up to fix the wrapper.
- **Memory file alone wasn't enough.** Mike confirmed I had been ignoring memory entries that documented preferred patterns. Real fix was baking the directive into the skill content itself so it loads at the moment the skill is invoked.
- **IX password "doesn't seem to work" alarm** — investigated end-to-end (SOPS, 1Password, SSH login, WHM HTTP login all verified working with the rotated password). Resolved as a copy-paste error on Mike's end.
## Configuration Changes
### Files created
- `.claude/memory/feedback_1password_service_token.md` — feedback memory entry on always using OP_SERVICE_ACCOUNT_TOKEN
- `session-logs/2026-04-30-session.md` — this file
### Files modified (ClaudeTools repo)
- `.claude/commands/1password.md` — added MANDATORY service-token section near the top, with vault resolution patterns and failure-mode guidance
- `.claude/memory/MEMORY.md` — added pointer to the new feedback entry
### Files modified (vault repo) — committed `f4d3554` rebased to `abfa955`, pushed
- `infrastructure/ix-server.sops.yaml` — root password updated to rotated value
- `infrastructure/websvr-legacy-hosting.sops.yaml` — root password updated, api-token replaced with new value
### 1Password Infrastructure vault items modified
- `WebSvr (Legacy Hosting)` (id `7tv3sgyhzbfpyhld6pyt5gn4li`): `password` and `API Token` fields updated
- `IX Server` (id `brsoqhoalrb4d53jn4lxcj4xdq`): `password` updated, **new `API Token` field added** (didn't exist before)
### Server-side changes (IX, 172.16.3.10)
- 9 flagged session files purged from `/var/cpanel/sessions/raw/`
- 4 uncommented SSH keys removed from `/root/.ssh/authorized_keys` (server-side backup at `/root/.ssh/authorized_keys.bak.20260430T132232Z`)
- Root password rotated via `chpasswd`
- New WHM API token created: `acg_post_cve_20260430T132232Z` = `PA42FSUXASFC0IO9MKH1DUHQ7L5G67PQ`
### Server-side changes (WebSvr, websvr.acghosting.com)
- 7 flagged session files purged from `/var/cpanel/sessions/raw/`
- Root password rotated via `chpasswd`
- New WHM API token created: `acg_post_cve_20260430T132423Z` = `81YBBUPPHZ2EEMWJ42WUN2VIOS01X9U5`
- Three transfer-* tokens revoked: `transfer-1749689378`, `transfer-1765466491`, `transfer-1766779535`
### Temp files (forensic evidence — preserved on local workstation)
- `C:/Users/guru/AppData/Local/Temp/cpanel-ioc-evidence/ix/` — 9 raw session files, access_log snapshot (~110 MB), last/wtmp dump, authorized_keys backup, NEW_ROOT_PASSWORD + NEW_API_TOKEN value files, IOC + forensic logs
- `C:/Users/guru/AppData/Local/Temp/cpanel-ioc-evidence/websvr/` — same structure, 7 session files, ~253 MB access_log
- `C:/Users/guru/AppData/Local/Temp/cpanel-ioc-evidence/session_76.18.103.222_dump.log` — extracted activity timeline for the unidentified IX root session
### Tooling artifacts (also preserved in temp)
- `ssh_check.py` — paramiko-based IOC scan + triage runner
- `ssh_remediate.py` — preserves evidence + removes keys + purges sessions + rotates root + creates new API token
- `fix_api_tokens.py` — secondary token enumeration via direct `root.json` read
- `revoke_transfer_tokens.py` — targeted whmapi1 revoke for specific token names
- `forensic_pass.py` — read-only deep-dive (cp_security_token usage analysis, suspect IP access logs, /etc/passwd + sudoers + SUID + system mod audit, webshell heuristic)
- `session_activity_dive.py` — full access_log extraction for a specific cpsess+IP combo with endpoint clustering and sensitive-keyword flagging
- `op_sync_creds.py` — subprocess-based 1Password item updater (avoids shell quoting issues)
- `sops_editor.py` — EDITOR-mode YAML field setter for SOPS-vaulted files
## Credentials & Secrets (UNREDACTED)
### Rotated 2026-04-30
| Service | Username | Value |
|---|---|---|
| IX server SSH/WHM root | root | `t4qygLl7{1zJcUj#022W^FBQ>}qYp-Od` |
| WebSvr SSH/WHM root | root | `[3H+_f.Yh4c0>@egH[6L!?u]S3s[9C82` |
| IX WHM API token (`acg_post_cve_20260430T132232Z`) | n/a | `PA42FSUXASFC0IO9MKH1DUHQ7L5G67PQ` |
| WebSvr WHM API token (`acg_post_cve_20260430T132423Z`) | n/a | `81YBBUPPHZ2EEMWJ42WUN2VIOS01X9U5` |
All four are stored in 1Password Infrastructure vault and SOPS vault entries. The IX vault entry doesn't currently have an `api-token` field structure — the IX API token is in 1Password but not yet in SOPS.
### Existing references confirmed (not rotated this session)
- `infrastructure/1password-service-account.sops.yaml` — Agentic-RW service token, kind=`api-key`. Used for prompt-free `op` CLI access. Scope: Clients, Infrastructure, Internal Sites, Managed Websites, MSP Tools, Projects, Sorting (Private intentionally excluded).
- WebSvr stale API token (now revoked from server, no longer in vault): `8ZPYVM6R0RGOHII7EFF533MX6EQ17M7O`
## Infrastructure & Servers
### IX Server (172.16.3.10 / `ix.azcomputerguru.com` / public 72.194.62.5)
- OS: CloudLinux 9.7 (TuxCare ELS kernel)
- cPanel: 11.134.0 build 20 (patched for CVE-2026-41940)
- 72 hosting accounts (per `/etc/trueuserdomains`)
- Auth: PAM via /etc/shadow (single password store; SSH and WHM share)
- WHM port 2087, cPanel port 2083, SSH port 22
### WebSvr (`websvr.acghosting.com`)
- External IPs: **162.248.93.78, 162.248.93.81, 162.248.93.233** (the vault entry shows .81; the public-IP probe returned .233)
- OS: CloudLinux 7.9 (CentOS 7 base — past EOL)
- cPanel: 11.110.0 build 97 (patched for CVE-2026-41940)
- 26 hosting accounts
- Same auth model as IX
### Suspect IPs encountered (history captured for future reference)
- `129.222.129.230` — confirmed Mike (today's WHM session on IX)
- `129.222.143.18` — likely Rob (web guy), Dec 15 2025 SSH burst on IX, no WHM activity
- `76.18.103.222` — unidentified WHM root session Apr 29 on IX (1203 hits, all benign — SSL maintenance), historical TPS support workflows match support pubkey ticket IDs (95714774, 95758605); could be Howard, Mike on different network, or a cPanel support tech
- `64.139.88.249` — WebSvr Feb 24 2026 root login from unfamiliar Cox AZ IP (5h 11m), logs rotated out, cannot characterize
- `23.180.120.132`, `143.198.113.39`, `159.65.217.152`, `149.102.229.144`, `195.177.94.161` — botnet scanner IPs (DigitalOcean, etc.) hitting `/json-api/version` with injected tokens. **All HTTP 403, all blocked.**
## Commands & Outputs
### CVE patched-version verification
```bash
/usr/local/cpanel/cpanel -V
# IX: 134.0 (build 20) <-- patched build for v134 stream
# WebSvr: 110.0 (build 97) <-- patched build for v110 stream
cat /etc/cpupdate.conf
# Both: UPDATES=daily <-- patches arrived automatically 2026-04-28
```
### IOC scan (script provided by cPanel)
- Saved at `/tmp/ioc_checksessions_files.sh` on each server (and locally in `cpanel-ioc-evidence/`)
- Output: 9 flagged on IX, 7 on WebSvr; 4 of the 16 were pre-auth `badpass` injection attempts that all later returned HTTP 403 in access_log
### Critical remediation key commands
```bash
# Backup + key removal (IX)
cp -p /root/.ssh/authorized_keys /root/.ssh/authorized_keys.bak.<TS>
sed -i.removed-<TS> '<line_nums>d' /root/.ssh/authorized_keys
# Session purge
for f in <flagged_files>; do rm -f "/var/cpanel/sessions/raw/$f"; done
# Password rotation
echo 'root:<NEWPW>' | chpasswd
# WHM API token rotation
whmapi1 api_token_create token_name='acg_post_cve_<TS>'
whmapi1 api_token_revoke token_name='<old_name>'
```
### 1Password CLI prompt-free auth (the new pattern)
```bash
SVC=$(sops -d /c/Users/guru/vault/infrastructure/1password-service-account.sops.yaml 2>/dev/null \
| grep -E '^\s*credential:' | sed -E 's/^\s*credential:\s*//' | head -1)
export OP_SERVICE_ACCOUNT_TOKEN="$SVC"
op whoami # User Type: SERVICE_ACCOUNT — no prompts
op item get "<id>" --vault Infrastructure --format json # NB: --vault required for service accounts
```
### Verification of rotated IX password (after Mike's "not working" alarm)
```
SSH (paramiko): OK — connected as root, /etc/shadow shows hash dated 20573
WHM web login: HTTP 200, status:1, /cpsess3383026374 issued
SOPS vault value: exact match
1Password value: exact match
```
→ password is correct end-to-end; Mike's "not working" was a copy error on his side.
## Pending / Incomplete Tasks
### Mike's outstanding items
- [ ] Send the Michelle Sora email when ready (`C:/Users/guru/AppData/Local/Temp/michelle-email-draft.txt`)
- [ ] Verify identity of `76.18.103.222` (likely Howard or Mike on a different network — once confirmed, case fully closed on IX)
- [ ] Decide whether to add `api-token` field structure to `infrastructure/ix-server.sops.yaml` and back-fill the new IX WHM API token there (it's in 1Password already)
- [ ] Decide whether to document the `Claude` and `ClaudeToken` WHM API tokens in SOPS or revoke them (currently undocumented + broad ACLs, kept per directive but flagged)
- [ ] Re-add Rob's SSH key to IX once he confirms which of the 4 removed keys was his (server-side backup at `/root/.ssh/authorized_keys.bak.20260430T132232Z`)
### Tracked TODOs (not blocking)
- [ ] Fix `vault.sh get-field` PyYAML dependency in the Python fallback path so the wrapper works for service-account-style entries
- [ ] Eventually run a fuller webshell/integrity scan across all hosted sites (the quick heuristic this session was minimal — only flagged WP core files)
- [ ] Long-term: WebSvr CentOS 7 → AlmaLinux migration via WHM Transfer Tool (separate project, not blocked)
### Items completed this session that close prior threads
- [x] CVE-2026-41940 IOC scan + remediation (both servers)
- [x] Credentials synced to 1Password Infrastructure vault
- [x] 1Password skill hardened with mandatory service-token directive
- [x] Memory entry added for service-token preference
- [x] Vault entries (SOPS) updated and pushed
## Reference Information
### Files / paths
- 1Password skill: `.claude/commands/1password.md` (project-local, takes precedence over the npm openclaw skill)
- Memory entry: `.claude/memory/feedback_1password_service_token.md`
- Memory index: `.claude/memory/MEMORY.md`
- Service account vault entry: `infrastructure/1password-service-account.sops.yaml`
- IX vault entry: `infrastructure/ix-server.sops.yaml`
- WebSvr vault entry: `infrastructure/websvr-legacy-hosting.sops.yaml`
- Forensic evidence directory: `C:/Users/guru/AppData/Local/Temp/cpanel-ioc-evidence/`
### Vault scope (Agentic-RW service account, verified 2026-04-30)
Visible: Clients, Infrastructure, Internal Sites, Managed Websites, MSP Tools, Projects, Sorting
Excluded: Private (intentional)
### CVE-2026-41940 reference
- CVSS 9.8, CRLF injection authentication bypass via session loading
- Affects all cPanel versions after 11.40 including DNSOnly
- Patched in: 11.110.0.97, 11.118.0.63, 11.126.0.54, 11.132.0.29, 11.134.0.20, 11.136.0.5
- Public PoC at watchTowr; active exploitation since ~2026-02-23
- Fix command: `/scripts/upcp --force`
### Why the deep-dive verdict was "patch held"
- Each of the 7 actual exploit attempts had distinct cp_security_tokens that, when grep'd against access_log, appeared exactly once each with HTTP 403 against `/json-api/version` (and /applist, /listwwwacctconf, /get_tweaksetting on one). No HTTP 200 with an injected token from any external IP. The patch's session-validation logic is doing its job.