diff --git a/session-logs/2026-06/2026-06-21-howard-security-assessment-scoring.md b/session-logs/2026-06/2026-06-21-howard-security-assessment-scoring.md new file mode 100644 index 00000000..825c91df --- /dev/null +++ b/session-logs/2026-06/2026-06-21-howard-security-assessment-scoring.md @@ -0,0 +1,122 @@ +# Session — security.azcomputerguru.com: posture scoring + gap/upsell findings engine + +## User +- **User:** Howard Enos (howard) +- **Machine:** Howard-Home +- **Role:** tech + +## Session Summary + +Built out the ACG Security Assessment tool (`security.azcomputerguru.com`, the `security-assessment` +submodule) per Howard's brief: make it user-friendly, "better calculate," add more ACG-fit questions, +and surface upsell gaps to both onboard a customer and find service gaps. The tool was a clean +single-assessor PHP+MySQL wizard (Syncro lookup → risk-ordered questionnaire → 365/Google consent → +flat export) with NO scoring and NO gap→service mapping. Confirmed two design choices with the user up +front: scoring model = 0–100 risk score per domain + overall A–F grade; output = two views (internal +upsell worklist + client-safe risk report). + +Designed a data-driven scoring engine: scoring rules live in `questions.json` (per-answer `risk` +weights + a `finding` with why/fix + the ACG `service` that closes the gap), and the engine is mirrored +in JS (live wizard) and PHP (export), both reading the same rules. Rewrote `questions.json` to v2 — +added `risk`/`finding` metadata to 25 fields, a `scoring` block (A–F grade thresholds, severity→weight +critical15/high10/medium6/low3, gapThreshold, `requiredControls`), and 6 new ACG-relevant questions +(3rd-party 365/Google backup, dark-web monitoring, DNS filtering, MDR/SOC, IT documentation, vCIO +cadence). Validated the engine in node against weak (0/F, 25 findings) and strong (100/A, 0) sample +clients. + +Implemented the engine + UI in `index.php`: live posture chip (overall grade) in the topbar, per-domain +grade badges in the rail, and a rich "Posture & Findings" results view (overall A–F card, per-domain +score bars, prioritized findings each mapped to the ACG service, and an internal↔client-safe toggle, +with a collapsible full intake). Mirrored the engine in `api.php` (`score_assessment`) and rewrote the +export to render posture + findings, honoring `?view=internal` (default; shows ACG service per gap + +raw intake) vs `?view=client` (client-safe risk/recommendation report, no upsell language). + +Then drove a 6-item todo list to completion of the code-doable work: responsive/mobile layout +(breakpoints; rail → horizontal stepper); replaced all `alert()` dialogs with an in-page saved-list +modal + toasts + light input validation; compliance-aware framing (when the client reports compliance +drivers or sensitive data, findings in `requiredControls` get a REQUIRED badge + a context banner). +Committed the build on `feature/posture-scoring-and-findings`, pushed, then (per Howard) merged +fast-forward to `security-assessment` `main` (`c82a3c9`) and deleted the branch. Two items remain: +GuruRMM endpoint prefill (deferred — infra) and the live IX deploy (gated on go + access). + +## Key Decisions +- **Scoring rules as DATA in questions.json, engine duplicated in JS + PHP.** Keeps the wizard's live + score and the export identical without a shared runtime; the only duplication is ~30 lines of engine + in each language. +- **Risk score + A–F grade** (not CIS/NIST maturity or pure insurance checklist) — user choice; + simplest + most sales-legible. +- **Two export views** (`?view=internal` default vs `?view=client`) — internal carries the ACG service + per gap; client view strips upsell language and frames gaps as risks/recommendations. +- **Domain score only counts ANSWERED scorable fields** (unanswered = not assessed, not penalized) so a + mid-consult partial intake still shows a meaningful posture; "Unsure" answers map to a mid risk frac. +- **Compliance framing via a `requiredControls` field-id list** (not per-framework rules) — when the + client has compliance drivers/sensitive data, those baseline-control gaps get a REQUIRED badge. Robust + + low-maintenance vs brittle per-framework mapping. +- **GuruRMM prefill deferred, not faked** — no Syncro→RMM client mapping exists in the RMM schema and no + reachable read API from the public IX host; a fuzzy name-match could prefill the wrong client's counts + into a client-facing report. Count fields stay manually editable, so nothing is broken. +- **Merged to submodule main, no auto-deploy** — DEPLOY.md is a manual cPanel upload, so merging to main + does not change the live site; the live deploy stays a gated, confirmed step. + +## Problems Encountered +- **Export Edit failed to match** — the live rows used unquoted `class=r`/`class=k`; re-read the exact + block and matched it. +- **node `require('./questions.json')` failed** from a /tmp test script (require is script-relative, not + cwd) — used an absolute path. +- **No PHP locally** — validated JSON + the wizard JS in node (engine is identical logic) and the export + PHP via careful review + a bracket-balance check; final PHP gate is the live host. + +## Configuration Changes +In the `security-assessment` submodule (committed `c82a3c9`, now on `main`, pushed): +- `app/questions.json` — v2: scoring rules + `requiredControls` + 6 new questions. +- `app/index.php` — scoring engine (JS), posture chip + rail badges, Posture & Findings view, responsive + CSS, toast/modal, validation, view toggle. +- `app/api.php` — PHP scoring engine (`score_assessment`/`grade_for`) + rewritten export (posture + + findings + `?view` internal/client + compliance banner). +- `README.md`, `DEPLOY.md`, `app/config.sample.php` (the last two carried pre-existing Jun 19 changes: + howard@ added to the allow-list; DEPLOY tweaks). +No code committed to the ClaudeTools repo from this work (only this session log + the submodule pointer, +which `/sync` already tracks at `c82a3c9`). + +## Credentials & Secrets +None created or discovered. The tool's config uses existing vault entries (referenced in +`config.sample.php`): DB `msp-tools/security-assessment-db`, Syncro `msp-tools/syncro-mike`; M365 +Security Investigator app `bfbc12a4-f0dd-4e12-b06d-997e7271e10c` (read-only, multi-tenant). `config.php` +is gitignored / lives on the server. + +## Infrastructure & Servers +- security.azcomputerguru.com — PHP + MySQL on the IX cPanel host (172.16.3.10), behind Cloudflare + Access (Zero Trust app `8ce5f31c-4f4e-4883-bae1-f7606e5b06c0`; allow mike@ + howard@). DB `acgsec_assess` / user `acgsec_app`. +- Consent redirect: `https://security.azcomputerguru.com/consent-callback.php`. +- security-assessment repo: Gitea `azcomputerguru/security-assessment`; `main` now `c82a3c9`. + +## Commands & Outputs +``` +# engine validation (node) +node engine-test.js # WEAK -> 0/100 F, 25 findings ; STRONG -> 100/100 A, 0 findings +# parse/lint checks +node -e "require('.../questions.json')" # JSON OK (25 scorable, requiredControls 13) +new Function(>) # wizard JS parses OK (200 lines) +# land +git checkout -b feature/posture-scoring-and-findings; git commit; git push -u origin +git checkout main; git merge --ff-only ; git push origin main # c3ca261..c82a3c9 +git branch -d ; git push origin --delete +``` + +## Pending / Incomplete Tasks +- **#1 GuruRMM endpoint prefill** — DEFERRED (infra): no Syncro→RMM mapping + no reachable RMM API from + IX. Unblock: add a Syncro id to GuruRMM clients (or name-match + confirm) and expose a read-only RMM + API reachable from IX with a key, then a config-gated lookup with graceful fallback. +- **#6 Deploy to IX + live smoke test** — GATED on go + access: manual upload of `app/index.php`, + `app/api.php`, `app/questions.json` to the cPanel docroot (config.php already on server); then smoke + test lookup, live posture/findings, and both export views behind Cloudflare Access. HOWARD-HOME may + lack SFTP/SSH to the IX docroot (IX SSH key is from GURU-5070). +- Optional: number-field scoring (global_admins), client-report exec-summary/branding polish. + +## Reference Information +- Submodule commit: `c82a3c9` on `security-assessment` main (claudetools pins it as of sync `68a05d3`). +- Export views: `api.php?action=export&id=&view=internal|client`. +- Scoring: domain = 100·(1 − Σ(weight·riskFrac)/Σweight) over answered scorable fields; overall = domains + weighted by points; grades A≥90 B≥80 C≥70 D≥60 else F; finding when riskFrac ≥ gapThreshold (0.5). +- Companion logs this session: `2026-06-21-howard-unifi-pfsense-control-verbs.md`, + `2026-06-21-howard-gururmm-bug-018-019.md`.