> For the complete documentation index, see [llms.txt](https://docs.heeler.com/mrecEO40m5D6bt7Pq5pE/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.heeler.com/mrecEO40m5D6bt7Pq5pE/solutions-and-use-cases/threat-modeling-with-service-decomposition.md).

# Threat-Model a Service

Threat-model a service using a model built from its correlated repository and cloud data — from the platform export, an agent skill, or the MCP server.

A **Service Decomposition export** is a structured model of a single service — its infrastructure, communication paths, application surface, dependencies, and correlated security findings — as one JSON file. It is assembled from the repository and cloud deployments Heeler has correlated for that service, so it describes the deployed system rather than an intended design. Feed it to an LLM or a threat-modeling tool as input.

**For:** Security architects and engineers threat-modeling a specific service. **Prerequisites:** the service **correlated** — its repository and cloud deployments connected — so the exported model reflects what's actually running.

## Choose how you run it

The same decomposition backs all three paths — pick the one that matches where you work.

| Path                                                                       | Best for                                                                                                | What you get                                                                                     |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| **Platform export** (below)                                                | A one-off model of a service; handing the JSON to any LLM or threat-modeling tool                       | `service-<id>-<name>_threat_context.json` you download and prompt with yourself                  |
| [**Agent skill**](#run-it-from-your-coding-agent) `heeler-threat-modeling` | Repeatable threat modeling from the repo you're working in; keeping the artifacts under version control | A context file **and** a ready-to-run PASTA/STRIDE prompt written to disk, then the model itself |
| [**MCP server**](#assemble-it-live-with-the-mcp-server)                    | Reviewing a change in progress, or a service you're actively editing                                    | Live context pulled on demand — no export file, scoped to the code in hand                       |

The export and the skill write files you can attach to a review or compare against a later run. MCP writes nothing to disk and is scoped to the code you have open.

## Generate the export

Once a service is correlated (its repository and cloud deployments are connected), export it from the catalog:

1. Go to **Catalog → Services** and open a service.
2. Stay on the **Model** tab (the default view) — this is the infrastructure graph the export is built from.
3. Click **Export** (top-right) → **Service Decomposition**.

Heeler downloads a JSON file named `service-<id>-<name>_threat_context.json`.

<figure><img src="https://414480750-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXP3dp2kecwKA2KvYkntz%2Fuploads%2Fgit-blob-842f4dff389a1d4e93c01e2e358b05f2a1bb2a84%2Fcc-svc-decomposition-export.png?alt=media" alt="A service&#x27;s Model tab with the Export menu open, showing SBOM and Service Decomposition."><figcaption><p>Export → Service Decomposition, from a service's Model tab.</p></figcaption></figure>

{% hint style="info" %}
The same **Export → Service Decomposition** action is available from the row menu on the **Catalog → Services** list, which exports the model without opening the service.
{% endhint %}

## What's in the JSON

The file is a single object — `export_type: heeler_threat_model_context`, `schema_version: 2.0`, and a `generated_at` timestamp — that layers architecture, runtime, and risk into one model.

{% hint style="warning" %}
**Schema 2.0 is a breaking change from 1.0.** CVSS fields are renamed, the per-deployment dependency lists are replaced by one service-level catalog that deployments reference, static-analysis findings are split across three sections, and endpoint rows are collapsed to routes. There are no compatibility aliases, so a consumer written against 1.0 needs updating.
{% endhint %}

In the field tables below, **`?` marks a value that can be null** — `string?` is a string or null, `string[]?` is an array or null. A type without `?` is always present, and an array without `?` is always an array, though it may be empty.

### Ordering and stability

Collections are emitted in a defined order — vulnerabilities by identifier, findings by file and line, dependencies by ecosystem then name, endpoints by path, infrastructure graph nodes by id, and graph links by source, target, then key — so a diff between two exports of the same service shows what changed rather than what merely moved.

Two exports are **not** byte-identical because `generated_at` moves on every run. When the source data is unchanged, the ordered sections — including `deployments[].infrastructure_graph` — remain stable.

Ignore `generated_at` when comparing complete exports, or diff the ordered sections directly.

### Top-level sections

| Section                   | Scope                     | What it contains                                                                                                                                                                                       |
| ------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`service`**             | Service                   | Identity, tier, language and ecosystem, the people and teams who own it, and its recorded data-handling annotations.                                                                                   |
| **`deployments[]`**       | Service *or* deployment   | One entry per active deployment — environment, cloud type, account, DNS names, source changeset — each carrying its own infrastructure graph, images, functions, endpoints, and dependency references. |
| **`api_routes[]`**        | Service *or* deployment   | The API security context of every route the export contains: authentication, authorization, specification, exposure, personal data, management surface, and MCP.                                       |
| **`service_connections`** | Service                   | Inter-service edges as **incoming** and **outgoing**, with the shared resources that evidence them — your observed **trust boundaries**.                                                               |
| **`vulnerabilities[]`**   | Code root *or* deployment | Active SCA findings with CVSS and EPSS scoring, exploitability signals, and the recommended fix version.                                                                                               |
| **`sast_findings[]`**     | Code root                 | Active static-analysis findings in application code.                                                                                                                                                   |
| **`iac_findings[]`**      | Code root                 | Active infrastructure-as-code findings — Dockerfiles, Terraform, Kubernetes manifests.                                                                                                                 |
| **`cicd_findings[]`**     | Code root                 | Active CI/CD configuration findings.                                                                                                                                                                   |
| **`secrets[]`**           | Repository                | Detected credentials with branch-tip visibility, exception state, and the action each one calls for. **Secret values are never exported.**                                                             |
| **`dependencies[]`**      | Service                   | The one catalog of packages — from code and from runtime — that deployments point into.                                                                                                                |
| **`data_assets[]`**       | Service *or* deployment   | Datastores on the service's deployment graphs, with their aggregate data posture.                                                                                                                      |
| **`agent_files[]`**       | Repository                | AI agent files in the repository's current catalog, with assessment status and risk score.                                                                                                             |
| **`_metadata`**           | —                         | What the export is, which mode it ran in, and the per-section manifest.                                                                                                                                |

**Scope** is the grain a section is reported at. It is restated per section in `_metadata.manifest`, and it matters most when you scope an export to one deployment — see [Service exports and deployment exports](#service-exports-and-deployment-exports).

### `service`

| Field                         | Type        | What it is                                                                                                                                                                                                                                                                                          |
| ----------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`, `description`, `tier` | `string?`   | Service identity and its assigned tier.                                                                                                                                                                                                                                                             |
| `language`, `ecosystem`       | `string?`   | Primary language and package ecosystem.                                                                                                                                                                                                                                                             |
| `cloud_types`                 | `string[]?` | The cloud providers this service runs on.                                                                                                                                                                                                                                                           |
| `owners`                      | object      | People, keyed by owner type — `tech_lead`, `security_lead`, `assignee`. All three keys are always present; each is a sorted list of email addresses, and any of them can be empty.                                                                                                                  |
| `heeler_teams`                | object\[]   | `{ id: integer, name: string }` — the teams that effectively own the code. A code-root override wins over the repository's teams, so this names the team a reader would actually be routed to.                                                                                                      |
| `annotations`                 | object\[]   | `{ key: string, value: string, source: string }` — the service's recorded data-handling facts. Keys are `Handles PII`, `Handles PHI`, `Data Categories`, `Data Severity`, and `Data Regulations`. Every recorded row is kept: where two sources disagree, both rows appear with their own `source`. |

### `deployments[]`

One entry per active deployment. Each nests the layers that make the model runtime-accurate.

| Field                                     | Type        | What it is                                                                                                                                                                                                                                                                                                                                  |
| ----------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                      | integer     | Which deployment this is.                                                                                                                                                                                                                                                                                                                   |
| `environment`, `cloud_type`, `account_id` | `string?`   | Where it runs.                                                                                                                                                                                                                                                                                                                              |
| `dns_names`                               | `string[]?` | DNS names resolving to it.                                                                                                                                                                                                                                                                                                                  |
| `source_changeset`                        | `string?`   | The commit the deployed artifact was built from.                                                                                                                                                                                                                                                                                            |
| `infrastructure_graph`                    | `object?`   | The cloud topology as `nodes` and `links`: compute, data stores, queues, storage, networking, and IAM roles, the relationship edges between them (network paths, IAM permissions, internet-exposure paths), and per-node cloud metadata. Null when the deployment has no graph — which the manifest flags rather than leaving you to infer. |
| `container_images`                        | object\[]   | `{ repository_name: string, image_tag: string }`.                                                                                                                                                                                                                                                                                           |
| `serverless_functions`                    | object\[]   | `{ runtime: string, handler: string?, iam_role: string? }`. `iam_role` is null for Azure Function Apps, which have no execution role in the Lambda sense.                                                                                                                                                                                   |
| `endpoints`                               | object\[]   | The application's HTTP surface, one row per route per source file — see below.                                                                                                                                                                                                                                                              |
| `dependency_refs`                         | object\[]   | `{ ecosystem: string?, name: string, version: string, bundle_path: string[] }` — references into the top-level `dependencies` catalog. All four fields together are the identity; the package object itself appears once, in the catalog.                                                                                                   |

{% hint style="info" %}
**Per-node cloud metadata is AWS-only.** Nodes for ECS tasks, RDS instances, ElastiCache clusters, SQS queues, IAM roles, security groups, and Lambda functions carry their encryption, networking, and compute settings. Nodes from other providers carry their `category` and nothing further, so absence of an encryption setting on a GCP, Azure, Kubernetes, or Vercel node is not evidence that encryption is off.
{% endhint %}

#### `deployments[].endpoints[]`

Identity is (`method`, `path`, `file_name`) — one row per route per declaring file per deployment, not one row per source line.

| Field                                | Type       | What it is                                                                                                                                                                                           |
| ------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `method`, `path`, `file_name`        | `string?`  | The route and the file that declares it. `method` is upper-cased. All three are nullable, so all three can be null on a partially resolved row — treat the triple as the identity, not `path` alone. |
| `line_number`                        | `integer?` | The lowest line number among the source rows collapsed into this route.                                                                                                                              |
| `internet_accessible_deployment_ids` | integer\[] | The exported deployments that both serve this route **and** have an internet-accessible compute node in their own infrastructure graph.                                                              |

`internet_accessible_deployment_ids` is deliberately narrow. A deployment id appears only when that deployment's own graph shows an accessible compute node; a route is not listed because it is declared public in code, because infrastructure is merely internet-*facing*, because a different deployment of the service is accessible, or because the service-level `api_routes[].exposure` says public. An empty list therefore means "no exported deployment is known to serve this route from the internet", not "internal".

The endpoint row carries no API security context of its own. Resolve it into `api_routes` by (`method`, `path`).

### `api_routes[]`

The API security context catalog, one entry per (`method`, `path`). This is service-grain data — the same route served by four deployments has one entry — so it sits at the top level rather than being copied into every deployment. Membership is the routes this export actually contains that have an API record behind them; it narrows with a deployment export, and a route with no record simply has no entry.

Almost every field here is nullable, because each is populated only when analysis established it. **Absence is not a negative finding:** `auth_protected: null` means auth was not determined, not that the route is unauthenticated.

| Fields                                                                                             | Type       | What they tell you                                                                                                                                                                                                                                                                                      |
| -------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `method`, `path`                                                                                   | string     | The catalog identity that endpoints resolve by.                                                                                                                                                                                                                                                         |
| `framework`, `protocol`, `direction`, `endpoint_type`, `endpoint_type_confidence`, `consumer_type` | `string?`  | What kind of route this is and who calls it, with the confidence of the classification.                                                                                                                                                                                                                 |
| `auth_protected`, `is_auth_issuer`                                                                 | `boolean?` | Whether the route authenticates, and whether it mints credentials.                                                                                                                                                                                                                                      |
| `auth_status`, `auth_method`, `auth_protocol`, `auth_protocol_confidence`, `auth_provider`         | `string?`  | How it authenticates and against which provider.                                                                                                                                                                                                                                                        |
| `auth_protocols`                                                                                   | string\[]  | Every protocol the route accepts, sorted.                                                                                                                                                                                                                                                               |
| `auth_chain`, `guard_names`                                                                        | string\[]  | The middleware and guards the route runs, **in their stored order** — that order is the sequence they execute in, so it carries meaning. Every other list on the entry is sorted.                                                                                                                       |
| `security_access`, `security_rule_file`                                                            | `string?`  | The authorization rule that applies, and the file it is declared in.                                                                                                                                                                                                                                    |
| `security_rule_line`                                                                               | `integer?` | The line that rule is declared on.                                                                                                                                                                                                                                                                      |
| `security_roles`                                                                                   | string\[]  | The roles the rule admits, sorted.                                                                                                                                                                                                                                                                      |
| `csrf_protection`, `cors_policy`                                                                   | `string?`  | Whether each control is configured. The setting names only — no policy detail strings.                                                                                                                                                                                                                  |
| `spec_documented`, `spec_deprecated`                                                               | `boolean?` | Whether the route appears in an API specification, and whether the spec marks it deprecated.                                                                                                                                                                                                            |
| `spec_auth_alignment`                                                                              | `string?`  | Whether the spec's declared auth matches what the code does.                                                                                                                                                                                                                                            |
| `params`                                                                                           | object\[]  | Parameter **shape**: `{ name: string, in: string?, type: string?, required: boolean? }`. Never example or default values.                                                                                                                                                                               |
| `request_fields`, `response_fields`                                                                | object\[]  | Body **shape**: `{ name: string, type: string?, required: boolean?, nullable: boolean? }`. `nullable` is null today — the current writer emits no such key.                                                                                                                                             |
| `exposure`, `exposure_confidence`                                                                  | `string?`  | The service-grain exposure verdict and how confident it is. Per-deployment truth lives in the endpoint's `internet_accessible_deployment_ids`, not here.                                                                                                                                                |
| `exposure_signals`                                                                                 | `object?`  | `{ graph_exposure: string, route_visibility: string? }`, where `graph_exposure` is `public`, `dark`, or `uncorrelated`.                                                                                                                                                                                 |
| `pii_consumes`, `pii_transmits`                                                                    | `boolean?` | Whether the route takes in or hands back personal data.                                                                                                                                                                                                                                                 |
| `pii_categories`, `pii_regulations`                                                                | string\[]  | The categories involved and the regulations that follow, sorted.                                                                                                                                                                                                                                        |
| `pii_severity`                                                                                     | `string?`  | How sensitive that data is.                                                                                                                                                                                                                                                                             |
| `pii_schema_entities`                                                                              | object\[]  | `{ entity_id: integer, name: string, kind: string, direction: string }` — the data-inventory entities this route reads or returns, with `direction` of `request` or `response`.                                                                                                                         |
| `management_exposure`                                                                              | `object?`  | `{ actuator_sensitivity: string?, exposure_include_pattern: string?, exposure_exclude_pattern: string?, separate_management: boolean?, env_show_values: string? }` — the actuator/management surface configuration. Patterns and tokens only; environment variable names and values are never exported. |
| `mcp`                                                                                              | `object?`  | `{ server: boolean?, transport: string?, tools: string[], resources: string[], prompts: string[], capability_names: string[] }` — present when the route is an MCP server surface.                                                                                                                      |

### `service_connections`

Communication between this service and its neighbours, split into `incoming` and `outgoing`.

{% hint style="warning" %}
**Only the counterpart's side of a connection is populated.** The exported service is implied by which list the connection is in, and its own pair is null:

* On an **`outgoing`** connection, `destination_service_id` / `destination_service_name` name the counterpart, and `source_service_id` / `source_service_name` are **both null**.
* On an **`incoming`** connection, `source_service_id` / `source_service_name` name the counterpart, and `destination_service_id` / `destination_service_name` are **both null**.

A decoder that requires all four fields, or that reads `source_service_id` as the service you exported, will reject or misread ordinary output.
{% endhint %}

| Field                                                | Type      | What it is                                                                                                                                                     |
| ---------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source_service_id`, `source_service_name`           | `string?` | The counterpart on an `incoming` connection; null on an `outgoing` one. `source_service_name` is also null when the counterpart has no resolved name.          |
| `destination_service_id`, `destination_service_name` | `string?` | The counterpart on an `outgoing` connection; null on an `incoming` one, and `destination_service_name` is also null when the counterpart has no resolved name. |
| `edge_type`                                          | string    | How they communicate — `WEB_REQUEST`, `DATA_STORE`, `MESSAGE_BROKER`, and so on.                                                                               |
| `shared_resources`                                   | object\[] | The resources that evidence the edge — below. Always `[]` on an `incoming` connection.                                                                         |

**What direction means depends on the evidence.** For `WEB_REQUEST`, direction is call direction. For `DATA_STORE` and `MESSAGE_BROKER` edges carrying writer/reader evidence, an `outgoing` edge means this service writes and the counterpart reads, and `incoming` is the reverse. Network-role evidence establishes only that the two can reach each other — it claims no direction, and no data flow should be inferred from it.

`shared_resources[]` is the evidence behind an edge: the resources the two services both touch.

| Field                          | Type      | What it is                                                                                                                 |
| ------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `resource_id`, `resource_type` | string    | The shared resource, under the same identity `data_assets[]` and the infrastructure graph use.                             |
| `resource_name`                | `string?` | Its display name, when one resolved.                                                                                       |
| `role`                         | `string?` | A **summary** of the evidence below: `writer` or `reader` from permission edges, `network` from security groups — or null. |
| `can_delete`                   | boolean   | Whether the access includes deleting the resource. Read independently of `role`.                                           |
| `permissions`                  | string\[] | The granted permissions, sorted. Empty when the evidence is a network edge.                                                |
| `security_groups`              | string\[] | The security groups that establish reachability, sorted. Empty when the evidence is a permission edge.                     |

{% hint style="warning" %}
**A null `role` is not "no access".** A resource is listed because it carries evidence at all — permissions, a read, write or delete grant, or network reachability. `role` is a convenience summary over that evidence, and it is null whenever no single summary resolves. Read `permissions` and `can_delete` on such a row rather than skipping it; what a null `role` withholds is a claim about *direction*, not about access.
{% endhint %}

Evidence rides `outgoing` edges only, because it describes the access of the edge's source — which, on an outgoing edge, is the service you exported. `incoming` connections name the counterpart and the edge type with `shared_resources` empty, so no neighbour's permissions are disclosed through your export.

### `vulnerabilities[]`

Active SCA findings, one row per (vulnerability, package, bundle path).

| Field                                     | Type      | What it is                                                                                                                                                                                 |
| ----------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `vuln_id`                                 | string    | The advisory identifier.                                                                                                                                                                   |
| `title`, `description`, `severity`        | `string?` | What it is and how bad.                                                                                                                                                                    |
| `cvss_score`                              | `number?` | CVSS base score as published.                                                                                                                                                              |
| `cvss_vector`                             | `string?` | The published vector.                                                                                                                                                                      |
| `cvss_version`                            | `string?` | `2.0`, `3.0`, `3.1`, or `4.0`, parsed from the vector itself rather than assumed, because the catalog mixes v3 and v4 vectors. Null when there is no vector, or its prefix cannot be read. |
| `epss_score`, `epss_percentile`           | `number?` | Exploit Prediction Scoring System.                                                                                                                                                         |
| `exploit_level`                           | `string?` | Known exploitation maturity.                                                                                                                                                               |
| `in_kev`                                  | boolean   | Whether it is on the CISA Known Exploited Vulnerabilities list.                                                                                                                            |
| `known_ransomware_campaign_use`           | `string?` | Whether it is known to be used in ransomware campaigns.                                                                                                                                    |
| `cwe_ids`                                 | string\[] | CWE mappings.                                                                                                                                                                              |
| `affected_package`                        | string    | The package the finding is on.                                                                                                                                                             |
| `bundle_path`                             | string\[] | The bundle it was introduced through.                                                                                                                                                      |
| `recommended_version`                     | `string?` | The version an open remediation recommends for this package and bundle. Null when no open remediation covers it.                                                                           |
| `fix_available`                           | boolean   | Whether `recommended_version` is set — that is, whether a fix exists for you to take.                                                                                                      |
| `found_at_runtime`, `internet_accessible` | boolean   | Whether the package is loaded at runtime, and whether it sits behind an internet-accessible path.                                                                                          |

### `sast_findings[]`, `iac_findings[]`, `cicd_findings[]`

Three disjoint sections with the same row shape, partitioned on `finding_type`: application-code findings, infrastructure-as-code findings (`sast.iac`), and CI/CD configuration findings (`sast.cicd-config`). All three carry active findings only, with suppressed and orphaned rows excluded, so a finding you have already dismissed does not come back as a threat.

| Field                                              | Type        | What it is                                                                                                                                                                    |
| -------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                               | integer     | The finding's Heeler id.                                                                                                                                                      |
| `rule_id`, `rule_name`, `category`, `finding_type` | string      | What was detected, and which family it belongs to.                                                                                                                            |
| `severity`, `confidence`                           | string      | How bad, and how sure.                                                                                                                                                        |
| `language`                                         | string      | The language it was found in.                                                                                                                                                 |
| `file_path`                                        | `string?`   | The file the finding is reported against.                                                                                                                                     |
| `line`                                             | `integer?`  | The canonical primary location — for a taint finding, the **sink**.                                                                                                           |
| `line_file`                                        | `string?`   | Null when `line` is in `file_path`; when the sink is in another file, this names it. The pair (`line_file` or `file_path`, `line`) is always the coordinate `line` refers to. |
| `cwe_ids`                                          | `string[]?` | CWE mappings. Null, not `[]`, when none are recorded.                                                                                                                         |
| `triage_status`, `fix_status`                      | string      | Where the finding stands.                                                                                                                                                     |
| `is_suppressed`                                    | boolean     | Always false — suppressed rows are excluded — and present so that is explicit rather than inferred.                                                                           |
| `mitigated`                                        | `boolean?`  | Whether a mitigation was recorded.                                                                                                                                            |
| `analysis_mode`                                    | `string?`   | How the finding was produced.                                                                                                                                                 |
| `sensitive_data`                                   | `boolean?`  | Whether the finding touches sensitive data.                                                                                                                                   |
| `data_sensitivity`                                 | `string?`   | What kind: `credential`, `pii`, `user_input`, or `system_metadata`.                                                                                                           |
| `resource_type`                                    | `string?`   | The infrastructure resource type, populated on IaC rows.                                                                                                                      |

`sensitive_data` and `data_sensitivity` are independent. Neither is derived from the other, either can be set without the other, and either can be null while the other is set.

### `secrets[]`

Detected credentials. **Secret values are never exported**, under any setting.

| Field                                            | Type      | What it is                                                                                                                                                                                                                                                                                                                              |
| ------------------------------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rule_id`, `confidence`                          | string    | What matched, and how sure the detection is.                                                                                                                                                                                                                                                                                            |
| `provider`                                       | `string?` | The provider the rule belongs to, derived from `rule_id`. Null when the rule names none.                                                                                                                                                                                                                                                |
| `file_path`                                      | `string?` | Where it was found.                                                                                                                                                                                                                                                                                                                     |
| `validation_status`                              | string    | The raw stored verdict.                                                                                                                                                                                                                                                                                                                 |
| `action_bucket`                                  | `string?` | What to do about it: `rotate_now` (confirmed live), `rotate` (presumed live), `triage` (no verdict yet), `no_action` (confirmed inactive), or `expected` (a canary token). Null when this build has no bucket for the stored verdict — in which case read `validation_status` directly, rather than treating the secret as unimportant. |
| `visibility_state`                               | string    | Whether the secret is still present at the tip of the scanned branch: `present`, `absent`, or `unknown`. **`unknown` is not `absent`** — it means presence could not be established either way.                                                                                                                                         |
| `is_visible`                                     | boolean   | True only for a confirmed sighting at the branch tip.                                                                                                                                                                                                                                                                                   |
| `occurrence_count`, `path_count`, `commit_count` | integer   | How many times, in how many paths, across how many commits.                                                                                                                                                                                                                                                                             |
| `last_validated_at`                              | `string?` | When the verdict was last confirmed against the provider. Null when only a repository scan ever set it.                                                                                                                                                                                                                                 |
| `exception_status`                               | string    | Whether the secret is under an exception, from the secret row itself. Upper-case: `NO_EXCEPTION`, `FALSE_POSITIVE`, `ACCEPTED_RISK`, `RULE_CATEGORY_EXCEPTION`.                                                                                                                                                                         |
| `exception_reason`                               | `string?` | The same distinction from the exception's own record, lower-cased: `no_exception`, `false_positive`, `accepted_risk`, or `rule_category_exception`. Null when no exception row exists.                                                                                                                                                  |
| `exception_expires_at`                           | `string?` | When the exception lapses. Null when there is no exception, and also null for one that never expires.                                                                                                                                                                                                                                   |

### `dependencies[]`

The service's single package catalog: everything found in code on its code root, plus everything referenced by its active deployments. Each package object appears exactly once here, and deployments point at it with `dependency_refs`.

| Field                               | Type        | What it is                                                                                                            |
| ----------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------- |
| `name`, `version`                   | string      | The package.                                                                                                          |
| `ecosystem`                         | `string?`   | Its ecosystem. Part of the identity even when null.                                                                   |
| `bundle_path`                       | string\[]   | The bundle it was found under. Also part of the identity — the same package under two bundles is two entries.         |
| `is_direct`                         | `boolean?`  | Direct or transitive. Null when it is not recorded.                                                                   |
| `found_in_code`, `found_at_runtime` | boolean     | Where the package was seen. A package found only in code, with no deployment referencing it, is still in the catalog. |
| `deployment_ids`                    | integer\[]  | The active deployments that reference this version. Empty for a code-only package.                                    |
| `dependency_groups`                 | `string[]?` | The manifest groups it was declared in — `development`, `test`, and so on. Null, not `[]`, when none are recorded.    |
| `first_party`                       | `string?`   | The internal source this package resolves to when it is one of yours rather than a public package. Null otherwise.    |
| `is_compromised`                    | `boolean?`  | Whether the package is flagged as malicious.                                                                          |
| `malicious_package_ids`             | `string[]?` | The identifiers behind that flag. Null, not `[]`, when none are recorded.                                             |

### `data_assets[]`

The datastores on the service's deployment graphs that carry a recorded data posture — what this service actually stores data in, and what kind.

| Field                                      | Type       | What it is                                                                                |
| ------------------------------------------ | ---------- | ----------------------------------------------------------------------------------------- |
| `resource_id`, `resource_type`             | string     | The store, under the same identity the infrastructure graph and `shared_resources[]` use. |
| `resource_name`                            | `string?`  | Its display name, when one resolved.                                                      |
| `provider`, `account_or_project`, `region` | `string?`  | Where it lives.                                                                           |
| `handles_pii`, `handles_phi`               | boolean    | Whether it holds personal or health data.                                                 |
| `categories`, `regulations`                | string\[]  | The data categories it holds and the regulations that follow.                             |
| `severity`                                 | `string?`  | The aggregate data severity recorded against it.                                          |
| `deployment_ids`                           | integer\[] | The deployments whose graphs contain the store.                                           |

A store appears when it carries **any** of the five posture annotations, so a store marked as holding PII but never categorised is still listed.

### `agent_files[]`

AI agent files — instruction files, skills, and bundles — in the repository's current catalog, one row per top-level file or bundle.

| Field             | Type       | What it is                                                                                                                                  |
| ----------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `path`, `kind`    | string     | The file and its kind.                                                                                                                      |
| `is_bundle`       | boolean    | Whether it rolls up members.                                                                                                                |
| `analysis_status` | string     | `pending`, `skipped`, or `complete`. This says how far analysis got, **not** whether a score exists.                                        |
| `assessed`        | boolean    | Whether a score exists. Read this, not `analysis_status`, before treating a file as evaluated.                                              |
| `score`           | `integer?` | 0–100, where **100 is safest**. Null when unscored.                                                                                         |
| `at_risk`         | `boolean?` | Whether the score falls below Heeler's at-risk threshold of 70. Null when `assessed` is false — an unscored file is never reported as safe. |
| `assessed_intent` | `string?`  | What the assessment judged the file to be for.                                                                                              |
| `content_skipped` | boolean    | Whether the file's contents were skipped.                                                                                                   |
| `skip_reason`     | `string?`  | Why they were skipped.                                                                                                                      |
| `finding_counts`  | object     | Counts by category and severity, as `{ category: { severity: count } }`.                                                                    |
| `invocations`     | object\[]  | `{ target_path: string?, target_class: string, trigger: string, confidence: string }` — what this file invokes, and what triggers it.       |

### `_metadata` and the manifest

`_metadata` is the export describing itself.

| Field                     | Type           | What it is                                                                                                            |
| ------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------- |
| `description`, `sections` | string, object | Plain-language notes on what the file is and what each section means, written to be read by the model you feed it to. |
| `export_scope`            | string         | `service` or `deployment`. Stated outright so the mode is never inferred.                                             |
| `deployment_id`           | `integer?`     | The requested deployment in a deployment export; null otherwise.                                                      |
| `manifest`                | object         | One entry per section — below.                                                                                        |

**The manifest is how you know what an empty section means.** On its own, `secrets: []` is ambiguous. The manifest resolves it, section by section, so neither you nor the model has to guess.

Every section has an entry, keyed by section name, plus one group per exported deployment keyed `deployments.<id>.endpoints`, `.dependency_refs`, `.container_images`, `.serverless_functions`, and `.infrastructure_graph`.

| Field                      | Type      | What it is                                                                                                                                |
| -------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `scope`                    | string    | The grain the section was read at: `service`, `repository`, `code_root`, or `deployment`.                                                 |
| `sources`                  | string\[] | Which stores the rows came from.                                                                                                          |
| `status`                   | string    | `complete`, `partial`, or `not_supported` — below.                                                                                        |
| `count`                    | integer   | How many rows the section carries.                                                                                                        |
| `warnings`                 | string\[] | Codes, not messages, so you can branch on them — below.                                                                                   |
| `truncated`                | boolean   | Whether an upstream source cut its rows short.                                                                                            |
| `collected_at`, `snapshot` | `string?` | When the underlying data was collected and which changeset or catalog it came from, where an authoritative marker exists. Null otherwise. |

Statuses:

| Status          | What an empty section means under it                                                                                                                                                                                             |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `complete`      | Every persisted row for the section was read, and there were none. Heeler holds no rows for this service — which is not the same as the service being clean, because it says nothing about whether an upstream scanner ever ran. |
| `partial`       | The section was read, but something named in `warnings` limits what you can conclude from it.                                                                                                                                    |
| `not_supported` | The section cannot apply to this service at all — a repository-scoped section on a service with no repository, for example.                                                                                                      |

What none of the three means is that a read failed. There is no failure status: if an export query errors, the whole request fails rather than returning a half-filled document, so any document you hold was read successfully end to end.

Warning codes:

| Code                       | What it means                                                                                                                                                              |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `zero_rows_ambiguous`      | A deployment export found no vulnerability rows correlated to that deployment. The export never fills the gap with service-wide findings.                                  |
| `deployment_graph_missing` | That deployment has no infrastructure graph, so everything derived from it is absent rather than empty.                                                                    |
| `api_row_missing`          | At least one endpoint in that deployment resolves to no `api_routes` entry, so it has no API security context.                                                             |
| `catalog_missing`          | The repository has no current agent-file catalog, so `agent_files: []` cannot be read as "no agent files".                                                                 |
| `truncated`                | An upstream source truncated its rows. This one sets `truncated: true` and leaves a successfully read section `complete`; every other warning makes the section `partial`. |

### Service exports and deployment exports

There are three ways to run the export, and two of them produce a **deployment export**:

* **Export → Service Decomposition** from a service — the Model tab, or the row menu on **Catalog → Services** — covers the whole service.
* **Actions (⋯) → Export Service Decomposition** on a [deployment row](/mrecEO40m5D6bt7Pq5pE/catalog/deployments/deployment-detail.md) covers exactly that deployment.
* `--deployment-id` on the CLI's `threat-model context` or `threat-model prompt` does the same from the command line.

A deployment export is a deliberate hybrid: it narrows what a deployment correlation can narrow, and keeps service context for the rest. Each section states its own grain in the manifest, so the mix is never ambiguous. `_metadata.export_scope` tells you which mode produced the file.

| Section                                                | Whole service                     | One deployment                                                     |
| ------------------------------------------------------ | --------------------------------- | ------------------------------------------------------------------ |
| `deployments[]`                                        | Every active deployment           | Only the requested one                                             |
| `vulnerabilities[]`                                    | Active findings for the code root | Only findings correlated to that deployment and not yet remediated |
| `api_routes[]`, `data_assets[]`                        | Across every active deployment    | Narrowed to the requested deployment                               |
| endpoints, images, functions, graph, `dependency_refs` | For every active deployment       | Only for the requested deployment                                  |
| `sast_findings[]`, `iac_findings[]`, `cicd_findings[]` | Code-root scoped                  | Still code-root scoped                                             |
| `secrets[]`, `agent_files[]`                           | Repository scoped                 | Still repository scoped                                            |
| `dependencies[]`, `service_connections`, `service`     | Service scoped                    | Still service scoped                                               |

The `dependencies` catalog stays service-wide in both modes — it is built from the code root plus every active deployment's references — so a deployment export's catalog can hold packages that deployment does not use. Its `dependency_refs` are what narrow.

### What the export does *not* include

The model is deliberately technical, and deliberately free of payloads that would be unsafe to hand to a model you don't control.

**Judgment you supply alongside the JSON:**

* Business purpose, criticality, and the consequences of a compromise.
* Intended authentication and authorization *design* — the export describes what the code does, not what it was meant to do.
* The context of external users, partner APIs, and third-party integrations.
* Known compensating controls and constraints that live outside Heeler.

**Content withheld by design, in every version:**

* **Secret values**, hashes, and validation responses.
* **Source code** — no snippets, no taint source or sink excerpts. Findings carry coordinates, not code.
* **Agent-file contents**, evidence quotes, rationale, verdict summaries, and locators.
* **Environment variable names and values**, including from the management surface.
* **Raw cloud provider properties** and raw graph edge attributes — only the allowlisted fields above.
* **Example and default values** on API parameters and body fields. Shape only.
* Free-form notes, messages, and committer identity.
* Any previously generated threat-model text, so a new run is never anchored to an old conclusion.

## Feed it to an LLM for STRIDE analysis

The export is designed to be dropped straight into an LLM. The workflow is:

1. Provide the JSON as input.
2. Prompt the model to threat-model or security-review it, **adding the business context the JSON omits** (above).

This prompt template runs a **STRIDE** assessment. It requires the model to separate observed facts from inference, and to state what context is missing rather than assume it:

{% code title="STRIDE threat-modeling prompt" overflow="wrap" %}

```
You are a security architect performing threat modeling using the STRIDE methodology.

Please use the uploaded Service Decomposition JSON representing a runtime-informed view of a
deployed service, including observed infrastructure, dependencies, and security findings.

Instructions

Use the JSON as the primary source of runtime truth:
 Treat it as evidence of what is actually running in the environment
 Do not assume architecture or behavior not supported by the data
 Where information is missing, explicitly call out gaps rather than guessing

Tasks
 Identify system components, services, and infrastructure from the JSON
 Infer trust boundaries based on observed communication patterns and deployment context
 Map key data flows between components and external dependencies
 Identify threats using the STRIDE framework:
  Spoofing
  Tampering
  Repudiation
  Information Disclosure
  Denial of Service
  Elevation of Privilege
 Highlight high-risk areas based on:
  Internet exposure
  Privilege levels and access paths
  Vulnerabilities or security findings in the export
  Critical dependencies or central components

Reasoning Requirements
 For all analysis, clearly distinguish between:
  Observed facts (directly supported by the JSON)
  Reasonable inferences (derived from observed data)
  Missing context / assumptions (required to complete the assessment)

Do not invent architecture. If something is unclear or absent, explain why it matters.

Additional Context (if provided)
 Incorporate the following context to refine the analysis:
  Business purpose of the service
  Data sensitivity and classification
  Authentication and authorization model
  External users, APIs, or third-party integrations
  Known security controls or constraints

If this context is not provided, explicitly identify what is missing and how it impacts risk evaluation.

Output Format
 Structure your response as:
  Application summary
  Observed components and dependencies
  Inferred trust boundaries
  Data flow overview
  STRIDE threat analysis (grouped by component or boundary)
  High-risk findings and why they matter
  Missing context and key assumptions
  Recommended follow-up questions
  Priority risks and next actions

Important
 Focus on realistic, high-impact threats, not exhaustive enumeration
 Prioritize issues that are exploitable given the observed runtime context
 Use security findings in the JSON to support or prioritize threats
```

{% endcode %}

The JSON carries the vulnerabilities, SAST findings, and secrets already correlated to the service, so the model can tie threats to findings that exist — internet-exposed paths, over-privileged IAM, exploitable CVEs — rather than only to hypothetical ones.

## Run it from your coding agent

The **`heeler-threat-modeling`** [agent skill](/mrecEO40m5D6bt7Pq5pE/prevent/agent-skills.md) does the whole flow from inside Claude Code, Cursor, Copilot, OpenCode, Codex, or VS Code — it resolves which service you're in, pulls the context, writes the prompt, and produces the threat model. Ask for it in plain language:

> *"Threat-model this service with Heeler."*

{% hint style="info" %}
**Prerequisites:** [`heelercli`](/mrecEO40m5D6bt7Pq5pE/prevent/cli.md) installed and authenticated — either a `HEELER_API_KEY` environment variable or a valid local login. The skill checks both before it runs anything and stops with login instructions if either is missing.
{% endhint %}

### What the skill runs

Three [CLI](/mrecEO40m5D6bt7Pq5pE/reference/cli-command-reference.md) commands, in order:

{% stepper %}
{% step %}

#### Resolve the service

```bash
heelercli threat-model resolve-service-id --path . -q
```

Maps the directory you're in to a Heeler service ID, and reports which source it came from.
{% endstep %}

{% step %}

#### Fetch the context

```bash
heelercli threat-model context --path . --format json --output .heeler/tm-context.json -q
```

The same decomposition described above, saved as a file so the run is auditable.
{% endstep %}

{% step %}

#### Build the prompt

```bash
heelercli threat-model prompt --path . --framework PASTA --format markdown --output .heeler/tm-prompt.md -q
```

Assembles the context into a complete, framework-specific prompt.
{% endstep %}

{% step %}

#### Generate the model

The agent uses `.heeler/tm-prompt.md` **verbatim** as its input — it doesn't paraphrase or rebuild the prompt from memory, so the model you get is reproducible from the artifacts on disk.
{% endstep %}
{% endstepper %}

Both artifacts are written under `.heeler/`:

* **`tm-context.json`** — the raw context export, kept for auditability.
* **`tm-prompt.md`** — the structured prompt handed to the model.

### Resolving the service ID

`--path` resolution walks these in order, and the first match wins:

1. An explicit `--service-id` flag.
2. The `HEELER_SERVICE_ID` environment variable.
3. A `threat_modeling.service_mappings` entry in `.heeler.yaml` whose path contains your target path — the most specific match wins, so a monorepo maps cleanly.
4. `threat_modeling.default_service_id`.

Service IDs are numeric. If none resolves, the skill **stops and asks** rather than guessing — and gives you the exact command to re-run once you supply one.

{% code title=".heeler.yaml" %}

```yaml
threat_modeling:
  default_framework: PASTA
  default_service_id: "100"
  service_mappings:
    - path: .
      service_id: "100"
    - path: services/payments
      service_id: "200"
  prompts:
    redact_secrets: true
```

{% endcode %}

Framework defaults to **PASTA**; pass `--framework STRIDE` (or set `default_framework`) to switch. Both are supported everywhere the framework is accepted.

{% hint style="warning" %}
**Secret redaction is on by default.** Secret *values* never leave Heeler under any setting; with `redact_secrets` enabled the prompt also replaces secret **file paths** with `[redacted]`, so the prompt is safe to paste into a model you don't control. Turn it off with `--redact-secrets=false` only when you need the paths and trust the destination.
{% endhint %}

### What the generated prompt contains

`tm-prompt.md` contains:

| Section                        | Contents                                                                                                                                                                       |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Objective**                  | A practical PASTA or STRIDE model focused on exploitable paths, trust boundaries, and prioritized mitigations.                                                                 |
| **Service Context**            | Description, tier, language and ecosystem, cloud types, and owners.                                                                                                            |
| **Runtime/Deployment Surface** | Per deployment — cloud, account, DNS names, endpoints (method, path, source file), container images, and serverless functions with their IAM role.                             |
| **Service Connections**        | Incoming and outgoing edges by name and type — the observed trust boundaries.                                                                                                  |
| **Security Signals**           | Vulnerabilities ordered by severity, weighted up for internet-accessible and KEV-listed findings, then SAST findings with CWE mappings, then secrets with validation status.   |
| **Required Output**            | System overview and trust boundaries, threat scenarios, the framework matrix, prioritized mitigations, detection and monitoring recommendations, residual risk.                |
| **Output Format**              | An executive summary, a threat register table (ID, category, asset, entry point, boundary crossed, likelihood, impact, evidence, mitigation, owner), and the top five actions. |

### The validation pass

After the first model, the skill offers a second pass:

> *Do you want me to validate the top findings against repository evidence to confirm/deny likely false positives?*

Say yes and it re-checks each finding against the actual code, marking it **`confirmed`**, **`likely`**, or **`not supported`**, citing a concrete entry point and evidence path, then re-prioritizes on that basis. The default is the top five highest-impact findings.

### Other formats

The CLI isn't limited to what the skill uses:

| Command                | Formats                                       | Default    |
| ---------------------- | --------------------------------------------- | ---------- |
| `threat-model context` | `json`, `llm` (a compact counts-only summary) | `json`     |
| `threat-model prompt`  | `markdown`, `json`                            | `markdown` |

Add `--deployment-id` to either command to scope the model to a single deployment instead of all of them.

## Assemble it live with the MCP server

The [MCP server](/mrecEO40m5D6bt7Pq5pE/prevent/mcp.md) produces no export file. Your agent pulls Heeler context on demand as you work, which suits reviewing a change in progress rather than modeling a whole service.

There is no single "threat model" tool — the agent composes the same picture from the context it can reach:

| Ask the agent for…             | It pulls                                                                                                      |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| The service's posture and risk | A service risk brief and portfolio context                                                                    |
| The attack surface             | Endpoints and their auth and internet-accessibility status                                                    |
| What's actually reachable      | Deployment exposure and runtime data for the project                                                          |
| What's already known-broken    | SAST findings for the repository or the exact file you're editing, and prioritized dependency vulnerabilities |
| Supply-chain context           | Package investigation — where a package is used and its safest version                                        |

Four built-in prompts give this structure without you writing one:

| Prompt                         | What it runs                                                                                              |
| ------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `secure_development_checklist` | A pre-commit secure-development pass over the code in hand, including endpoint auth and security context. |
| `secure_code_risk_review`      | A focused risk review of a change — what could go wrong and what to verify.                               |
| `heeler_sast_pass`             | A code-security review that pulls and reasons over SAST findings.                                         |
| `heeler_sca_dependency_guard`  | A dependency check that surfaces and triages vulnerable packages.                                         |

A practical review sounds like: *"Using Heeler, review this service for security risk — pull its endpoints, exposure, and open findings, then tell me what an attacker reaches first."* The agent scopes to your repository, pulls what it needs, and reasons over live data rather than a snapshot.

{% hint style="info" %}
**Which to reach for.** MCP is read-only and live, so it's the better fit for *"is this change safe?"*. The skill and the export produce a dated artifact you can attach to a design review or re-run next quarter and diff. Teams commonly use both — MCP during development, the skill at a release or architecture checkpoint.
{% endhint %}

## Validate success

Whichever path you used:

* The output cites **specific** resources, endpoints, and findings from your own data — not generic threats — which tells you it reasoned from your runtime.
* Its high-risk section lines up with what you know is internet-exposed and over-privileged.
* Where the model flags **missing context** (business purpose, data classification, auth design), you have a concrete list of follow-ups to close the model out.

If you used the agent skill, two more:

* `.heeler/tm-context.json` and `.heeler/tm-prompt.md` both exist and the prompt names the service you expected — proof the service ID resolved correctly.
* After the validation pass, every top finding carries a **`confirmed`**, **`likely`**, or **`not supported`** mark with an evidence path.

## Related

* The **Model** tab (Catalog → Services → a service) — the infrastructure graph this export serializes.
* [Agent Skills](/mrecEO40m5D6bt7Pq5pE/prevent/agent-skills.md) — the full skill catalog, including `heeler-security-review` for a repository-level audit alongside this service-level model.
* [MCP Server](/mrecEO40m5D6bt7Pq5pE/prevent/mcp.md) — the live-context counterpart, and the built-in prompts listed above.
* [CLI](/mrecEO40m5D6bt7Pq5pE/reference/cli-command-reference.md) — every `threat-model` flag, for running this in CI rather than an agent.
* [Install Agent Skills](/mrecEO40m5D6bt7Pq5pE/get-started/set-up-developer-tooling/agent-skills.md) — rolling `heeler-threat-modeling` out across your repositories.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.heeler.com/mrecEO40m5D6bt7Pq5pE/solutions-and-use-cases/threat-modeling-with-service-decomposition.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
