sync: auto-sync from HOWARD-HOME at 2026-06-25 12:45:08

Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-25 12:45:08
This commit is contained in:
2026-06-25 12:45:43 -07:00
parent bd1e84d32c
commit e9ece35c2a
3 changed files with 182 additions and 3 deletions

View File

@@ -38,6 +38,7 @@ import argparse
import dataclasses
import json
import os
import re
import subprocess
import sys
@@ -312,11 +313,15 @@ def cmd_company_delete(client, args):
def cmd_endpoints(client, args):
if args.company and not _require_oid(args.company, "company"):
return 2
_emit(client.list_endpoints(args.company, per_page=args.per_page),
args.json, _print_endpoint_table)
def cmd_endpoint(client, args):
if not _require_oid(args.endpoint_id, "endpoint"):
return 2
_emit(client.get_endpoint_details(args.endpoint_id), args.json, _print_kv)
@@ -348,6 +353,8 @@ def cmd_policy(client, args):
# getPolicyDetails returns the FULL granular module configuration (verified
# live 2026-06-21). Use --json for the complete settings tree; the table
# view shows the top-level keys only.
if not _require_oid(args.policy_id, "policy"):
return 2
_emit(client.get_policy_details(args.policy_id), args.json, _print_kv)
@@ -649,6 +656,12 @@ def cmd_inventory(client, args):
def cmd_create_package(client, args):
if args.company and not _require_oid(args.company, "company"):
return 2
if not _gated(f"create installer package '{args.name}'"
+ (f" in company {args.company}" if args.company else ""),
args.confirm):
return 3
result = client.create_package(
package_name=args.name,
company_id=args.company,
@@ -656,6 +669,7 @@ def cmd_create_package(client, args):
language=args.language,
)
_emit({"created": args.name, "result": result}, args.json, _print_kv)
return 0
def cmd_install_links(client, args):
@@ -664,16 +678,33 @@ def cmd_install_links(client, args):
def cmd_scan(client, args):
for t in args.targets:
if not _require_oid(t, "scan target"):
return 2
if not _gated(f"start a type-{args.type} scan on {len(args.targets)} "
f"target(s): {','.join(args.targets)}", args.confirm):
return 3
result = client.create_scan_task(
target_ids=args.targets, scan_type=args.type, name=args.name
)
_emit({"scanTask": result}, args.json, _print_kv)
return 0
def cmd_move(client, args):
for e in args.endpoints:
if not _require_oid(e, "endpoint"):
return 2
if not _require_oid(args.group, "group"):
return 2
if not _gated(f"move {len(args.endpoints)} endpoint(s) into group "
f"{args.group} (changes inherited policy): "
f"{','.join(args.endpoints)}", args.confirm):
return 3
result = client.move_endpoints(args.endpoints, args.group)
_emit({"moved": args.endpoints, "to": args.group, "result": result},
args.json, _print_kv)
return 0
def _print_tags(tags) -> None:
@@ -709,8 +740,15 @@ def cmd_reconfigure(client, args):
def cmd_make_group(client, args):
if args.parent and not _require_oid(args.parent, "parent group"):
return 2
if not _gated(f"create custom group '{args.name}'"
+ (f" under parent {args.parent}" if args.parent else ""),
args.confirm):
return 3
result = client.create_custom_group(args.name, args.parent)
_emit({"createdGroup": args.name, "result": result}, args.json, _print_kv)
return 0
# Substrings that mark a JSON-RPC method as state-destroying. `raw` can reach
@@ -724,7 +762,11 @@ DESTRUCTIVE_RAW_PATTERNS = ("delete", "createuninstall", "createremove",
"configurenotif", "createcompany", "suspendcompany",
"activatecompany", "setendpointlabel", "createreport",
"createrestore", "createcustomrule", "changeincident",
"updateincident", "sendtestpush")
"updateincident", "sendtestpush",
# state-changing methods also exposed as gated
# subcommands - keep them gated via `raw` too.
"moveendpoints", "movecustomgroup", "createscan",
"createpackage", "createcustomgroup")
def _is_destructive_method(method: str) -> bool:
@@ -750,11 +792,31 @@ def cmd_raw(client, args):
return 0
# --- input validation ---------------------------------------------------------
# GravityZone object IDs are 24-char hex (Mongo ObjectId). Validating client-side
# stops a malformed/empty id from hitting the live tenant AND from being mislogged
# as a functional skill error (the API's "Expected format: 24-char hex ID" reply
# matches none of the expected-error markers, so it would otherwise land in
# errorlog.md as noise - the bulk of the 2026-06-21 bitdefender entries).
_OID_RE = re.compile(r"^[0-9a-fA-F]{24}$")
def _require_oid(value, label: str) -> bool:
"""True if `value` is a valid 24-char hex id; else print [ERROR] and False.
The caller should `return 2` (user-input error) and must NOT log it."""
if value and _OID_RE.match(str(value)):
return True
print(f"[ERROR] {label} '{value}' is not a valid 24-char hex GravityZone id.",
file=sys.stderr)
return False
# --- destructive (gated) ------------------------------------------------------
def _gated(action_desc: str, confirm: bool) -> bool:
if not confirm:
print("[WARNING] Refusing destructive action without --confirm.")
print(f"[INFO] Would: {action_desc}")
print("[WARNING] Refusing destructive action without --confirm.",
file=sys.stderr)
print(f"[INFO] Would: {action_desc}", file=sys.stderr)
return False
return True