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

13 KiB
Raw Blame History

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().