Batched the audit doc/LOW findings plus the two pagination LOWs: - Pagination (gz_client): security_sweep and refresh_inventory stopped on a 'total' field some responses omit, truncating after page 1. Now page until a short page (< per_page) - the reliable last-page signal. - isolate/restore docstrings (gz_client): removed the stale 'v1.2 takes an ARRAY endpointIds' lines that contradicted the verified single-endpointId code. - Cache 'no PII' wording corrected (gz_client header + SKILL.md): cache holds infra identifiers (hostnames/FQDNs); no secrets. Dead _require_company_for_sweep removed. - Doc drift fixed: delete-package is '--id <packageId>' not '--package <name>' (SKILL.md + api-reference.md, verified live); module docstring + sweep --company help corrected (sweep with no --company fans out to ALL companies). - selftest aligned to the improved behavior: malformed ids now exit rc2 client-side (H3) instead of rc1; gate-refusal 'Would' messages now assert on stderr (they moved off stdout so --json isn't polluted). 75/75 pass; live sweep verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.7 KiB
name, description
| name | description |
|---|---|
| bitdefender | Manage the ACG Bitdefender GravityZone Cloud MSP tenant (Public JSON-RPC API): inventory/audit endpoints, live security sweeps (infected / outdated-signature / outdated-product), client companies, install packages, custom groups, scans, move/delete endpoints (gated), policies (full read + assign), reports, accounts, scan tasks, notifications, push event service, quarantine, EDR (isolate / blocklist). Live production partner tenant — treat destructive actions conservatively. Triggers: bitdefender, gravityzone, install bitdefender on, list endpoints, infected machines, av coverage, security sweep, endpoint protection, assign policy, quarantine, reports, accounts. |
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.
# from the scripts dir, or pass full paths
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" "C:/claudetools/.claude/skills/bitdefender/scripts/gz.py" status
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" "C:/claudetools/.claude/skills/bitdefender/scripts/gz.py" companies
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" "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; it
does hold infra identifiers: endpoint hostnames/FQDNs and id<->name maps).
- 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 —
sweepandendpointalways hit the API. - Refresh:
inventory --refreshforces a full re-pull.get_inventory()auto-refreshes when the cache is stale. - Write-through: a successful
create-packageormake-groupupdates the cache with the new id immediately, so you don't need a full refresh to reference it.
Policy control (corrected 2026-06-21)
Earlier docs claimed policy detail was "shallow." That was WRONG. Verified live:
- READ — full config.
policy <id> --jsonreturns the COMPLETE granular module configuration (general/antimalware/firewall/content-control/etc.), not a shallow subset.policieslists id+name;policy <id>dumps the full tree. - ASSIGN — supported.
assign-policy --policy <id> --targets <ep/group ids>pushes an EXISTING policy onto endpoints/groups (gated behind--confirm). Param shape (policyId+targetIds) verified live. - AUTHOR — still console-only. The Public API has NO create / edit / clone policy method. You can read and assign, but to CREATE or MODIFY a policy body you still use the GravityZone console. (This is the one true API limitation.)
Safety gating
Destructive subcommands refuse to run without --confirm; without it they print
what they would do and exit non-zero:
delete-endpoint <id> --confirmdelete-package --id <packageId> --confirm(param ispackageId, NOT packageName)delete-group --group <id> --confirmisolate --endpoints <id> ... --confirm(cuts the endpoint off the network; reversible viaunisolate)unisolate --endpoints <id> ... --confirmblocklist-add --company <id> --hashes <h> ... --confirmblocklist-remove --id <hashItemId> --confirmassign-policy --policy <id> --targets <id> ... --confirm(applies an existing policy to endpoints/groups)push-set --status 1 --url <receiver> --confirm(configures the GravityZone push event service)move --endpoints <id> ... --group <id> --confirm(relocating endpoints changes their INHERITED policy — gated 2026-06-25)scan --targets <id> ... --type <n> --confirm(starts a scan on live endpoints — gated 2026-06-25)create-package --name <n> [--company <id>] --confirm(creates an installer package — gated 2026-06-25)make-group --name <n> [--parent <id>] --confirm(creates a custom group — gated 2026-06-25)
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
GZ="bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" 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 (full read + assign; authoring is console-only)
$GZ policies
$GZ policy <policyId> --json # FULL granular config
$GZ assign-policy --policy <policyId> --targets <epId> ... --confirm
# Reports / accounts / scan tasks / notifications (read)
$GZ reports
$GZ accounts
$GZ scan-tasks
$GZ notif-settings
# Push event service (event-driven alerts instead of polling)
$GZ push-settings # current config (or "not configured")
$GZ push-stats
$GZ push-set --status 1 --url https://<receiver> --confirm # enable
$GZ push-set --status 0 --confirm # disable
# 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
Enabled API scopes (live key, 2026-06-21)
companies, licensing, packages, network, integrations, policies, maintenancewindows, reports, accounts, incidents, push, quarantine, phasr, patchmanagement.
Wrapped & verified — ALL live modules complete (see references/BUILDOUT.md
for the per-method checklist): general, licensing (+monthly-usage), companies
(full CRUD-ish), network (endpoints/groups/scan/move/delete/assignPolicy/
reconfigure/setLabel/tags), policies (full read + assign), packages (+details),
reports (create/links/delete), accounts (full 7), quarantine (list/remove/
restore), incidents/EDR (blocklist + isolate + custom-rules + incident
status/note), push (get/stats/set/test), integrations (read).
Dead on this tenant (license/feature OFF — raw only, returns errors):
patchmanagement, phasr, maintenancewindows, integrations. incidents. getIncidentsList returns "Method not found" (blocklist + isolate on the same
module DO work).
Phase-2 hooks
- Push webhook (half-built):
push-setnow configures the GravityZone push event service over the API (verified thestatusparam + gating). Remaining: stand up the RECEIVER — an HTTPS endpoint (coord API or an RMM route) that accepts GravityZone's event POSTs and fans them into coord/RMM alerts — thenpush-set --status 1 --url <receiver> --confirm. Without a receiver URL there is nothing to enable yet. - GuruRMM push-deploy (not yet wired): use
install-linksto 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. Needs the cross-skill call into/rmmagainst a chosen agent.
Reference
Full verified vs unverified method spec, JSON-RPC envelope, auth, and the
policy/deployment caveats: references/api-reference.md.