sync: auto-sync from HOWARD-HOME at 2026-06-21 20:56:44
Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-06-21 20:56:44
This commit is contained in:
@@ -23,6 +23,7 @@ set -u
|
||||
|
||||
TARGET="${1:-}"
|
||||
PHASE="${2:-all}"
|
||||
SCANNER_ARG="${3:-}"
|
||||
|
||||
if [ -z "$TARGET" ]; then
|
||||
echo "[ERROR] Usage: bash guruscan-agent-test.sh <hostname|uuid> <prep|scan|collect|all>" >&2
|
||||
@@ -530,11 +531,63 @@ PS
|
||||
post_alert "[RMM] GuruScan verify-each on $AGENT_HOST complete - see per-engine detect/remove matrix"
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
# scan-one <Scanner>: fully automated single-scanner test. ONE command does it
|
||||
# all, hands-off: restore the malware-lab samples, clear stale cleanup state,
|
||||
# run the scanner DETACHED + NO-CAP via the same path production uses, then
|
||||
# collect and report (detections, removal, results.json, reboot-cleanup task).
|
||||
# Mirrors how we proved Emsisoft -- no manual stitching.
|
||||
# ===========================================================================
|
||||
phase_scan_one() {
|
||||
local scanner="${SCANNER_ARG:-HitmanPro}"
|
||||
echo ""; echo "=== PHASE: scan-one ($scanner) - automated, detached, no-cap ==="
|
||||
|
||||
# Setup: free the mutex (kill any prior GuruScan run + scanner procs), clear
|
||||
# stale cleanup task/state, and restore the malware lab samples.
|
||||
local sf="$WORK_DIR/one_setup.ps1"
|
||||
cat > "$sf" <<'PS'
|
||||
$ErrorActionPreference='Continue'
|
||||
Get-ScheduledTask -EA SilentlyContinue | Where-Object { $_.TaskName -like 'GuruScan-*' } | ForEach-Object { try{ Stop-ScheduledTask -TaskName $_.TaskName -EA SilentlyContinue }catch{}; try{ Unregister-ScheduledTask -TaskName $_.TaskName -Confirm:$false -EA SilentlyContinue }catch{} }
|
||||
foreach($n in @('a2cmd','HitmanPro_x64','rkill','EmsisoftCommandlineScanner64')){ Get-Process -Name $n -EA SilentlyContinue | Stop-Process -Force -EA SilentlyContinue }
|
||||
Start-Sleep 3
|
||||
Get-ScheduledTask -TaskName 'GuruRMM-ScannerCleanup' -EA SilentlyContinue | Unregister-ScheduledTask -Confirm:$false -EA SilentlyContinue
|
||||
Remove-Item 'C:\GuruScan\cleanup-state.json' -Force -EA SilentlyContinue
|
||||
$zip = Get-ChildItem 'C:\Users\Owner\Downloads' -Filter '*.zip' -EA SilentlyContinue | Where-Object { $_.Name -match 'malware' } | Select-Object -First 1
|
||||
if($zip){ Expand-Archive -Path $zip.FullName -DestinationPath 'C:\Users\Owner\Desktop' -Force -EA SilentlyContinue }
|
||||
$n=(Get-ChildItem 'C:\Users\Owner\Desktop\malware-samples-master' -Recurse -File -EA SilentlyContinue).Count
|
||||
Set-Content 'C:\GuruScan\_one_before.txt' $n
|
||||
Write-Output ("setup done - malware samples present: $n")
|
||||
PS
|
||||
run_ps "$sf" 240 70 "setup" || { echo "[ERROR] setup failed"; return 1; }
|
||||
|
||||
# Run the scanner the production way: detached scheduled task, unlimited time.
|
||||
gs_launch_detached "-Scanners $scanner -Headless" "one" || { echo "[ERROR] launch failed"; return 1; }
|
||||
gs_wait_detached "one" "$scanner" || true
|
||||
|
||||
# Report the outcome (parsed from what GuruScan actually wrote).
|
||||
local rf="$WORK_DIR/one_report.ps1"
|
||||
cat > "$rf" <<'PS'
|
||||
$ErrorActionPreference='Continue'
|
||||
$before=Get-Content 'C:\GuruScan\_one_before.txt' -EA SilentlyContinue
|
||||
$after=(Get-ChildItem 'C:\Users\Owner\Desktop\malware-samples-master' -Recurse -File -EA SilentlyContinue).Count
|
||||
Write-Output ("samples: before=$before after=$after REMOVED=" + ([int]$before-[int]$after))
|
||||
$d=Get-ChildItem 'C:\ScanLogs' -Directory -EA SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
if($d -and (Test-Path (Join-Path $d.FullName 'results.json'))){ $r=Get-Content (Join-Path $d.FullName 'results.json') -Raw|ConvertFrom-Json
|
||||
Write-Output ('results.json -> total_threats=' + $r.total_threats + ' reboot_required=' + $r.reboot_required)
|
||||
$r.scanners|ForEach-Object{ Write-Output (' ' + $_.name + ': exit=' + $_.exit_code + ' threats=' + $_.threats_found) } }
|
||||
$ct=Get-ScheduledTask -TaskName 'GuruRMM-ScannerCleanup' -EA SilentlyContinue
|
||||
if($ct){ Write-Output ('reboot-cleanup task -> REGISTERED (state=' + $ct.State + ', logon-delay=' + $ct.Triggers[0].Delay + ')') } else { Write-Output 'reboot-cleanup task -> NOT registered' }
|
||||
PS
|
||||
run_ps "$rf" 60 24 "report" || true
|
||||
post_alert "[RMM] GuruScan automated scan-one ($scanner) complete on $AGENT_HOST"
|
||||
}
|
||||
|
||||
case "$PHASE" in
|
||||
prep) phase_prep ;;
|
||||
scan) phase_scan ;;
|
||||
scan-one) phase_scan_one ;;
|
||||
collect) phase_collect ;;
|
||||
verify-each) phase_verify_each ;;
|
||||
all) phase_prep && phase_scan && phase_collect ;;
|
||||
*) echo "[ERROR] Unknown phase '$PHASE' (prep|scan|collect|verify-each|all)" >&2; exit 1 ;;
|
||||
*) echo "[ERROR] Unknown phase '$PHASE' (prep|scan|scan-one <Scanner>|collect|verify-each|all)" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
## User
|
||||
- **User:** Howard Enos (howard)
|
||||
- **Machine:** Howard-Home
|
||||
- **Role:** tech
|
||||
|
||||
## Session Summary
|
||||
|
||||
Finalized and validated the new `screenconnect` skill (ConnectWise Control / ScreenConnect
|
||||
RESTful API Manager extension), built earlier in the parent session the same way as the
|
||||
`bitdefender` skill: gather the full API surface, then test until everything works. This
|
||||
session picked up at the validation/finalization stage.
|
||||
|
||||
Ran the `skill-creator` skill against the screenconnect skill to confirm rule compliance.
|
||||
All checks passed except one: the SKILL.md did not document the errorlog behavior (the
|
||||
skill-creator mandate wants an explicit line, even though the CLI code already logs genuine
|
||||
failures and skips expected conditions). Added an "Error logging" section to SKILL.md.
|
||||
Cleaned up stale "pending unlock" help text in `sc.py` (the control methods are verified
|
||||
live now, so the text was inaccurate). Re-ran the selftest: 12/12 passing. Committed the
|
||||
skill finalization to the parent repo (`3a1edb7`).
|
||||
|
||||
Captured the RMM-integration vision as **Feature 7** in the GuruRMM `RMM_THOUGHTS.md`:
|
||||
when a device is flagged for ScreenConnect in the RMM, the RMM builds the correct
|
||||
parameterized access installer from the device's client/site/tags, pushes it via the agent
|
||||
so the SC client self-tags into the right Company/Site/Tag, then controls the session and
|
||||
keeps the SC custom properties in sync. Status: Raw; needs Mike's go before building.
|
||||
|
||||
Per Howard's choice, did NOT tear down the SC test on RMM-TEST-MACHINE — kept the
|
||||
ScreenConnect Client service running and the test session live as a fixture for the
|
||||
upcoming RMM-integration phase. Only removed the two stray test temp files
|
||||
(`sc-control-test.txt`, `sc-access.msi`) via an RMM PowerShell command; posted the required
|
||||
`[RMM]` write-op alert to #dev-alerts.
|
||||
|
||||
Fixed two flags raised at the end of the skill work, both in the `guru-rmm` submodule's
|
||||
`RMM_THOUGHTS.md`: (1) the Feature 7 edit was uncommitted in a detached-HEAD submodule
|
||||
full of other sessions' in-progress files; (2) the file contained 3 raw NUL bytes that
|
||||
made it read as binary to grep. Delivered both fixes on an isolated branch off origin/main
|
||||
(`docs/rmm-thoughts-sc-feature7`, commit `4f10149`, pushed) without disturbing the parent
|
||||
gitlink or the other 8 dirty files. The NUL bytes turned out to be intentional `^@`
|
||||
examples inside a jsonb-bug writeup, so they were escaped to readable `\x00` rather than
|
||||
deleted (meaning preserved).
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- Ran skill-creator as a validation pass against an existing skill (not to create a new
|
||||
one) — used its Quality Checklist + mandatory-errorlog rule as the rubric.
|
||||
- Kept SC installed on RMM-TEST-MACHINE rather than tearing down: the upcoming
|
||||
GuruRMM<->ScreenConnect integration (Feature 7) needs exactly that fixture. Only removed
|
||||
test litter (marker file + downloaded msi).
|
||||
- Delivered the submodule doc change via a branch off origin/main rather than committing in
|
||||
the detached-HEAD checkout — committing on detached HEAD amid 8 other dirty files risks
|
||||
orphaning their work. Branch handed off for Mike to merge (the standard submodule
|
||||
workflow: merge = deploy).
|
||||
- Reset the redundant uncommitted Feature 7 out of the shared dirty checkout (set the live
|
||||
file back to origin/main) so it cannot double-merge once the branch lands. The other 8
|
||||
modified files were left untouched.
|
||||
- Escaped the NUL bytes to `\x00` instead of stripping them: cat -v revealed they were
|
||||
deliberate illustrative `^@` characters in a Postgres jsonb-NUL bug writeup; stripping
|
||||
would have turned "contains a `^@` (NUL)" into empty backticks and lost the point.
|
||||
|
||||
## Problems Encountered
|
||||
|
||||
- skill-creator validation surfaced one deviation (SKILL.md not documenting errorlog
|
||||
behavior) -> added an Error-logging section to SKILL.md.
|
||||
- RMM command dispatch failed twice with "invalid escape" / JSON parse errors because the
|
||||
PowerShell payload used Windows backslash paths inside the JSON body -> switched to
|
||||
forward-slash paths (PowerShell accepts them), command then ran clean.
|
||||
- NUL-byte replacement repeatedly no-op'd: the Bash tool's heredoc was collapsing `\x00`
|
||||
escapes before Python's string parser saw them, so both the search and replacement
|
||||
became a bare NUL (replace = no-op). Confirmed via an isolated in-memory test, then fixed
|
||||
by writing a real script file (`.nulfix.py`) that builds the bytes numerically
|
||||
(`bytes([0])` and `bytes([0x5C,0x78,0x30,0x30])`) with no backslash escapes in source.
|
||||
- `git worktree remove` failed with "Permission denied" (Windows file lock) -> force-removed
|
||||
the directory with PowerShell `Remove-Item -Recurse -Force`, then `git worktree prune`.
|
||||
Important because the worktree lived under C:\claudetools\ and would have been swept by
|
||||
/save's `git add -A`.
|
||||
|
||||
## Configuration Changes
|
||||
|
||||
Modified / committed (parent repo, commit `3a1edb7`):
|
||||
- `.claude/skills/screenconnect/scripts/sc.py` — removed stale "pending unlock" help text.
|
||||
|
||||
Modified earlier in parent session, already committed before this session:
|
||||
- `.claude/skills/screenconnect/SKILL.md` (incl. the Error-logging section added this
|
||||
session), `scripts/sc_client.py`, `scripts/selftest.py`, `references/api-reference.md`.
|
||||
|
||||
guru-rmm submodule (branch `docs/rmm-thoughts-sc-feature7`, commit `4f10149`, pushed):
|
||||
- `docs/RMM_THOUGHTS.md` — added Feature 7 (ScreenConnect parameterized deploy + control)
|
||||
and index line; escaped 3 raw NUL bytes to `\x00`.
|
||||
|
||||
Removed (scratch): `C:\claudetools\.nulfix.py`, `C:\claudetools\.nultest.md`,
|
||||
worktree `C:\claudetools\.gr-wt-feature7`.
|
||||
|
||||
## Credentials & Secrets
|
||||
|
||||
No new credentials. The screenconnect skill loads its API secret from the SOPS vault
|
||||
`msp-tools/screenconnect.sops.yaml` field `credentials.api_secret` (never hardcoded);
|
||||
auth is the `CTRLAuthHeader` (raw secret, no "Basic" prefix) + `Origin` header.
|
||||
|
||||
## Infrastructure & Servers
|
||||
|
||||
- ScreenConnect instance: `https://computerguru.screenconnect.com`; RESTful API Manager
|
||||
extension GUID `2d558935-686a-4bd0-9991-07539f5fe749`.
|
||||
- RMM-TEST-MACHINE active agent id `99d6d692-99e0-4359-9f9c-f43be89f49e5` (a stale
|
||||
re-enrollment `7d3456f5-...` also exists; last_seen distinguishes them). SC client
|
||||
service on it: `ScreenConnect Client (1912bf3444b41a08)` = Running (kept as fixture).
|
||||
- SC test session: `a5d867ab-cbf2-4b00-99a0-80b082ec5ddd` (kept live).
|
||||
- GuruRMM API `http://172.16.3.30:3001`; Gitea remote
|
||||
`https://git.azcomputerguru.com/azcomputerguru/gururmm.git`.
|
||||
|
||||
## Commands & Outputs
|
||||
|
||||
- Selftest: `CLAUDETOOLS_ROOT=C:/claudetools python scripts/selftest.py` -> 12/12 passed.
|
||||
- RMM cleanup dispatch (forward-slash paths) -> command `d935cd06-...`, completed exit 0:
|
||||
`REMOVED: C:/Windows/Temp/sc-control-test.txt, C:/Windows/Temp/sc-access.msi` /
|
||||
`SC SERVICE KEPT: ScreenConnect Client (1912bf3444b41a08) = Running`.
|
||||
- NUL fix verify: source NULs 3 -> dst NULs 0, 3 literal `\x00` tokens, spans read
|
||||
"contains a `\x00` (NUL)", "forbids `\x00`", "grep for `\x00`".
|
||||
|
||||
## Pending / Incomplete Tasks
|
||||
|
||||
- Feature 7 branch `docs/rmm-thoughts-sc-feature7` awaits Mike's merge into gururmm main
|
||||
(the branch also escapes the 3 NUL bytes; merging fixes them in origin/main).
|
||||
- GuruRMM<->ScreenConnect integration itself (Feature 7) — needs Mike's go before building.
|
||||
- SC GetSessions full-fleet inventory gap: the RESTful API Manager extension does not expose
|
||||
GetSessions/GetAllSessions/GetSessionGroups ("web method does not exist"); needs Mike to
|
||||
update the extension. Tracked via coord msg 60d9e876. By-name lookup works post-install.
|
||||
- SC test fixture remains on RMM-TEST-MACHINE intentionally (client service + session live).
|
||||
|
||||
## Reference Information
|
||||
|
||||
- Parent commit: `3a1edb7` (screenconnect finalize).
|
||||
- Submodule branch/commit: `docs/rmm-thoughts-sc-feature7` / `4f10149` (off origin/main
|
||||
`1dce66d`).
|
||||
- RMM write-op alert message_id `1518461799618449428` (#dev-alerts).
|
||||
- Skill path: `.claude/skills/screenconnect/` (SKILL.md, scripts/{sc.py,sc_client.py,
|
||||
selftest.py}, references/api-reference.md).
|
||||
- ScreenConnect docs: https://docs.connectwise.com/ScreenConnect_Documentation
|
||||
Reference in New Issue
Block a user