Files
claudetools/.claude/skills/bitdefender/SKILL.md
Howard Enos db6aa3683f fix(bitdefender): all-clients sweep, quarantine path, EDR controls, self-test
Several bugs found and fixed during live testing against the ACG GravityZone
tenant:
- security_sweep_all_clients: iterate each company (the companies container is
  not a valid endpoint parent; passing it 400'd the whole sweep)
- list_quarantine: use service-scoped path quarantine/computers with companyId
  (bare quarantine module 404'd; param is companyId not parentId)
- rename GZEndpointSummary.detection_active -> threat_detected with corrected
  semantics (True = active threat, tracks with infected; not an engine-on flag)
- status: readable sectioned table renderer for the nested apiKey/license dict
- portable CLAUDETOOLS_ROOT resolution (derive from file path, not a Windows
  literal) so it works on the Mac/Linux fleet

Adds scripts/selftest.py: a 29-check read-only harness (all passing) covering
every read command, --json, error exit codes, and destructive-action gating.
EDR/incident commands (blocklist, isolate/unisolate, blocklist-add/remove) and
raw destructive-method gating are included from this session's work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 07:29:55 -07:00

169 lines
6.7 KiB
Markdown

---
name: bitdefender
description: >-
Manage the Arizona Computer Guru (ACG) Bitdefender GravityZone Cloud MSP
tenant via the Public JSON-RPC API. Inventory and audit endpoints, run live
security sweeps (infected / outdated-signature / outdated-product), list
client companies, build and fetch installation packages, manage custom groups,
start scans, move/delete endpoints (gated), inspect policies (read-only,
shallow), and review quarantine. Invoke for: "bitdefender", "gravityzone",
"gravity zone", "add machine to bitdefender", "install bitdefender on",
"list endpoints", "infected machines", "av coverage", "security sweep",
"endpoint protection", "policy assignment", "quarantine". This skill talks to
the real production ACG GravityZone partner tenant — treat destructive actions
conservatively.
---
# Bitdefender GravityZone Skill
Standalone CLI client for the GravityZone Cloud Public API (JSON-RPC). Talks to
the live ACG partner tenant. Read-only by default; destructive operations are
gated behind `--confirm`.
## Running the CLI
This machine's Python launcher is `py` (per identity.json). The scripts also
work with `python`/`python3`.
```bash
# from the scripts dir, or pass full paths
py "C:/claudetools/.claude/skills/bitdefender/scripts/gz.py" status
py "C:/claudetools/.claude/skills/bitdefender/scripts/gz.py" companies
py "C:/claudetools/.claude/skills/bitdefender/scripts/gz.py" sweep --company <id> --json
```
Transport auto-selects: uses `httpx` if installed, otherwise stdlib `urllib`
(no third-party dependency required).
## Credentials
The API key is NEVER hardcoded. At runtime the client loads it from the SOPS
vault:
```
bash "$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh" \
get-field msp-tools/gravityzone.sops.yaml credentials.api_key
```
`CLAUDETOOLS_ROOT` resolves from the env var, else `claudetools_root` in
`C:/claudetools/.claude/identity.json`, else `C:/claudetools`. For testing you
can override with `GRAVITYZONE_API_KEY`. Auth is HTTP Basic (key as username,
empty password).
## Cache model (important)
The CLI keeps a local cache at
`.claude/skills/bitdefender/.cache/inventory.json` (gitignored — no secrets, no
PII).
- **Cached (identity / structure tier):** company id<->name map, endpoint
id<->name/company/fqdn map, policy id<->name map, package list, and custom
groups created via this tool. TTL = 86400s (24h).
- **NEVER cached (volatile):** infected status, last-seen, online/offline,
signature/product freshness. Those are ALWAYS pulled live — `sweep` and
`endpoint` always hit the API.
- **Refresh:** `inventory --refresh` forces a full re-pull. `get_inventory()`
auto-refreshes when the cache is stale.
- **Write-through:** a successful `create-package` or `make-group` updates the
cache with the new id immediately, so you don't need a full refresh to
reference it.
## Policy API limitation
The Public API exposes policies only shallowly. You CAN list policies, read
their id/name, audit which endpoints carry which policy (via endpoint detail),
and — via the UNVERIFIED `assignPolicy` — assign an existing policy. You CANNOT
read the granular module configuration of a policy, and there is NO create /
edit / clone policy method in the Public API. For policy authoring, use the
GravityZone console.
## Safety gating
Destructive subcommands refuse to run without `--confirm`; without it they print
what they would do and exit non-zero:
- `delete-endpoint <id> --confirm`
- `delete-package --package <name> --confirm`
- `delete-group --group <id> --confirm`
- `isolate --endpoints <id> ... --confirm` (cuts the endpoint off the network; reversible via `unisolate`)
- `unisolate --endpoints <id> ... --confirm`
- `blocklist-add --company <id> --hashes <h> ... --confirm`
- `blocklist-remove --id <hashItemId> --confirm`
Never run destructive calls casually against this tenant. UNVERIFIED methods
(assignPolicy, uninstall/reconfigure tasks, quarantine remove/restore, set
label) are intentionally NOT exposed as dedicated subcommands — reach them only
through `raw` after confirming the correct params against
`references/api-reference.md` and the official Bitdefender docs.
`raw` itself refuses destructive method names (delete/uninstall/remove/
reconfigure, plus the EDR verbs isolat*/addToBlocklist/removeFromBlocklist)
unless `--confirm` is passed. Note that `raw` prints the upstream
response verbatim — it can carry data from the called method, so do not paste
raw output into tickets/logs without review.
## Common commands
```bash
GZ="py C:/claudetools/.claude/skills/bitdefender/scripts/gz.py"
# Status / inventory
$GZ status
$GZ companies
$GZ inventory --refresh
$GZ endpoints --company <companyId>
$GZ endpoint <endpointId>
# Live security posture
$GZ sweep --company <companyId> # readable table
$GZ sweep --company <companyId> --json # machine output
# Policies (read-only, shallow)
$GZ policies
$GZ policy <policyId>
# Quarantine
$GZ quarantine --company <companyId>
# Deployment
$GZ packages
$GZ create-package --name "Win Default" --company <companyId>
$GZ install-links --package "Win Default" --company <companyId>
# Org structure
$GZ make-group --name "New Site" --parent <parentId>
$GZ move --endpoints <id1> <id2> --group <groupId>
# Scans
$GZ scan --targets <id1> <id2> --type 2 --name "Full scan"
# EDR / incident response
$GZ blocklist # list blocklisted hashes (whole tenant)
$GZ blocklist --company <companyId> # scope to one company
$GZ incidents --company <companyId> # list incidents (parentId required; method UNVERIFIED on this tenant - may return "Method not found")
$GZ isolate --endpoints <id1> <id2> --confirm # cut endpoint(s) off the network (reversible via unisolate)
$GZ unisolate --endpoints <id1> <id2> --confirm # restore endpoint(s) from isolation
$GZ blocklist-add --company <companyId> --hashes <h1> <h2> --hash-type 1 --source-info "..." --confirm
$GZ blocklist-remove --id <hashItemId> --confirm # id comes from `blocklist` output
# Power use — call any method directly
$GZ raw --module network --method getEndpointsList --params '{"page":1,"perPage":50}'
# Destructive (gated)
$GZ delete-endpoint <id> --confirm
```
## Phase-2 hooks (not yet implemented)
- **GuruRMM push-deploy:** use `install-links` to fetch the platform installer
URL, then push the installer to a target via the GuruRMM agent fleet (`/rmm`)
for one-step Bitdefender rollout from RMM.
- **Push webhook:** subscribe to GravityZone Push events (new malware /
endpoint state changes) and surface them through the coord API / RMM alerts
instead of polling `sweep`.
## Reference
Full verified vs unverified method spec, JSON-RPC envelope, auth, and the
policy/deployment caveats: `references/api-reference.md`.