diff --git a/.claude/skills/bitdefender/references/api-reference.md b/.claude/skills/bitdefender/references/api-reference.md index 33df7fab..c20f57af 100644 --- a/.claude/skills/bitdefender/references/api-reference.md +++ b/.claude/skills/bitdefender/references/api-reference.md @@ -140,7 +140,7 @@ In `getNetworkInventoryItems` results, `type == 1` denotes a company node. |---|---|---|---| | `getPoliciesList` | `page?, perPage?` | VERIFIED | List policies (id, name). | | `getPolicyDetails` | `policyId` | VERIFIED | **Full** granular config (not shallow). | -| `assignPolicy` (`/network`) | `policyId, targetIds[], forcePolicyInheritance?, inheritFromAbove?` | VERIFIED (official docs + probe) | Assign existing policy to endpoints/containers. NOT applied to targets with an ENFORCED policy. CLI `assign-policy`, gated. STATE-CHANGING. Docs: 77212-924802-assignpolicy.html | +| `assignPolicy` (`/network`) | `policyId, targetIds[], inheritFromAbove:false (REQUIRED w/ policyId), forcePolicyInheritance?` | VERIFIED LIVE 2026-06-21 | Assigning a policyId REQUIRES `inheritFromAbove:false` in the same call or the API rejects with a misleading "inheritFromAbove should not be used" error. `{targetIds, inheritFromAbove:true}` (no policyId) = make target inherit. CLI `assign-policy`, gated. | ## reports (`/reports`) — COMPLETE @@ -204,8 +204,8 @@ In `getNetworkInventoryItems` results, `type == 1` denotes a company node. |---|---|---|---| | `getBlocklistItems` | `companyId?, page?, perPage?` | VERIFIED LIVE | Returns `{total, page, perPage, pagesCount, items:[{id, source, sourceInfo, hashType, hash, companyId}]}`. Returned 26 items live. `perPage` defaults to 100 in the CLI. `companyId` scopes to one company; omit for the whole tenant. | | `getIncidentsList` | `parentId, page?, perPage (500-10000), filters?` | UNVERIFIED / possibly unavailable | `parentId` = a company/group id and is REQUIRED. `perPage` must be 500-10000 (the API rejected 100 with "Invalid value for 'perPage' parameter. The value should be between 500 and 10000"); the CLI defaults it to 500. **However**, live re-testing on 2026-05-30 returned `Method not found` for this method on the `/incidents` module, while `getBlocklistItems` on the SAME module succeeds in the same request — so this is NOT rate-limiting or a bad key. The method is likely gated behind an EDR/incidents license feature that is OFF on this tenant, or is named differently in this API version. The CLI `incidents` subcommand is wired up but will surface `Method not found` until the feature is enabled / the correct name is confirmed. | -| `createIsolateEndpointTask` | `endpointIds[]` | VERIFIED (destructive) | v1.2: takes an ARRAY `endpointIds` (max 1000), returns an array of task ids. Cuts the endpoint off the network. CLI-gated behind `--confirm`; the client enforces the 1000-id cap. | -| `createRestoreEndpointFromIsolationTask` | `endpointIds[]` | VERIFIED (destructive) | v1.2: takes an ARRAY `endpointIds` (max 1000), returns an array of task ids. Un-isolates (reverses `createIsolateEndpointTask`). CLI-gated behind `--confirm`; the client enforces the 1000-id cap. | +| `createIsolateEndpointTask` | `endpointId` (SINGLE) | VERIFIED LIVE 2026-06-21 | Takes ONE `endpointId` per call (NOT an `endpointIds` array — that errors "not expected"). Client loops for multiple. Cuts endpoint off network (BD mgmt link preserved). CLI `isolate`, gated. | +| `createRestoreEndpointFromIsolationTask` | `endpointId` (SINGLE) | VERIFIED LIVE 2026-06-21 | One `endpointId` per call. FAILS if the isolation task is still in progress ("cannot be restored") — wait + retry. CLI `unisolate`, gated. | | `addToBlocklist` | `companyId, hashType, hashList[], sourceInfo, operatingSystems?` | VERIFIED (destructive) | `hashType` is an int (1 is the common value seen live; see the console / API docs for the full mapping). `hashList` is an array of hash strings. `sourceInfo` is a free-text description. CLI-gated behind `--confirm`. | | `removeFromBlocklist` | `hashItemId` *(UNVERIFIED param name)* | VERIFIED method, UNVERIFIED param | Removes one blocklist entry. The param name `hashItemId` is UNVERIFIED — the `id` field from `getBlocklistItems` is the candidate. Confirm against the official API reference before relying on it. CLI-gated behind `--confirm`; the CLI `--id` value comes from `blocklist` output. | | `getCustomRulesList` | `page?, perPage?` | VERIFIED LIVE | EDR custom rules (returned 1 live). CLI `custom-rules`. | diff --git a/.claude/skills/bitdefender/scripts/gz.py b/.claude/skills/bitdefender/scripts/gz.py index ddbcb52f..6c996908 100644 --- a/.claude/skills/bitdefender/scripts/gz.py +++ b/.claude/skills/bitdefender/scripts/gz.py @@ -420,7 +420,6 @@ def cmd_assign_policy(client, args): result = client.assign_policy( args.policy, args.targets, force_inheritance=args.force_inheritance, - inherit_from_above=args.inherit_from_above, ) _emit({"assignedPolicy": args.policy, "targets": args.targets, "result": result}, args.json, _print_kv) @@ -1032,8 +1031,6 @@ def build_parser() -> argparse.ArgumentParser: help="One or more endpoint/group ids.") sp.add_argument("--force-inheritance", action="store_true", help="Force policy inheritance to sub-items.") - sp.add_argument("--inherit-from-above", action="store_true", - help="Inherit the policy from the parent group.") sp.add_argument("--confirm", action="store_true") sp = sub.add_parser("account-create", help="Create a console account (gated).", diff --git a/.claude/skills/bitdefender/scripts/gz_client.py b/.claude/skills/bitdefender/scripts/gz_client.py index c598b238..c8074308 100644 --- a/.claude/skills/bitdefender/scripts/gz_client.py +++ b/.claude/skills/bitdefender/scripts/gz_client.py @@ -612,14 +612,14 @@ class GravityZoneClient: v1.2 takes an ARRAY `endpointIds` (max 1000) and returns an array of task ids. STATE-CHANGING — gate behind --confirm at the call site. """ - if len(endpoint_ids) > 1000: - raise GravityZoneError( - "isolate_endpoints accepts at most 1000 endpoint ids per call " - f"(got {len(endpoint_ids)})." - ) - return self._jsonrpc_request( - "incidents", "createIsolateEndpointTask", {"endpointIds": endpoint_ids} - ) + # VERIFIED LIVE 2026-06-21: the API takes a SINGLE `endpointId` per call + # (NOT an `endpointIds` array — that errors "not expected"). Loop for many. + results = [] + for eid in endpoint_ids: + results.append(self._jsonrpc_request( + "incidents", "createIsolateEndpointTask", {"endpointId": eid} + )) + return results if len(results) != 1 else results[0] def restore_endpoints_from_isolation(self, endpoint_ids: list[str]) -> Any: """Un-isolate endpoints (incidents.createRestoreEndpointFromIsolationTask). @@ -627,16 +627,16 @@ class GravityZoneClient: v1.2 takes an ARRAY `endpointIds` (max 1000) and returns an array of task ids. STATE-CHANGING — gate behind --confirm at the call site. """ - if len(endpoint_ids) > 1000: - raise GravityZoneError( - "restore_endpoints_from_isolation accepts at most 1000 endpoint " - f"ids per call (got {len(endpoint_ids)})." - ) - return self._jsonrpc_request( - "incidents", - "createRestoreEndpointFromIsolationTask", - {"endpointIds": endpoint_ids}, - ) + # VERIFIED LIVE 2026-06-21: single `endpointId` per call (not an array). + # Note: fails if the isolation task is still in progress — wait + retry. + results = [] + for eid in endpoint_ids: + results.append(self._jsonrpc_request( + "incidents", + "createRestoreEndpointFromIsolationTask", + {"endpointId": eid}, + )) + return results if len(results) != 1 else results[0] def add_to_blocklist( self, @@ -689,22 +689,26 @@ class GravityZoneClient: policy_id: str, target_ids: list[str], force_inheritance: bool = False, - inherit_from_above: bool = False, ) -> Any: """Assign an existing policy to endpoints/groups (network.assignPolicy). - Param shape VERIFIED against the official docs + live validation probe - (2026-06-21): `policyId` and `targetIds` (endpoint/container ids) are - required; `forcePolicyInheritance` and `inheritFromAbove` are optional - bools. NOTE: the policy is NOT applied to targets that already carry an - ENFORCED policy. STATE-CHANGING — gate at the call site behind --confirm. + VERIFIED LIVE 2026-06-21: assigning a `policyId` REQUIRES sending + `inheritFromAbove=false` in the same call. An inheriting target defaults + inheritFromAbove to true, so omitting it makes the API reject the call + with a misleading "inheritFromAbove should not be used with policyId" + error. `forcePolicyInheritance` optionally pushes the policy down to + sub-items. (To make a target INHERIT instead, call with + {targetIds, inheritFromAbove:true} and no policyId via `raw`.) + STATE-CHANGING — gate at the call site behind --confirm. Docs: bitdefender.com/business/support/en/77212-924802-assignpolicy.html """ - params: dict = {"policyId": policy_id, "targetIds": target_ids} + params: dict = { + "policyId": policy_id, + "targetIds": target_ids, + "inheritFromAbove": False, + } if force_inheritance: params["forcePolicyInheritance"] = True - if inherit_from_above: - params["inheritFromAbove"] = True return self._jsonrpc_request("network", "assignPolicy", params) def list_scan_tasks(