fix(bitdefender): gate move/scan/create-package/make-group + validate object IDs

Audit cluster C1/C2/H1/H3/M1 on the live GravityZone tenant:
- C1/H1/M1: move, scan, create-package, make-group called the live API with
  no --confirm; added _gated() + a --confirm flag to each (move can change an
  endpoint's inherited policy posture).
- C2: extend raw's destructive-method denylist with moveEndpoints/moveCustomGroup/
  createScanTask/createPackage/createCustomGroup so 'raw' can't bypass the gates.
- H3: add _require_oid() 24-char-hex validation to endpoint/policy/endpoints +
  the gated handlers, so malformed ids no longer hit the tenant or get mislogged
  as functional errors (source of the 2026-06-21 errorlog noise).
- Gate refusals now print to stderr (don't pollute --json). SKILL.md gating list
  updated. Verified: compile clean; gates exit 3, bad ids exit 2, raw denylist hits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 12:47:45 -07:00
parent e9ece35c2a
commit d8f0974e0f
2 changed files with 14 additions and 4 deletions

View File

@@ -94,6 +94,10 @@ what they would do and exit non-zero:
- `blocklist-remove --id <hashItemId> --confirm`
- `assign-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

View File

@@ -1024,31 +1024,37 @@ def build_parser() -> argparse.ArgumentParser:
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 (gated).",
parents=[common])
sp.add_argument("--name", required=True)
sp.add_argument("--company")
sp.add_argument("--description")
sp.add_argument("--language")
sp.add_argument("--confirm", action="store_true")
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.", parents=[common])
sp = sub.add_parser("scan", help="Create a scan task (gated).", 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.add_argument("--confirm", action="store_true")
sp = sub.add_parser("move", help="Move endpoints into a group.", parents=[common])
sp = sub.add_parser("move", help="Move endpoints into a group (gated).",
parents=[common])
sp.add_argument("--endpoints", nargs="+", required=True)
sp.add_argument("--group", required=True)
sp.add_argument("--confirm", action="store_true")
sp = sub.add_parser("make-group", help="Create a custom group.", parents=[common])
sp = sub.add_parser("make-group", help="Create a custom group (gated).",
parents=[common])
sp.add_argument("--name", required=True)
sp.add_argument("--parent")
sp.add_argument("--confirm", action="store_true")
sub.add_parser("endpoint-tags", help="List endpoint tags.", parents=[common])