--- name: assessment description: Run a guided, end-to-end code assessment — frame the engagement, onboard the codebase's requirements via the setup skill, run `zenable check` plus an AI review pass against those same requirements, merge and verify the findings, and produce a polished HTML report bundle. Use this skill whenever the user asks to "scan", "audit", "review", or "assess" a codebase against custom requirements, compliance frameworks, or security concerns; whenever they want a deliverable report (PDF/HTML) summarizing a code review engagement; or whenever they invoke `/z:assessment`. Also trigger for phrases like "do a security review of this repo", "build me a compliance scan report", "I need to audit this codebase against X", or "scan this code for Y". --- # Zenable assessment An assessment is the `setup` skill plus validation and reporting. Setup produces durable, well-scoped requirements; this skill checks the code against them, verifies every claim it makes, and hands the user a deliverable report. The skill is collaborative throughout — the user shapes what gets assessed, what requirements are enforced, and what the final report says. You are the analyst: gather objective context, ask thoughtful questions, draft strawman content for the user to react to, and only commit once they've signed off. ## Mental model Five phases that build on each other. Don't shortcut them — the value comes from the user's input being woven through, not from running a tool. 1. **Frame & define** — establish the engagement, then run `setup` to co-author and persist the requirements 2. **Evaluate** — run `zenable check` AND an AI review pass against those same requirements, then merge 3. **Report** — populate the HTML template, walk the user through it, iterate 4. **Verify** — audit every factual claim in the report adversarially 5. **Groom** — gut-check the report's shape with the person who has to stand behind it The interview work in phases 1, 3, and 5 is what makes the report bespoke. Anything you assume without asking will read as generic. ## Phase 1 — Frame & define ### Frame the engagement first Do this before handing off to `setup` — the answers change what's worth collecting and how deep to go. Ask one or two questions about the user's background so the conversation lands at the right level: - Are they a developer or owner of the system assessing a specific concern? - Are they less familiar, tasked with covering a broader company or audit need? - Do they want technical detail, executive framing, or both? Ask how the output will be used — internal only, external audit, customer or prospect review, third-party/vendor review, public, or another distribution constraint. Then ask *why* the assessment exists, since it drives requirement selection and report tone: - What's prompting this? (audit prep, customer due diligence, pre-acquisition, incident response, internal hygiene, certification milestone…) - Who reads the final report? (board, execs, engineering, external auditor, regulator) - Is this a one-shot deliverable, a baseline to re-run quarterly, or a snapshot for negotiation leverage? - Is there a classification or distribution constraint? (Confidential, Recipient-Only, NDA-bounded) These shape `meta.engagement`, `meta.classification`, the `investigator.statement`, and how aggressive versus conservative you should be about flagging findings. Agree on depth before doing analysis: - **Fast** — focused answer, fewer requirements, lighter report, no optional appendices unless they serve the stated goal. - **Balanced** — the default workflow. - **Exhaustive** — auditor-grade detail, richer evidence capture, actor/action matrices, trust-boundary diagrams, and the Phase 4 claim audit. Also settle the assessment's boundary, which `setup` alone won't ask about: - **Scope** — organization, department, software, platform (Kubernetes, Lambda, data warehouse), customer-facing SaaS, internal tool, or something else. - **Architecture and data flow** — vendors, users, servers, databases, queues, clouds, identity providers, integrations, trust boundaries. - **Data classification and capability** — what data exists; who or what can create, read, update, delete, export, or share it. - **Actors and actions** — normal and adversarial personas: external without access, customer user, admin, service account, malicious insider, compromised vendor, compromised CI. - **Success criteria** — what can go wrong, and how would we know we did well? After the structured questions, switch into looser brainstorming: invite the user to wander through intended use, weird edge cases, misuse, attack ideas, surprising dependencies, and failures they worry about. Keep it relaxed; turn the useful parts into the plan and the requirements. Record all of this in `frame.md`, and the running narrative of decisions — and why they changed — in `interview-log.md`. ### Set up the workspace Establish a workspace inside the target repo (or cwd if there is no repo). Default location: `./zenable-assessment//`. Confirm the location with the user before creating it. ``` zenable-assessment// AGENTS.md # copied from this skill; persistent deterministic-work rules CLAUDE.md # contains @AGENTS.md so Claude loads those rules frame.md # engagement context: audience, use, classification, depth interview-log.md # running narrative of decisions and why they changed artifacts-spec.json # optional generator spec for artifact hashing metrics-aliases.json # optional generator config for git author grouping context/ # RAW/UNCHANGED transcripts, emails, docs, diagrams evidence/ # GENERATED/CAPTURED ONLY; never hand-edit, rerun tools/scripts findings/ # zenable check JSON/SARIF + AI-pass JSON sbom/ # syft/grype JSON, if the dependency appendix is in scope secrets/ # trufflehog NDJSON, if a secrets scan is in scope research/ # captured external sources with raw files + metadata metrics.json # extract_metrics.py artifacts.json # hash_artifacts.py dependencies.json # sbom_to_dependencies.py secrets.json # trufflehog_to_secrets.py experiments.json # collect_experiments.py experiments/ # reproduction scripts/fixtures/captures; emits *.experiment.json report/ # working report template copy (edit data.js here) dist/ # GENERATED build output; includes assessment-bundle.zip ``` Copy the bundled workspace instructions and report template into it: ```bash cp /assets/workspace/AGENTS.md /AGENTS.md cp /assets/workspace/CLAUDE.md /CLAUDE.md cp -R /assets/template/. /report/ ``` The `AGENTS.md`/`CLAUDE.md` pair keeps the deterministic evidence posture available after this skill run ends, when another agent or reviewer continues from the workspace. ### Run `setup` Hand off to the bundled `setup` skill for everything from pre-flight through persisted requirements: CLI install, authentication, MCP availability, repository identity, standards discovery, candidate mining and triage, requirement co-authoring, scoping, and creation through the Zenable MCP. Invoke it as `z:setup` (the user can also type `/z:setup`). Do not restate or re-implement any of that here — a second copy of the pre-flight and requirement-authoring workflow will drift from setup's. Two things to pass it: - **The workspace you just created.** Setup defaults to its own `./zenable-setup//`; tell it to write its notes (`sources.md`, `candidates.md`, `codebase-context.md`, `requirements.md`) into the assessment workspace instead, so the whole engagement lives in one directory. - **The framing from `frame.md`.** Depth, audience, classification, and the scope boundary all change what setup should collect and how hard to push on requirement count. Setup mines the standards a team has already written down; the engagement framing tells it which of those matter here. Setup owns the pre-flight gates. If it stops because the CLI isn't installed, the user isn't authenticated, or the MCP isn't available, this skill stops too — without persistent scopes and requirements, most of the workflow's value is gone. When setup finishes, `requirements.md` holds the adopted requirements with their Zenable IDs and scopes. Those IDs are what findings reference later via `requirementIds`, so the assessment can cross-link every finding back to the requirement it violates. ### Evidence discipline Everything under `context/` is source material from the engagement: transcripts, emails, meeting notes, design docs, diagrams, screenshots. Add it exactly as received. Do not edit, redact, summarize, normalize, or convert a context file in place. If a cleaner summary or derived fact is useful, put that in `frame.md`, `data.js`, or generated `evidence/` and link back to the untouched context file. Everything under `evidence/` is produced by a command, tool, script, or captured source fetch. Never edit it by hand: edit `data.js` for prose, edit root-level generator specs such as `artifacts-spec.json` or `metrics-aliases.json`, or edit experiment scripts under `experiments/`, then rerun the producing tool. Every number in the generated appendices traces to a tool or script output, not a typed value. For byte-stable output, pass `--generated-at ""` to scripts that support it; re-running one over the same inputs then reproduces byte-identical output. All analysis must be deterministic. If you need a new fact, create or rerun a tool, script, or capture that produces evidence for it. If a fact cannot be made deterministic from available context, mark it low confidence and ask the user whether to gather more evidence or leave it out. ### Metrics snapshot Once the scope is settled, run the metrics script for the objective baseline. This one needs no user input: ```bash uv run --script /scripts/extract_metrics.py \ --repo-root \ --out /evidence/metrics.json \ --html /report/index.html \ --generated-at "" ``` **Assessing a sub-package of a monorepo?** Pass `--path-prefix ` (repo-relative) so the metrics describe the sub-package's history, not the whole repo's. Without it the script walks every commit reachable from HEAD, which dilutes the metrics with unrelated activity and misleads readers who think they're looking at the scoped package. Set `meta.target` in `data.js` to match. If the target repo has a long history with author identity drift (people changing names or emails), offer to take a list of manual alias groups from the user, save it as `/metrics-aliases.json`, and pass `--aliases /metrics-aliases.json`. Don't require it. ## Phase 2 — Evaluate ### `zenable check` pass Run the CLI against the target. Exact flags depend on the scope settled in Phase 1; ask the user before running anything noisy or destructive. Typical: ```bash zenable check --branch \ --format text,json=/evidence/findings/zenable-check.json,sarif=/evidence/findings/zenable-check.sarif ``` If the user wants the full tree rather than the branch diff, drop `--branch` and pass globs. Show them the command before running it. **Filter known-noise rules before populating findings.** Some rules are test fixtures or universal-good-practice nags that fire on every file and dilute the signal. When you see floods of identical findings of that shape — same rule, scattered across unrelated files, no real risk — drop them from the merged set and note in `interview-log.md` which rules you suppressed and why. If you're unsure whether a rule is signal or noise, ask the user before suppressing. ### AI review pass Now do a second pass the CLI cannot do. For each requirement, walk the relevant files looking for what the deterministic scanner missed: architectural concerns, threat-model gaps, design-level issues, anything needing judgment. Keep AI-pass findings in the same schema as the `zenable check` JSON output so the merge is mechanical. Write them to `evidence/findings/ai-review.json` as generated output, not hand-authored report prose. Tag every finding with `source: "ai-review"` and a `requirementIds` array so it traces back to the requirement setup persisted. If a requirement is best evaluated by reading specific files, read them directly — don't sample. The user will judge the report by how grounded each finding is. ### Merge Combine the two streams into a single deduplicated list: - Same file + same rule or requirement + overlapping line range → one merged finding, `source: "merged"`, both sources captured in evidence - Severity is the max of the two - Recommendations: one per *finding cluster*, not one per source The merged set populates `findings[]` in `data.js`. Set `requirementIds` on each so the renderer cross-links to the Custom Requirements section. Score every finding using the rubric documented in `assets/template/data.js`. ### Collect optional tool evidence Only run tools that match the engagement scope. Put direct tool output under a scoped directory in `evidence/`; transform scripts then turn it into deterministic top-level `evidence/*.json` registries. Do not install these tools without explicit user permission. If they're missing, show the install link, ask, and proceed only after approval. - Syft: `https://github.com/anchore/syft#installation` - Grype: `https://github.com/anchore/grype#installation` - TruffleHog: `https://github.com/trufflesecurity/trufflehog#installation` On macOS, commonly: `brew install syft grype trufflehog` Source-tree SBOM and vulnerability capture: ```bash mkdir -p /evidence/sbom syft -o json > /evidence/sbom/source-tree.syft.json grype sbom:/evidence/sbom/source-tree.syft.json -o json \ > /evidence/sbom/source-tree.grype.json ``` TruffleHog output is NDJSON. Never paste secret values into `data.js`; the transform emits redacted summaries only. ```bash mkdir -p /evidence/secrets trufflehog filesystem --json \ > /evidence/secrets/trufflehog.fs.json trufflehog git file:// --json \ > /evidence/secrets/trufflehog.git.json ``` ### Generate evidence registries Anything that surfaces a hash, a dependency or license count, or a secrets-scan number belongs in a generated `evidence/*.json`, never typed into `data.js` by hand — those numbers drift the moment the tree changes. Run only the transforms whose appendix is in scope: ```bash # Artifact hashes (author a small spec listing what to hash) uv run --script /scripts/hash_artifacts.py \ --repo-root --spec /artifacts-spec.json \ --commit --out /evidence/artifacts.json \ --generated-at "" # Components/licenses/vulns from the syft+grype output in evidence/sbom/ uv run --script /scripts/sbom_to_dependencies.py \ --sbom-dir /evidence/sbom --out /evidence/dependencies.json \ --generated-at "" # Secrets-scan summary from trufflehog NDJSON (redaction-safe; never emits Raw) uv run --script /scripts/trufflehog_to_secrets.py \ --secrets-dir /evidence/secrets --out /evidence/secrets.json \ --generated-at "" # Demonstrated-attack stats — each experiment emits its OWN *.experiment.json uv run --script /scripts/collect_experiments.py \ --experiments-dir /experiments --out /evidence/experiments.json \ --generated-at "" ``` Each script also takes `--html /index.html` to inline its result at build time (Phase 3). Authored prose — findings, strengths, narratives — stays in `data.js`; the evidence numbers come from these files. The three trees serve different purposes: - `context/` — raw source material, preserved unchanged and cited, never rewritten in place. - `evidence/` — generated JSON registries and direct tool output supporting appendix rows, counts, hashes, and scan summaries. Nothing here is hand-edited. - `experiments/` — reproduction scripts, input fixtures, and captured output for demonstrated attacks or behavioral proofs. Each experiment emits its own `*.experiment.json`; `collect_experiments.py` aggregates them. The published report can link to files in any of these trees without embedding them. In `data.js` prose use the inline syntax `[label](download:context/path/to/file.md)`, or `download:evidence/...` / `download:experiment/...` for generated artifacts and reproduction files. The renderer emits `data-download-kind` + `data-download-path` links. In the Zenable app the report runs in a sandboxed iframe and cannot fetch authenticated files itself, so the iframe posts `{type: "report:download", kind, path, nonce}` to the parent, which verifies the nonce and fetches the file with the user's normal session. Standalone offline opens simply ignore that bridge. External research becomes evidence only when captured reproducibly. Put each source under `evidence/research//` with: - the unmodified raw download (`raw.html`, `raw.pdf`, `raw.json`, …) - optional derived markdown or text for easier reading - a metadata JSON with source URL, retrieval command or service, timestamp, content type, and any conversion tool used Do not cite live web pages as if they are stable. Cite the captured evidence file and keep the original URL in its metadata. ## Phase 3 — Report ### Template walkthrough Before populating anything, walk the user through the section list and ask which they want. The template ships with all of these — keeping a section means filling it; removing one means deleting both the `data.js` field and the HTML `
`. - **1. Executive summary** — required - **2. Scope** — required (Phase 1 framing goes here) - **3. Trust Boundary, Data Flow & Attack Paths** — optional; a mermaid data-flow diagram plus demonstrated attack paths. Strong for engagements with a clear trust boundary. Hides when `trustBoundary`/`attackPaths` stay null. - **4. SAMM maturity** — optional; useful for exec or auditor audiences - **5. Strengths** — optional but recommended (a report of nothing but findings reads as adversarial) - **6. Risk matrix** — required if there are findings - **7. Findings** — required if there are findings - **8. Confirmed exploitable CVEs** — optional; only if the scope included dependency exploitability analysis - **9. Custom Requirements Review** — required; each requirement setup persisted gets a status here - **10. Recommendations** — required if there are findings - **11. Investigator statement** — required; must include the standing attestation, near-verbatim, that "All AI usage was via inference providers which do not retain or train on inputs or outputs." - **A. Provided artifacts** — optional; SHA-256 of the inputs, from `hash_artifacts.py` - **B. Possible (unconfirmed) findings** — optional; things worth more research - **C. Repository history** — required; the Phase 1 metrics populate it - **D. License & dependency inventory** — optional; from `sbom_to_dependencies.py` Custom appendices are allowed, but only when the user asks for that line of analysis or the engagement clearly requires it. Do not push dead-code, reachability, performance, or privacy appendices as part of the baseline. If you add one, generate an `evidence/.json` registry from deterministic context/evidence/experiment inputs and inline it into the `extensions-data` block in `report/index.html`; do not hand-author appendix rows in HTML. For every "no", remove the matching `
` block from the user's `report/index.html` and the matching field from `data.js`. Don't leave dead containers. **After pruning, renumber the numbered sections (1–11).** The template ships with numbered headings and a matching TOC. Dropping sections 3 and 7 leaves the TOC reading 1, 2, 4, 5, 6, 8, 9, 10 — which looks broken. Renumber sequentially in both the `