fix(bitdefender): gate raw destructive calls, allow --json after subcommand
- raw now refuses destructive methods (delete/uninstall/remove/reconfigure) without --confirm (it previously bypassed all gating) - --json is now accepted after the subcommand (shared via a common parent parser), matching the documented usage - drop a placeholder-less f-string - SKILL.md: document raw gating + that raw echoes upstream responses verbatim Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,11 @@ label) are intentionally NOT exposed as dedicated subcommands — reach them onl
|
||||
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) 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
|
||||
|
||||
@@ -207,7 +207,22 @@ def cmd_make_group(client, args):
|
||||
_emit({"createdGroup": args.name, "result": result}, args.json, _print_kv)
|
||||
|
||||
|
||||
# Substrings that mark a JSON-RPC method as state-destroying. `raw` can reach
|
||||
# any method (incl. UNVERIFIED ones), so gate these behind --confirm too.
|
||||
DESTRUCTIVE_RAW_PATTERNS = ("delete", "createuninstall", "createremove",
|
||||
"createreconfigure")
|
||||
|
||||
|
||||
def _is_destructive_method(method: str) -> bool:
|
||||
m = method.lower()
|
||||
return any(pat in m for pat in DESTRUCTIVE_RAW_PATTERNS)
|
||||
|
||||
|
||||
def cmd_raw(client, args):
|
||||
if _is_destructive_method(args.method) and not args.confirm:
|
||||
print(f"[WARNING] '{args.method}' looks destructive; refusing without "
|
||||
"--confirm.", file=sys.stderr)
|
||||
return 3
|
||||
try:
|
||||
params = json.loads(args.params) if args.params else {}
|
||||
except json.JSONDecodeError as exc:
|
||||
@@ -224,7 +239,7 @@ def cmd_raw(client, args):
|
||||
# --- destructive (gated) ------------------------------------------------------
|
||||
def _gated(action_desc: str, confirm: bool) -> bool:
|
||||
if not confirm:
|
||||
print(f"[WARNING] Refusing destructive action without --confirm.")
|
||||
print("[WARNING] Refusing destructive action without --confirm.")
|
||||
print(f"[INFO] Would: {action_desc}")
|
||||
return False
|
||||
return True
|
||||
@@ -261,74 +276,91 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
prog="gz.py",
|
||||
description="GravityZone Cloud Public API CLI (ACG MSP tenant).",
|
||||
)
|
||||
p.add_argument("--json", action="store_true", help="Emit raw JSON output.")
|
||||
# --json is shared by every subcommand (via parents) so it is accepted
|
||||
# after the subcommand, e.g. `gz.py companies --json`.
|
||||
common = argparse.ArgumentParser(add_help=False)
|
||||
common.add_argument("--json", action="store_true", help="Emit raw JSON output.")
|
||||
sub = p.add_subparsers(dest="command", required=True)
|
||||
|
||||
sub.add_parser("status", help="API key + license status.")
|
||||
sub.add_parser("companies", help="List client companies.")
|
||||
sub.add_parser("status", help="API key + license status.", parents=[common])
|
||||
sub.add_parser("companies", help="List client companies.", parents=[common])
|
||||
|
||||
sp = sub.add_parser("endpoints", help="List endpoints under a company/group.")
|
||||
sp = sub.add_parser("endpoints", help="List endpoints under a company/group.",
|
||||
parents=[common])
|
||||
sp.add_argument("--company", help="Parent company/group id.")
|
||||
sp.add_argument("--per-page", type=int, default=100)
|
||||
|
||||
sp = sub.add_parser("endpoint", help="Full detail for one endpoint.")
|
||||
sp = sub.add_parser("endpoint", help="Full detail for one endpoint.",
|
||||
parents=[common])
|
||||
sp.add_argument("endpoint_id")
|
||||
|
||||
sp = sub.add_parser("sweep", help="Live security posture sweep.")
|
||||
sp = sub.add_parser("sweep", help="Live security posture sweep.", parents=[common])
|
||||
sp.add_argument("--company", help="Parent id (defaults to ACG container).")
|
||||
|
||||
sub.add_parser("policies", help="List policies (id + name).")
|
||||
sp = sub.add_parser("policy", help="Shallow detail for one policy.")
|
||||
sub.add_parser("policies", help="List policies (id + name).", parents=[common])
|
||||
sp = sub.add_parser("policy", help="Shallow detail for one policy.",
|
||||
parents=[common])
|
||||
sp.add_argument("policy_id")
|
||||
|
||||
sub.add_parser("packages", help="List installation packages.")
|
||||
sub.add_parser("packages", help="List installation packages.", parents=[common])
|
||||
|
||||
sp = sub.add_parser("quarantine", help="List quarantine items for a company.")
|
||||
sp = sub.add_parser("quarantine", help="List quarantine items for a company.",
|
||||
parents=[common])
|
||||
sp.add_argument("--company", required=True)
|
||||
|
||||
sp = sub.add_parser("inventory", help="Show cached identity/structure.")
|
||||
sp = sub.add_parser("inventory", help="Show cached identity/structure.",
|
||||
parents=[common])
|
||||
sp.add_argument("--refresh", action="store_true", help="Force a full re-pull.")
|
||||
|
||||
sp = sub.add_parser("create-package", help="Create an installer package.")
|
||||
sp = sub.add_parser("create-package", help="Create an installer package.",
|
||||
parents=[common])
|
||||
sp.add_argument("--name", required=True)
|
||||
sp.add_argument("--company")
|
||||
sp.add_argument("--description")
|
||||
sp.add_argument("--language")
|
||||
|
||||
sp = sub.add_parser("install-links", help="Get installer download URLs.")
|
||||
sp = sub.add_parser("install-links", help="Get installer download URLs.",
|
||||
parents=[common])
|
||||
sp.add_argument("--package", required=True)
|
||||
sp.add_argument("--company")
|
||||
|
||||
sp = sub.add_parser("scan", help="Create a scan task.")
|
||||
sp = sub.add_parser("scan", help="Create a scan task.", parents=[common])
|
||||
sp.add_argument("--targets", nargs="+", required=True)
|
||||
sp.add_argument("--type", type=int, required=True,
|
||||
help="1=Quick 2=Full 3=Memory 4=Custom (verify in console).")
|
||||
sp.add_argument("--name")
|
||||
|
||||
sp = sub.add_parser("move", help="Move endpoints into a group.")
|
||||
sp = sub.add_parser("move", help="Move endpoints into a group.", parents=[common])
|
||||
sp.add_argument("--endpoints", nargs="+", required=True)
|
||||
sp.add_argument("--group", required=True)
|
||||
|
||||
sp = sub.add_parser("make-group", help="Create a custom group.")
|
||||
sp = sub.add_parser("make-group", help="Create a custom group.", parents=[common])
|
||||
sp.add_argument("--name", required=True)
|
||||
sp.add_argument("--parent")
|
||||
|
||||
sp = sub.add_parser("raw", help="Call any method directly (power use).")
|
||||
sp = sub.add_parser("raw", help="Call any method directly (power use).",
|
||||
parents=[common])
|
||||
sp.add_argument("--module", required=True)
|
||||
sp.add_argument("--method", required=True)
|
||||
sp.add_argument("--params", default="{}", help="JSON object of params.")
|
||||
sp.add_argument("--confirm", action="store_true",
|
||||
help="Required for destructive methods (delete/uninstall/"
|
||||
"remove/reconfigure).")
|
||||
|
||||
# destructive (gated)
|
||||
sp = sub.add_parser("delete-endpoint", help="Delete an endpoint (gated).")
|
||||
sp = sub.add_parser("delete-endpoint", help="Delete an endpoint (gated).",
|
||||
parents=[common])
|
||||
sp.add_argument("endpoint_id")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("delete-package", help="Delete a package (gated).")
|
||||
sp = sub.add_parser("delete-package", help="Delete a package (gated).",
|
||||
parents=[common])
|
||||
sp.add_argument("--package", required=True)
|
||||
sp.add_argument("--company")
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
sp = sub.add_parser("delete-group", help="Delete a custom group (gated).")
|
||||
sp = sub.add_parser("delete-group", help="Delete a custom group (gated).",
|
||||
parents=[common])
|
||||
sp.add_argument("--group", required=True)
|
||||
sp.add_argument("--confirm", action="store_true")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user