190 lines
13 KiB
Markdown
190 lines
13 KiB
Markdown
# 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(<index.php <script>>) # wizard JS parses OK (200 lines)
|
||
# land
|
||
git checkout -b feature/posture-scoring-and-findings; git commit; git push -u origin <branch>
|
||
git checkout main; git merge --ff-only <branch>; git push origin main # c3ca261..c82a3c9
|
||
git branch -d <branch>; git push origin --delete <branch>
|
||
```
|
||
|
||
## 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=<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`.
|
||
|
||
---
|
||
|
||
## Update: 21:41 PT — Fully-filled demo client (quote) for review
|
||
|
||
### Session Summary
|
||
Howard asked to see a comprehensively filled-out client with a real score and Posture & Findings,
|
||
explicitly NOT entered into Syncro/RMM — just a persistent test record to review how the live site
|
||
renders with data. Built a realistic demo: `DEMO - Saguaro Family Dental (TEST)`, a fictional
|
||
18-person HIPAA dental practice on break-fix support, with a deliberate mix of strengths and gaps so
|
||
the report exercises every section (scored risk fields + unscored upsell opportunity fields) and
|
||
produces a varied posture rather than all-or-nothing.
|
||
|
||
Authored a one-off PHP inserter (`app/_demo.php`) that opens config.php's PDO and INSERTs a single
|
||
`status='quote'` row into the `assessments` table (data as JSON in the `data` column). Uploaded it to
|
||
the IX docroot via MSYS `ssh` + base64 (askpass helper from vault), ran it with `php`, captured
|
||
`DEMOID=12`, then removed the server-side file. The DB row stays so Howard can open it. This never
|
||
touches Syncro — the tool only ever reads Syncro; the insert went straight into the assessment tool's
|
||
own DB as a quote.
|
||
|
||
Verified server-side by fetching the internal export through the Cloudflare external-IP path
|
||
(`--resolve ...:443:72.194.62.5` + `Cf-Access-Authenticated-User-Email` header). The report renders:
|
||
overall 32/100 / Grade F; domain bars Endpoints 53%, Cloud/SaaS/DNS 40%, Identity&Email 38%, Backup 25%,
|
||
Physical/Governance 20%, Access&Ops 17%, Network&Perimeter 0%; HIPAA REQUIRED badges fire; multiple
|
||
security findings; and 8 upsell opportunities in the internal view. Confirmed `index.php` auto-loads
|
||
from `?id=` (line 213), so the direct link `https://security.azcomputerguru.com/?id=12` opens the
|
||
record. Provided Howard the URL.
|
||
|
||
### Key Decisions
|
||
- Inserted the demo straight into the DB as a `quote` (not via Syncro), satisfying the standing
|
||
guardrail that new/entered info must never auto-sync to Syncro/RMM/any service.
|
||
- Chose a HIPAA dental office persona so REQUIRED-control badges and the compliance banner exercise,
|
||
and the upsell story (no MFA, RDP exposed, untested backups, no MDR) is realistic and ACG-relevant.
|
||
- Left the row in place (did not delete) and removed only the server-side inserter, so the record
|
||
persists for review while no scratch file lingers on the host.
|
||
- Accepted a Grade F as a legitimate, demonstrative result (varied domain bars, not all-zero);
|
||
offered to dial answers up to a C/D if a softer demo is wanted.
|
||
|
||
### Problems Encountered
|
||
- `setsid` not present in Git-Bash — dropped it; bare MSYS `ssh -T` with SSH_ASKPASS worked.
|
||
- Initial grep for finding/opportunity CSS classes returned 0 (wrong class-name guess); the export
|
||
content clearly contained the findings + 8 opportunities, so this was a false negative, not a defect.
|
||
|
||
### Configuration Changes
|
||
- Created (local, uncommitted scratch): `projects/msp-tools/security-assessment/app/_demo.php` —
|
||
reusable demo-quote seeder (uses config.php PDO; no secrets inline).
|
||
- Server: temporarily uploaded + removed `_demo.php` from the IX docroot after running.
|
||
- DB: inserted one row into `assessments` (id=12, status='quote'). No schema/app code changes.
|
||
|
||
### Infrastructure & Servers
|
||
- IX server root SSH: `root@172.16.3.10:22` (ext 72.194.62.5). Password in vault `infrastructure/ix-server`.
|
||
- Docroot: `/home/azcomputerguru/public_html/security` (cPanel acct `azcomputerguru`, PHP 8.1).
|
||
- DB `acgsec_assess` via config.php creds on the server.
|
||
|
||
### Commands & Outputs
|
||
- Insert: `base64 app/_demo.php | ssh -T root@172.16.3.10 "base64 -d > $DOCROOT/_demo.php && chown azcomputerguru:azcomputerguru ... && cd $DOCROOT && php _demo.php; rm -f $DOCROOT/_demo.php"` -> `DEMOID=12`.
|
||
- Verify: `curl -sk --resolve security.azcomputerguru.com:443:72.194.62.5 -H 'Cf-Access-Authenticated-User-Email: mike@azcomputerguru.com' '.../api.php?action=export&id=12&view=internal'` -> 26415 bytes, grade F, 8 opportunities, REQUIRED badges.
|
||
|
||
### Pending / Incomplete Tasks
|
||
- Optional: tune demo id=12 answers to land a C/D grade if a softer showcase is preferred.
|
||
- Still deferred: #1 GuruRMM endpoint prefill (infra); FR-1 multi-tenant portal (auth decision);
|
||
wire Activate -> live import (button intentionally disabled for now).
|
||
|
||
### Reference Information
|
||
- Demo URL: `https://security.azcomputerguru.com/?id=12` (Cloudflare Access login as howard@, hard-reload).
|
||
- Demo record: `DEMO - Saguaro Family Dental (TEST)`, id=12, status=quote, phone 5205550100.
|
||
- Auto-load: `index.php:213` reads `?id=` and calls `load()`.
|