13 KiB
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=internaldefault 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
requiredControlsfield-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 +?viewinternal/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/syncalready tracks atc82a3c9).
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@). DBacgsec_assess/ useracgsec_app. - Consent redirect:
https://security.azcomputerguru.com/consent-callback.php. - security-assessment repo: Gitea
azcomputerguru/security-assessment;mainnowc82a3c9.
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.jsonto 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:
c82a3c9onsecurity-assessmentmain (claudetools pins it as of sync68a05d3). - 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
setsidnot present in Git-Bash — dropped it; bare MSYSssh -Twith 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.phpfrom 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 vaultinfrastructure/ix-server. - Docroot:
/home/azcomputerguru/public_html/security(cPanel acctazcomputerguru, PHP 8.1). - DB
acgsec_assessvia 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:213reads?id=and callsload().