원클릭으로
threat-model
Guided end-to-end threat modeling workflow — scope through validation and sync
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Guided end-to-end threat modeling workflow — scope through validation and sync
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Assign classes to unclassified components, boundaries, data flows, and data items
Auto-discover infrastructure components from codebase using the infrastructure-scout agent
Populate security attributes, MITRE ATT&CK references, credentials, and monitoring tools
Quality dashboard with score breakdown, gap analysis, and readiness assessment
Attack surface summary with component breakdown, trust boundary crossings, and control gap analysis
Push local model to platform, pull platform model to local, or check sync status
| name | threat-model |
| description | Guided end-to-end threat modeling workflow — scope through validation and sync |
| agent | threat-modeler |
| argument-hint | [system description or model path] [--full-scan] |
@../../docs/guidelines-layout.md @../../docs/guidelines-schema.md @../../docs/guidelines-examples.md
Walk through the complete 11-step threat modeling workflow. Each step builds on the previous, with state checkpoints that allow resuming later. The workflow delegates to specialized subagents for discovery, enrichment, and validation.
This workflow is local-first: model files are created on disk now and synced to the platform later (Step 10). Use built-in tools directly — do NOT invent scaffolding scripts, chained shell heredocs, or wrapper utilities.
| Operation | Tool to use |
|---|---|
| Create a directory | Bash with mkdir -p <path> |
| Write a JSON/YAML/Markdown file | Write tool, one call per file |
| Read a model file | Read tool |
| Edit an existing file | Edit tool |
| Inspect / sample attribute files between steps | Read (one call per file, parallelisable) — never cat/head/tail in a Bash loop |
| Search for a field across attribute files | Grep with the field name and attributes/ path |
| Check enrichment progress / template coverage | mcp__plugin_dethereal_dethereal__validate_model_json(action: 'quality') — never sample files with head |
| Check control coverage / gap analysis | mcp__plugin_dethereal_dethereal__validate_model_json(action: 'coverage') |
| Validate model JSON | mcp__plugin_dethereal_dethereal__validate_model_json |
| Generate attribute stubs | mcp__plugin_dethereal_dethereal__generate_attribute_stubs |
| Match classes for elements | mcp__plugin_dethereal_dethereal__match_classes |
| Push/pull to platform | mcp__plugin_dethereal_dethereal__create_threat_model / update_model / export_model (invoked by /dethereal:sync at Step 10) |
Do not:
Write once per file insteadcat <<EOF > file.json) to create files — use the Write tool, which produces a reviewable difffor f in ...; do head -N "$f"; done to "check progress" — call validate_model_json(action: 'quality') for an authoritative per-element coverage report, or Read/Grep for targeted inspectionmcp__plugin_dethereal_dethereal__create_threat_model during Step 1 — that tool requires platform connectivity and is invoked by the sync skill at Step 10get_classes for match_classes in classification (D51) unless the offline fallback chain triggers itEvery mandated artifact in this workflow — the progress table (Step 0), discovery confirmation table (Step 2), boundary hierarchy tree (Step 4), approved-channels ratification gate (Step 5), enrichment/control-pass decision tables (Step 8), quality dashboard (Step 9), and push summary (Step 10) — is rendered as a markdown text block in the conversation BEFORE any AskUserQuestion. The confirmation modal never substitutes for the presentation: render the table/tree/dashboard first, then let the modal carry only the blocking question (and, where useful, mirror modify-variants into an AskUserQuestion preview). Information that travels only inside option labels is information the operator cannot review, compare, or scroll back to.
This applies equally to the subagent-delegated steps (6, 8, 9): when a step delegates to Agent(...), the orchestrator relays the subagent's returned tables into the conversation before prompting — the subagent cannot reach the user itself (see the relay obligations in Steps 8 and 9).
Parse $ARGUMENTS to determine whether to start a new model or resume an existing one:
./threat-models/payment-api) → resolve existing model, check state.json for resume.dethernety/models.json:
If .dethereal/state.json exists and currentState is not REVIEWED:
state.json, quality.json (if exists), and model filesstate.lastReconcileCommit is present and the user did not pass --full-scan, run the Drift Orchestration Protocol from the threat-modeler agent before computing the cursor:
Bash(node "${CLAUDE_PLUGIN_ROOT}/scripts/detect-drift.js" --model-dir <model-path>). If ${CLAUDE_PLUGIN_ROOT} is not propagated to the shell (variable expands empty), resolve the plugin root via node -e "console.log(require.resolve('@dether.net/dethereal/package.json').replace(/\/package\.json$/, ''))" and reissue the command. Parse stdout JSON { baseline, scoped }; on non-zero exit, surface stderr message + hint and stop drift logic.scoped is empty, emit [info] No drift since last reconcile (commit <baseline>). and skip to step 3..dethereal/discovery.json. If absent, emit [info] No prior discovery provenance; drift detection skipped. Run /dethereal:discover to set a baseline. and skip to step 3.priorElements_in_scope (elements whose sources[].file ⊆ scoped), invoke Agent(infrastructure-scout-scoped) in discover elements mode with the file allowlist, and compute the four-way delta (REMOVED / ADDED / CHANGED-substrate / CHANGED-attribute-only) by name-joining against newElements and comparing suggestedClass.id.[drift] <R> removed (<C> crown-jewel-tagged), <A> added, <S> reclassified, <T> attribute-changed since <baseline>./dethereal:remove <id> for REMOVED, /dethereal:add <description> for ADDED, mcp__plugin_dethereal_dethereal__match_classes + Edit on structure.json + /dethereal:enrich --pick <id> for CHANGED-substrate, and /dethereal:enrich --pick <id> for CHANGED-attribute-only.state.lastReconcileCommit = <git rev-parse HEAD> and update lastModified via Edit. Do not advance the baseline mid-loop — the deferred advance is the entire crash-safety story.--full-scan, skip drift entirely; emit [info] Full scan requested; running /dethereal:discover end-to-end. and invoke /dethereal:discover instead of the cursor.currentState (see step-to-state mapping in the Guided Workflow Orchestration section of the threat-modeler agent)[done]attribute_completion_rate > 0 → note "Step 8 partially complete"Progress: "<Model Name>" (Quality: X/100)
[done] 1. Scope Definition
[done] 2. Discovery (modules: dethernety-general, Kubernetes)
[done] 3. Model Review
[auto-skip] 4. Boundary Refinement (hierarchy already well-structured)
[>>>>] 5. Data Flow Mapping — current step
[ ] 6. Classification
[ ] 7. Data Item Classification
[ ] 8. Enrichment
[ ] 9. Validation
[ ] 10. Sync
[ ] 11. Post-Sync Linking
[done]: step's corresponding state is in completedStates[auto-skip]: Step 4 auto-skips if boundary_hierarchy_quality factor = 1.0[>>>>]: current step[ ]: not yet reachedIf currentState is REVIEWED: "Workflow complete. Model is reviewed and ready for analysis. Run /dethereal:sync push to publish, or /dethereal:surface to review attack surface."
Collect scope information through conversation — ask naturally, don't present a form.
| Field | Required | Default | Guidance |
|---|---|---|---|
system_name | yes | — | From description or ask |
description | yes | — | 1-2 sentence architecture summary |
depth | no | architecture | Options: architecture, design, implementation |
modeling_intent | no | initial | Options: initial, security_review, compliance, incident_response |
compliance_drivers | no | [] | Ask if not mentioned: "Any compliance requirements?" |
crown_jewels | yes (≥1) | — | Ask: "What are the most valuable assets in this system?" |
exclusions | no | [] | Ask if multi-system: "Anything explicitly out of scope?" |
trust_assumptions | no | [] | Ask: "What do you explicitly trust?" |
adversary_classes | no | — | Only for security_review or incident_response intent |
Scaffold the model on disk. Default path: ./threat-models/<kebab-case-name>/.
Bash: mkdir -p <model-path>/.dethereal
Write tool (one tool call per file — no scripts, no heredocs):| File | Content |
|---|---|
<model-path>/manifest.json | { schemaVersion, format: "split", model: { id: null, name, description, defaultBoundaryId }, files: { structure, dataFlows, dataItems, attributes }, modules: [] } per the schema |
<model-path>/structure.json | Initial structure with defaultBoundary (no children yet) |
<model-path>/dataflows.json | [] |
<model-path>/data-items.json | [] |
<model-path>/.dethereal/scope.json | The scope object from this step (system_name, description, crown_jewels, compliance_drivers, etc.) |
<model-path>/.dethereal/state.json | { "currentState": "SCOPE_DEFINED", "completedStates": ["INITIALIZED", "SCOPE_DEFINED"], "lastModified": "<ISO 8601>", "staleElements": [] } |
.dethernety/models.json at the project root:
Read to load it if it exists, then Write the updated JSON; if it does not exist, Write a new file with { "version": 1, "models": [{ name, path, createdAt }] }.Do not call mcp__plugin_dethereal_dethereal__create_threat_model here — it requires platform auth and is invoked by /dethereal:sync at Step 10.
Delegate to Agent(infrastructure-scout) per the Discovery Orchestration Protocol in the threat-modeler agent.
.dethereal/scope.json — pass scope context to scout.dethernety/discovery-cache.jsonAgent(infrastructure-scout) with model directory path and scope summary.dethereal/discovery.jsonAskUserQuestion labels.structure.json and dataflows.jsonmcp__plugin_dethereal_dethereal__validate_model_json to check structural validity.dethernety/discovery-cache.json if this is a multi-model projectEdit on .dethereal/state.json: currentState → DISCOVERED, add DISCOVERED to completedStates, refresh lastModified, and write lastReconcileCommit = <git rev-parse HEAD> to establish the drift-detection baseline (resolve via Bash(git -C <model-path> rev-parse HEAD); on non-zero exit, omit the field — drift detection will skip on resume until the operator re-runs discovery in a git repo)If the model was created from a description (not IaC), discovery still scans the codebase for IaC corroboration. If no codebase context is available, skip to Step 3 with the description-derived structure.
Select which platform modules provide classes for classification and enrichment.
.dethereal/discovery.json — extract recommendedModules and source type patternsmcp__plugin_dethereal_dethereal__match_classes or mcp__plugin_dethereal_dethereal__get_classes(fields: ['module']) with no module filter to list all installed modulesdethernety-general (non-removable)## Module Selection
Based on discovery, these modules are recommended for classification:
| # | Module | Reason | Include? |
|---|--------|--------|----------|
| 1 | dethernety-general | Always included (baseline classes) | yes (locked) |
| 2 | Kubernetes | K8s manifests found (12 resources) | yes |
| 3 | AWS | Terraform aws_* resources found (8 resources) | yes |
Available but not recommended:
| 4 | Databases | No database sources detected | no |
| 5 | Azure | No azurerm_* resources detected | no |
Confirm selection? (yes / modify)
.dethereal/scope.json as activeModules:"activeModules": [
{ "id": "module-uuid-1", "name": "dethernety-general" },
{ "id": "module-uuid-2", "name": "Kubernetes" }
]
If no discovery was performed (model created from description only), skip module recommendation — classification will search all installed modules.
No state transition — stays at DISCOVERED (same as Step 3).
Run deterministic Pass 1 classification and review the discovered model.
activeModules from .dethereal/scope.json. Call mcp__plugin_dethereal_dethereal__match_classes with element names, types, and descriptions from the model, scoped to active module IDs (moduleIds). Auto-accept exact_name matches; present others for confirmation. If activeModules is absent, call match_classes without moduleIds (searches all installed modules). Prefer classes from specialized modules over baseline (dethernety-general) when both match — specialized classes have more targeted attribute schemas (D51).dethereal/discovery.json), use the pre-classification from IaC mapping## Model Review
| # | Element | Type | Boundary | Proposed Class | Confidence |
|---|---------|------|----------|----------------|------------|
| 1 | PostgreSQL | STORE | Data Tier | Database | high (IaC) |
| 2 | Redis | STORE | Data Tier | Key-Value Store | high (IaC) |
| 3 | API Server | PROCESS | Internal | Web Application | medium (LLM) |
Confirm classifications? (yes / modify / skip)
structure.json (set classData on elements)mcp__plugin_dethereal_dethereal__generate_attribute_stubs(directory_path) to write class template attribute stubs for all classified elementsNo state transition — stays at DISCOVERED.
Review and refine the trust boundary hierarchy, then ratify the trust skeleton (zone + plane) alongside the boundary enforcement posture — both decision families in one batched accept-all / adjust gate, never a per-boundary interrogation.
Display current boundary hierarchy as a tree view — rendered in the conversation before any refinement prompt (see Presentation Discipline)
Check for structural issues:
Compute the trust skeleton: call mcp__plugin_dethereal_dethereal__validate_model_json(action: 'zoning', assets: 'skeleton'). The skeleton phase sets external + exposure tiers + the INTERNAL default only — RESTRICTED is deferred to the close of Step 7 (its inputs, the flow graph and asset classification, don't exist yet). Never propose RESTRICTED here.
Render the two stacked tables under one gate. Use the display names from guidelines-core.md (never raw enums):
## Boundary Refinement
Trust classification (proposed from discovery + scope)
| Boundary | Zone | Role (plane) | Resolved |
|-------------|-----------------|--------------|-----------------------|
| Edge / DMZ | DMZ [hi] | Workload | declared |
| App Tier | Internal | Workload | declared |
| payment-db | Internal | Workload | declared |
| Logging | — | Undecided | inherited · App Tier |
Enforcement posture
| Boundary | Implicit deny | Any inbound | Egress |
| … | … | … | … |
Proposed from discovery + scope. Accept all, or adjust specific rows?
suggestedZone in .dethereal/discovery.json (topology-derived at discovery); the assets:'skeleton' payload cross-checks the external/exposure tiers and is authoritative for the Resolved column. The inline [hi/med/lo] is the scout's classificationConfidence for that boundary — leave it blank when there is no topology signal. (Do not render the payload's own confidence here — under the skeleton it is low for every internal boundary by construction.)resolvedSource/resolvedFrom); never walk the tree yourself. Shown as a glyph, not prose: ⬆ = declared and stricter than its parent (the one case the operator must catch); a plain zone name = declared; · inherited · <ancestor> = resolves from a named ancestor; — = unclassified (surfaces in the Step 9 count); — structural = a container that nests child boundaries — it abstains and is not in the Step 9 count. The operator scans this one column for ⬆.Workload / Management from the scout's suggestedPlane; default Undecided (distinct from Workload). A mixed Workload + Management boundary carries an inline ⚠.structural: true (it nests child boundaries) abstains — render its Zone cell — structural, propose no zone, and do not nag. Its subtree can span multiple tiers, so it has no single well-defined zone; trust classification lives at the leaves, and the container stays unzoned unless the operator manually declares one for inheritance leverage. (What the container actually spans is surfaced as a display roll-up in the Step-9 review, once assets are known — the skeleton table here shows only the bare glyph.)domains/planes tags, not zone-bearing boundaries. A boundary that groups by identity or node co-tenancy forces heterogeneous tiers into one segment and will (correctly) abstain. Tag the co-resident segments with a shared domains value instead: when such a tag couples an externally-reachable segment with a protected one, Step 9 surfaces it as a cross-tier-domain blast-radius/co-tenancy finding (the signal is otherwise invisible to zoning). Prefer a logical (k8s-first) perspective for workloads.resolvedSource: 'declared' is already operator-ratified — show it as declared; never overwrite it with a fresh scout proposal (on resume/drift too).implicit_deny_enabled (boundary blocks traffic by default), allow_any_inbound (allows unrestricted inbound), egress_filtering ("deny_all" | "allow_list" | "allow_all" | "unknown" — outbound policy).Apply the accepted refinements as a split write — proposed ≠ set, so only accepted rows persist:
zone / planes → structure.json (the boundary's own fields)attributes/boundaries/<id>.jsonAn unconfirmed boundary persists no zone — it stays — and is counted as unclassified at Step 9, unless it is a structural container (structural: true), which renders — structural and is excluded from the unclassified count.
Auto-skip: Skip refinement only when boundary_hierarchy_quality factor = 1.0 (hierarchy depth ≥ 2, no single-child boundaries, no external entities in internal boundaries) AND there are no unratified zone proposals, showing:
Boundary hierarchy is well-structured (quality factor: 1.0), no zone proposals pending. Skipping refinement.
Mark as [auto-skip] in the progress table. A perfect hierarchy that still has zone proposals renders the trust table as the Step-4 interaction (the hierarchy is what's "already well-structured"); only a perfect hierarchy with no proposals skips. User can still jump to Step 4 explicitly.
Update state: currentState → STRUCTURE_COMPLETE, add STRUCTURE_COMPLETE to completedStates.
Connect components with data flows to complete the structural model.
Review existing data flows from discovery
Identify components without any data flows (orphaned) — prompt user to connect them
Check for commonly missing operational flows and prompt for each:
For each new flow:
Write new flows to dataflows.json
Validate: mcp__plugin_dethereal_dethereal__validate_model_json
Ratify risk-bearing crossings as approved channels. Now that the flows exist, surface the boundary crossings that carry trust risk and let the operator declare approved channels (conduits) for them — one batched gate, in the same step (not a second round-trip). This is selective by design: do not elicit every crossing; ordinary in-zone and child↔parent crossings are inheritance-implied and never surfaced.
Compute the crossings: call mcp__plugin_dethereal_dethereal__validate_model_json(action: 'zoning', assets: 'skeleton') and read its findings. The external-ingress findings are the risk-bearing crossings to ratify — an external-tier boundary (Open internet / Trusted external) with a modelled flow into a trusted tier (Internal / Restricted) and no approved channel. Each finding carries boundaryId (the source) and peerId (the target boundary) — use them directly; never re-walk the flow graph yourself. If there are no such findings, skip this sub-step (no empty prompt).
For each finding, lift a draft justification from the description of the flow crossing boundaryId → peerId. Do not re-propose a crossing whose source boundary already carries a conduit to that peer (Read its conduits[] first — resume/drift safety, mirroring Step 4's "don't re-propose a ratified row").
Render one batched gate, in display names (never raw enums — see guidelines-core.md):
## Approved channels
Risk-bearing crossings — declared intent, not enforcement. Ratify as approved channels, or adjust:
| Channel (source → peer) | Draft justification |
|------------------------------|----------------------------------------------|
| Trusted external → Internal | Vendor sync pushes orders into the order svc |
Ratify all, or adjust specific rows?
Apply as a split write — proposed ≠ set, so only ratified rows persist. For each ratified row, append an OUTBOUND-only conduit to the source boundary's conduits[] in structure.json (via Edit, the same split-write locus as Step 4):
{ "peerId": "<target boundary id>", "direction": "OUTBOUND", "justification": "<non-empty>" }
Write the OUTBOUND end only — never an INBOUND mirror (the read path re-derives it, and a lone inbound conduit is rejected by validation). Never populate controlRefs — an approved channel is declared intent, not an enforced control (flow analysis never reads channel-absence as a deny).
A risk-bearing crossing the operator does not ratify stays a plain flow and re-surfaces at Step 9 as an external-ingress finding (advisory, never sync-blocking). Crossings into asset-bearing boundaries aren't classifiable yet (assets are set at Step 7) — those surface at Step 9 too.
Update state: currentState → ENRICHING, add ENRICHING to completedStates. Per the canonical Phase-to-Step Mapping, Steps 5-8 all operate within the ENRICHING state.
Show post-action footer before the session break:
[done] Data flow mapping complete. Quality: X/100.
After Step 5 completes, insert a size-calibrated checkpoint.
Count components in structure.json:
Small models (< 15 components):
Your model structure is complete and saved. You can continue enrichment
now or resume later — your progress is saved.
Consider committing your model before enrichment (clean revert point).
Continue now? (yes / later)
Large models (≥ 15 components):
Your model structure is complete and saved. For models this size,
starting enrichment in a fresh session produces better results — the
enrichment phase reads model files from disk and doesn't need the
discovery context.
Consider committing your model before enrichment (clean revert point).
Continue now? (yes / later)
If the user says "later": show resume instructions and stop:
To resume: /dethereal:threat-model <model-path>
Your progress is saved at Step 5 (ENRICHING).
If the user says "yes" (even on a large model): proceed to Step 6 without re-asking. The recommendation is informational, not blocking.
Delegate classification to Agent(security-enricher) per the classify skill workflow.
activeModules from .dethereal/scope.json — classification searches only within active modules (same module-scoped approach as Step 3)scope.json to components; on no component match, also match against data-item names. An unresolved crown-jewel declaration is a blocking confirm — unmatched, it silently drops to Tier 4 in every downstream pass (enrichment priority, surface, control pass), which is the one gap worth stopping the guided flow for/dethereal:classify)mcp__plugin_dethereal_dethereal__generate_attribute_stubs(directory_path) to write class template attribute stubs for all classified elementsState: no transition — already at ENRICHING from Step 5.
Model each sensitive data type once, then associate it with every element that handles it across its lifecycle — its origin (EXTERNAL_ENTITY / PROCESS), the flows that carry it, the components that process or store it, and the boundaries that contain it. dataItemIds is valid on components (all types), data flows, and boundaries.
compliance_drivers from scope.json for regulatory mappingsensitivity scale — restricted | confidential | internal | public — via the regulatory flag → sensitivity-floor mapping (e.g. PCI cardholder/PHI → restricted, PII/GDPR personal → confidential). Regulatory labels go in regulatory_flags, never in the sensitivity fieldData item associations:
| Element (type) | Data Item | Sensitivity | Compliance | Confirm? |
|----------------|-----------|-------------|------------|----------|
| User (external entity) | User credentials | confidential | SOC2, GDPR | Y |
| User Login → Auth (flow) | User credentials | confidential | SOC2, GDPR | Y |
| payment-db (store) | Customer records | confidential | GDPR | Y |
| API → DB (flow) | Customer records | confidential | GDPR | Y |
| Cache (process) | Session tokens | internal | SOC2 | Y |
data-items.json and link them via dataItemIds on each handling elementmcp__plugin_dethereal_dethereal__match_classes(elements: [{name, description}, ...], classLabel: 'DATA', moduleIds: [...], topN: 3), confirm matches (auto-accept exact_name), write confirmed classData onto the items in data-items.json, then call mcp__plugin_dethereal_dethereal__generate_attribute_stubs(directory_path) so attributes/dataItems/<id>.json stubs exist before Step 8 enrichment. If no suitable DATA class exists, leave the item unclassified and note the gapmcp__plugin_dethereal_dethereal__validate_model_json(action: 'zoning') (no assets arg — the full phase, assets populated). This is the promotion deferred at Step 4, and it only ever tightens.
proposedTier === 'RESTRICTED' AND resolvedZone !== 'RESTRICTED' (not already there) AND declaredZone is null or INTERNAL. Never touch a row the operator declared PUBLIC/EXPOSED/VENDOR/UNTRUSTED (exposure outranks asset pull — the engine keeps those as-is).zone: 'RESTRICTED' to structure.json only for rows the operator confirms this turn. A boundary the operator explicitly declared INTERNAL is offered here (the human decides) — it is never silently overwritten, consistent with the Step-4 "don't overwrite a ratified zone" rule. Frame it as completion, not reversal (display names, never raw enums):
## Restricted promotion — completing the trust skeleton
The promotion we deferred at Step 4: now that data sensitivity and the flow graph exist, these
Internal boundaries qualify for Restricted. This is a safe-direction change (Internal → Restricted)
— tightening only; declared zones are never loosened or overwritten.
| Boundary | Now | → Proposed | Why |
|------------|----------|------------|---------------------------------------------------|
| payment-db | Internal | Restricted | holds Card data (PCI), ingress only from App Tier |
Promote all, or adjust? Boundaries that hold assets but can't be Restricted yet (reachable from a
DMZ, or vendor-touched) stay Internal and are flagged at Step 9 — not promoted here.
structure.json (zone: 'RESTRICTED' on the boundary; same split-write locus as Step 4). Boundaries that hold an asset but did not qualify are left untouched — they surface as the Step-9 under-protected finding, not here.State: no transition — already at ENRICHING.
Delegate to Agent(security-enricher) for comprehensive security attribute enrichment.
Agent(security-enricher)generate_attribute_stubs (run during classification and Step 7). The enricher reads the configuration guide from .dethereal/class-cache/<classId>.json, discovers values from code/IaC, asks the user for undiscoverable attributes, and sets all template fields (100% coverage)crown_jewel, credential_scope, monitoring_tools). The attribute-file crown_jewel is a derived copy — structure.json crownJewel is the single source of truth read by the tier sweep; never let crown-jewel status live only in the attribute file.deny, fallback, fail_open)staleElements[] first (if any exist from backward transitions)MITRE ATT&CK tactic coverage is derived server-side from Exposure.exploitedBy (see /dethereal:surface §5). The enricher does not annotate MITRE techniques on attribute files.
3. Receive the enricher's return payload: the compact summary (enriched element count + template coverage metrics), the proposal table of values it set, and the "Operator confirmations needed" list (per the enricher's non-interactive return contract — every value taken on a defensive default or undiscoverable from code, e.g. enrichment_note-flagged attributes).
4. Read updated attribute files from disk
5. Relay (mandatory). The enricher ran non-interactively and could not reach you — it made evidence-based decisions and self-confirmed nothing. Render its proposal table and the "Operator confirmations needed" list as a markdown block in the conversation (see Presentation Discipline), then offer one batched confirm/adjust prompt before the Control Pass Offer:
Edit before proceeding. Do not silently accept the defaults on the operator's behalf.State: no transition — already at ENRICHING from Step 5.
After Step 8 (Enrichment) completes, offer a session break for the control assignment pass:
Enrichment complete. Quality: X/100.
Ready for control assignment (~N prompts). Continue now or resume later?
[continue] Run control pass now
[later] Resume with /dethereal:enrich --focus controls
Compute N from boundary count: N = B + 2 (B enforcement prompts + 1 detection + 1 governance). For B=0, N = 3.
If "continue":
Agent(security-enricher) as a new agent instance with control-pass context@docs/controls-enrichment.mdIf "later":
To resume: /dethereal:enrich --focus controls
Your enrichment progress is saved. Control assignment can be done at any time.
Proceed to Step 9 (Validation) without controls. The quality score's control_coverage_rate factor reflects the gap.
Delegate to Agent(model-reviewer) for quality assessment.
Agent(model-reviewer).dethereal/quality.json (the threat-modeler has Write access; the model-reviewer is read-only)AskUserQuestion label:
### Zoning Coherence block) — advisory, never sync-blocking (Gate 3 is unchanged). The rolled-up "N unclassified" line here is the unclassified count the Step-4 trust gate forward-references — one mechanism, not two. A structural container is rendered with its display roll-up (— structural · spans <min>…<max>, plus · contains crown jewels / · includes a vendor enclave / · N descendant segments still unclassified from the payload's summary) — the abstention is made legible, not silent. The roll-up is full-phase (Step 9) only; the Step-4 skeleton table shows the bare — structural glyph.If Gate 3 passes (quality ≥ 70, all Gate 3 criteria met):
currentState → REVIEWED, add ENRICHING to completedStatesIf Gate 3 fails:
ENRICHINGPush the model to the platform for analysis.
Before pushing, verify Gate 2 (sync-blocking) criteria: manifest completeness, structure validity (≥1 boundary, ≥1 component, ≥1 data flow), reference integrity, no orphaned attribute files. Call mcp__plugin_dethereal_dethereal__validate_model_json(action: 'validate'). If Gate 2 fails, show the specific failures and stop — do not push a broken model.
Read ~/.dethernety/tokens.json. Find the entry keyed by the platform URL (DETHERNETY_URL env var, default http://localhost:3003).
/dethereal:login first, or skip sync for now."Consent is orthogonal to auth — passing the auth check (including auth-disabled dev platforms, which count as authenticated) is permission to reach the platform, not permission to publish this model. Before executing the push, ask explicitly:
Model passed Gate 2 and is ready to publish to <platform URL>.
Push to platform now? (push / skip — resume later with /dethereal:sync push)
/dethereal:sync push as the resume next-step. Never auto-push on the operator's behalf just because auth succeeded.Check manifest.model.id:
mcp__plugin_dethereal_dethereal__import_model with directory_pathmcp__plugin_dethereal_dethereal__update_model with delete_orphaned: truePushed "<model-name>" to platform.
<N> boundaries, <M> components, <K> flows, <J> data items.
Platform model ID: <id>
Server IDs written to local files.
Commit these changes to preserve sync state.
Link local countermeasures to platform-computed exposures so that defense coverage analysis credits existing controls.
Pre-check: Scan attribute files for components that have controls defined. If no countermeasures exist in the model:
No countermeasures defined. After analysis completes, run /dethereal:surface
to see control gaps and exposure distribution.
Skip to README generation.
If countermeasures exist:
mcp__plugin_dethereal_dethereal__manage_exposures(action: 'list') to get platform-computed exposures## Exposure-to-Countermeasure Linking
| Exposure | Component | Candidate Countermeasure | Link? |
|----------|-----------|------------------------|-------|
| SQL Injection | payment-db | Input validation control | Y |
| Auth Bypass | api-gateway | OAuth2 enforcement | Y |
| Data Exfil | user-db | Encryption at rest | ? |
Link all? (yes / modify / defer)
mcp__plugin_dethereal_dethereal__manage_countermeasures to create the linksAfter Step 10 (or at workflow end if sync was skipped), generate README.md per the threat-modeler's README Generation Protocol:
# <Model Name> with auto-generated noticeAt any point during Steps 6–9, if the user requests structural changes (adding or removing elements), follow the Backward Transition Protocol from the threat-modeler agent:
currentState to STRUCTURE_COMPLETE.dethereal/quality.jsonmodel_signed_off if presentstaleElements[]The user can then proceed forward again from Step 4.
[done] Threat model "<name>" complete. Quality: X/100 (<label>). State: REVIEWED.
[next] Run analysis on the platform, then /dethereal:surface (review attack surface)
If sync was skipped:
[done] Threat model "<name>" complete. Quality: X/100 (<label>). State: <state>.
[next] /dethereal:sync push (publish to platform for analysis)