Commit Graph

21 Commits

Author SHA1 Message Date
1852f755ad fix(bitdefender): doc LOWs + sweep pagination + selftest alignment
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>
2026-06-25 13:28:38 -07:00
4d7508382d fix(bitdefender): atomic cache writes + advisory lock on write-through (M3)
Audit fix M3: _write_cache did a non-atomic CACHE_FILE.write_text and the
write-through helpers did unlocked read-modify-write, so a crash mid-write could
truncate inventory.json and two concurrent gz.py runs could lose an update.
- _write_cache now writes a temp file (fsync) then os.replace() - atomic on the
  same filesystem; a reader/crash can never see a partial file, and a failed
  write leaves the prior cache intact and no .tmp residue.
- Added a best-effort cross-platform advisory lock (_cache_lock) around the
  read-modify-write in _cache_add_group/_cache_add_package; steals a stale lock,
  proceeds unlocked on timeout (a lost update is tolerable, a hang is not).
- Dropped the dead cache.setdefault('companies', ...) line in _cache_add_group.
- Verified: compile clean; unit tests for round-trip, lock acquire/release/steal,
  write-through, temp cleanup on failure, and prior-cache survival.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:54:50 -07:00
d4fd71baab sync: auto-sync from HOWARD-HOME at 2026-06-25 12:53:21
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-25 12:53:21
2026-06-25 12:54:04 -07:00
51751e6473 fix(bitdefender): retry 429/5xx/timeout with backoff + reuse one httpx client
Audit fix H2 (+ M2): the live GravityZone tenant is rate-limited and sweeps fan
out one getManagedEndpointDetails per endpoint across every company, which hit a
real HTTP 429 (errorlog 2026-06-21). _post had zero retry and opened a fresh
httpx.Client (new TLS handshake) per request.
- _post now retries 429/500/502/503/504/timeout up to RETRY_MAX_ATTEMPTS with
  bounded exponential backoff + jitter, honoring Retry-After (numeric or HTTP-date).
  Retry notices go to stderr (don't pollute --json). Terminal errors still raise.
- M2: a single httpx.Client is created lazily and reused (connection pooling),
  closed via client.close() in main()'s finally. Makes the docstring's pooling
  claim true and cuts handshake overhead + 429 pressure during sweeps.
- Verified: compile clean; offline unit tests (persistent 429 -> 4 attempts then
  raise, flaky 503 -> recovers, Retry-After honored); live status read OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:51:08 -07:00
d8f0974e0f 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>
2026-06-25 12:47:45 -07:00
e9ece35c2a 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
2026-06-25 12:45:43 -07:00
ef55121d95 errorlog lint follow-ups: bitdefender log-gap fix + submodule memory + Windows CORE rules
- bitdefender gz.py: add "missing name" to _EXPECTED_ERROR_MARKERS — closes the last gap in
  Howard's errorlog suppression ("Missing name 'X' in 'options' object" validation errors were
  still logged). Verified all 10 real spam messages now suppressed; genuine errors still log.
- memory feedback_submodule_autosync_discipline: capture the recurring auto-synced-submodule
  rule (worktree or push-by-SHA + ls-remote verify; assert HEAD==origin/main before audits;
  never checkout-- shared files). Recurred on Howard-Home x3 + GURU-5070 this session.
- CLAUDE.md CORE Windows bullet: promote the two top recurring mechanical traps (/tmp path
  mismatch, curl.exe/plink quote-stripping) to always-loaded hard rules so they stop repeating.

Lint of errorlog.md: bitdefender expected-validation spam was ~70% of entries (Howard's
suppression now complete); fabb3421/Mail.Send drift closed earlier this session; wiki-compile
lock-release doc already fixed (entries predate the fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:48:00 -07:00
8f17c17258 fix(bitdefender): errorlog rule-compliance + moveCustomGroup param + ASCII-clean code
Finalizing the skill to "done, no errors, all skill rules":
- errorlog compliance: gz.py no longer logs EXPECTED API responses (validation,
  method-not-found, not-configured, rate-limit, expected state) or `raw`/selftest
  runs to errorlog.md. Per CLAUDE.md "do not log expected/handled conditions".
  Verified: selftest + probes leave errorlog unchanged.
- moveCustomGroup: param is `parentId`, not `newParentId` (6th doc-vs-live fix
  caught by a full param-shape audit).
- ASCII-clean code: removed all non-ASCII (em-dashes, U+21D2 arrow) from scripts
  (avoids cp1252 encode errors; aligns with the ASCII-markers rule).
- api-reference updated.

Verified: 18/18 read commands rc=0 live; selftest 75/75; parser builds; ASCII
markers + vault load + errorlog helper present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 17:06:46 -07:00
bfc6631c36 sync: auto-sync from HOWARD-HOME at 2026-06-21 14:37:28
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-21 14:37:28
2026-06-21 14:38:01 -07:00
603773cf25 fix(bitdefender): correct assignPolicy + isolate/unisolate param shapes (live-verified)
Found during the RMM-TEST-MACHINE full-function test (live tenant):
- assignPolicy: assigning a policyId REQUIRES inheritFromAbove:false in the same
  call, else the API rejects with a misleading "inheritFromAbove should not be
  used with policyId" error. Fixed assign_policy to always send it; dropped the
  wrong --inherit-from-above flag.
- isolate/unisolate: the API takes a SINGLE endpointId per call, NOT an
  endpointIds array (errored "not expected"). Client now loops per endpoint.
  unisolate fails while the isolate task is in progress — wait + retry.
- api-reference updated with the live-verified shapes.

Full function test PASSED on RMM-TEST-MACHINE: install(offline kit/SYSTEM) ->
enroll -> move(ZZ-RMM-TEST) -> assign-policy(GPS Base, applied) -> set-label ->
scan -> reconfigure -> isolate -> unisolate -> quarantine/blocklist read ->
managed uninstall(deleteEndpoint). selftest 75/75.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 14:26:44 -07:00
96a5dd6e7a sync: auto-sync from HOWARD-HOME at 2026-06-21 11:22:19
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-21 11:22:19
2026-06-21 11:23:04 -07:00
5ede4fee26 sync: auto-sync from HOWARD-HOME at 2026-06-21 10:42:33
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-21 10:42:33
2026-06-21 10:43:16 -07:00
a254e5f641 feat(bitdefender): complete Network module (build-out 3/N)
- Completed Network module for bitdefender skill (GravityZone Public API)
- Added getEndpointTags (read), setEndpointLabel (gated), createReconfigureClientTask/reconfigure (gated)
- Confirmed createUninstallTask, getEndpointsByPolicy, getManagedEndpointDetailsByIp, createScanTaskByMailboxes not found under /network
- Fixed endpoint-tags renderer to handle list result (previously crashing _print_kv)
- raw gates setEndpointLabel; reconfigure already gated
- selftest 55 -> 60 passing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:32:26 -07:00
2a1ffab19f feat(bitdefender): complete Companies module (build-out 2/N)
- Completed Companies module for bitdefender GravityZone Public API
- Implemented: getCompanyDetails, getCompanyDetailsByUser, createCompany, suspendCompany, activateCompany, deleteCompany
- Discovered updateCompany and getCompaniesList not available; companies retrieved via network inventory
- Company types: 0=Partner, 1=Customer; createCompany accepts nested licenseSubscription via JSON passthrough
- All write operations require --confirm; raw also restricts createCompany/suspendCompany/activateCompany
- selftest 49 -> 55 passing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:26:14 -07:00
8a64bc48e6 feat(bitdefender): complete Accounts module (build-out 1/N)
- Completed Accounts module for bitdefender skill (GravityZone Public API)
- Added 5 methods: getAccountDetails, createAccount, updateAccount, deleteAccount, configureNotificationsSettings
- Write methods require --confirm; raw also gates createAccount/updateAccount/configureNotificationsSettings
- Param shapes validated against official docs and safe validation probes
- configureNotificationsSettings is a setter with no required param; warning documented against empty payload on live tenant
- selftest 42 -> 49 passing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:22:01 -07:00
4cf34f5221 feat(bitdefender): doc-verify assignPolicy/push + add full-API build-out tracker
- assign_policy: add inheritFromAbove option; mark VERIFIED via official docs
  (policyId/targetIds/forcePolicyInheritance/inheritFromAbove; not applied to
  ENFORCED-policy targets).
- setPushEventSettings: documented serviceType (splunk/cef/jsonRPC), TLS 1.2+
  receiver requirement, subscribeToEventTypes event-flag map; webhook receiver
  pattern noted.
- api-reference.md: cite GravityZone Support Center as authoritative source.
- add references/BUILDOUT.md — master checklist to implement every API method
  module-by-module; seeded with current done/todo/dead state.
- memory: reference_gravityzone_support (+ index).

selftest 42/42.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:14:23 -07:00
d622a05b84 feat(bitdefender): expand GravityZone control surface + correct policy docs
Re-verified the live tenant's full API scope and wrapped the modules the key
allows but the skill didn't expose. New CLI subcommands:
- assign-policy (gated) — apply an existing policy to endpoints/groups
  (param shape policyId+targetIds verified live)
- reports, accounts, notif-settings, scan-tasks — read
- push-settings / push-stats / push-set (gated) — push event service
  (status param verified; needs a receiver URL to enable)

Corrections from live probing:
- policies are NOT shallow: getPolicyDetails returns the FULL granular config.
  Removed the false "shallow" warning; documented read+assign, console-only authoring.
- raw now gates assignPolicy + setPushEventSettings.
- documented dead modules (patchmanagement/phasr/maintenancewindows/integrations,
  incidents.getIncidentsList) and unconfigured-push handled cleanly (rc0, no errorlog).

selftest 29/29 -> 42/42, all green against the live tenant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 10:03:19 -07:00
da87d314c5 harness: fleet-wide functional-error + correction + friction logging
Add .claude/scripts/log-skill-error.sh — the canonical agent error log helper
(writes errorlog.md in DATE | MACHINE | skill | [type] error format, soft-fails).
Three categories: execution failures (default), user corrections (--correction),
and preventable self-inflicted friction (--friction; cite ref= when it repeats a
documented gotcha). Goal: stop paying tokens twice for the same avoidable mistake.

- CLAUDE.md: make logging mandatory for all skills + corrections + friction.
- skill-creator: new skills must wire in the helper (guidance + checklist).
- Retrofit every skill script's genuine failure branches to call the helper
  (b2/bitdefender/mailprotector/packetdial/coord python CLIs; remediation-tool
  + onboard365 bash; vault, rmm-auth, post-bot-alert, agy, grok, 1password,
  run-onboarding-diagnostic). Handled conditions + self-tests left alone.
- errorlog.md: broaden header to cover skills + harness + corrections; seed this
  session's corrections (INKY, Mail.Send token-audience, omnibox-strictness) and
  friction (git-bash /tmp, env-persistence, argv-limit, PowerShell var-case).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:40:25 -07:00
d52694a50d fix(bitdefender): all-clients sweep, quarantine path, EDR controls, self-test
Several bugs found and fixed during live testing against the ACG GravityZone
tenant:
- security_sweep_all_clients: iterate each company (the companies container is
  not a valid endpoint parent; passing it 400'd the whole sweep)
- list_quarantine: use service-scoped path quarantine/computers with companyId
  (bare quarantine module 404'd; param is companyId not parentId)
- rename GZEndpointSummary.detection_active -> threat_detected with corrected
  semantics (True = active threat, tracks with infected; not an engine-on flag)
- status: readable sectioned table renderer for the nested apiKey/license dict
- portable CLAUDETOOLS_ROOT resolution (derive from file path, not a Windows
  literal) so it works on the Mac/Linux fleet

Adds scripts/selftest.py: a 29-check read-only harness (all passing) covering
every read command, --json, error exit codes, and destructive-action gating.
EDR/incident commands (blocklist, isolate/unisolate, blocklist-add/remove) and
raw destructive-method gating are included from this session's work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 07:29:55 -07:00
6eb4087e8a 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>
2026-05-30 07:29:55 -07:00
c9387e0704 feat(bitdefender): GravityZone Cloud Public API skill
Adds a /bitdefender skill that drives the ACG GravityZone partner tenant
via the JSON-RPC Public API. Read + management ops (companies, endpoints,
live security sweep, policies [read-only/shallow], packages, quarantine,
scans, groups, move/delete). Identity-tier JSON cache (24h TTL,
--refresh); volatile status is always pulled live, never cached.

Security hardening: API key loaded from SOPS vault at runtime (never on
disk/logs/argv/cache); destructive deletes gated behind --confirm; `raw`
also gates destructive methods; upstream error bodies truncated. UNVERIFIED
API methods reachable only via `raw`. Reuses the auth/JSON-RPC pattern from
api/services/gravityzone_service.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 07:29:55 -07:00