Files
claudetools/session-logs/2026-06/2026-06-21-howard-security-assessment-scoring.md
Howard Enos 567986fa49 sync: auto-sync from HOWARD-HOME at 2026-06-21 21:42:44
Author: Howard Enos
Machine: HOWARD-HOME
Timestamp: 2026-06-21 21:42:44
2026-06-21 21:43:11 -07:00

190 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 = 0100 risk score per domain + overall AF 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 (AF 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 AF 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 + AF 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()`.