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:-}"
|
TARGET="${1:-}"
|
||||||
PHASE="${2:-all}"
|
PHASE="${2:-all}"
|
||||||
|
SCANNER_ARG="${3:-}"
|
||||||
|
|
||||||
if [ -z "$TARGET" ]; then
|
if [ -z "$TARGET" ]; then
|
||||||
echo "[ERROR] Usage: bash guruscan-agent-test.sh <hostname|uuid> <prep|scan|collect|all>" >&2
|
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"
|
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
|
case "$PHASE" in
|
||||||
prep) phase_prep ;;
|
prep) phase_prep ;;
|
||||||
scan) phase_scan ;;
|
scan) phase_scan ;;
|
||||||
|
scan-one) phase_scan_one ;;
|
||||||
collect) phase_collect ;;
|
collect) phase_collect ;;
|
||||||
verify-each) phase_verify_each ;;
|
verify-each) phase_verify_each ;;
|
||||||
all) phase_prep && phase_scan && phase_collect ;;
|
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
|
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