> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mnemom.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Opt in to lockfile-hash substrate attribution

> Set the X-Mnemom-Lockfile-Hash header on outbound requests so AEGIS attributes behavior to a specific deployed dependency-graph at L0. SHA-256 digest of your resolved manifest; the hash is sent, the manifest is not.

The default [substrate fingerprint](/concepts/substrate-fingerprint) AEGIS stamps on every row covers `<provider>:<model>`. Setting the `X-Mnemom-Lockfile-Hash` header (and, optionally, the companion `X-Mnemom-Sdk-Version` header) extends the fingerprint to a four-component identifier that includes a hash of *your* deployed dependency graph — making AEGIS able to attribute behavioral deviation to a specific deployed version of your stack, not just the model and SDK level.

This guide is the runnable how-to. The conceptual surface is [substrate fingerprint](/concepts/substrate-fingerprint); the spec for the L1 read is [`/specifications/threat-state-response-schema`](/specifications/threat-state-response-schema).

## What you'll change

You will:

1. Compute a stable SHA-256 digest of your resolved package manifest at deploy time.
2. Set `X-Mnemom-Lockfile-Hash: <hex>` on every outbound request from your agent to the Mnemom gateway.
3. (Optionally) set `X-Mnemom-Sdk-Version: <package>@<version>` to tighten SDK attribution past the gateway's User-Agent fallback.
4. Verify the substrate is recorded as the four-component form via the integrity-checkpoint read path.

The two headers compose into the `substrate_id` stamped on every `integrity_checkpoints` row per the production trigger (migration 252). The four collapse forms are documented at [Substrate fingerprint — the four collapse forms](/concepts/substrate-fingerprint#the-four-collapse-forms).

## Step 1 — Compute the digest

The hash is SHA-256, 64 hex chars, lowercase canonical on the wire (mixed case is accepted and normalized; malformed input is rejected at the gateway with `400` and `X-Mnemom-Error: invalid-lockfile-hash`).

Compute the digest over whatever stable serialization of your resolved manifest best represents the deployed dependency graph. For most ecosystems the lockfile-on-disk bytes are the natural input.

<CodeGroup>
  ```bash bash (sha256sum) theme={null}
  # Most lockfile types: hash the file bytes as-shipped.
  LOCKFILE_HASH=$(sha256sum pnpm-lock.yaml | awk '{print $1}')
  echo "$LOCKFILE_HASH"
  # → e.g. 9e8a4f3c2b1d0e7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a
  ```

  ```python Python (hashlib) theme={null}
  import hashlib

  with open("pnpm-lock.yaml", "rb") as f:
      lockfile_hash = hashlib.sha256(f.read()).hexdigest()
  print(lockfile_hash)
  # → e.g. 9e8a4f3c2b1d0e7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a
  ```

  ```typescript TypeScript (node:crypto) theme={null}
  import { readFileSync } from "node:fs";
  import { createHash } from "node:crypto";

  const lockfileHash = createHash("sha256")
    .update(readFileSync("pnpm-lock.yaml"))
    .digest("hex");
  console.log(lockfileHash);
  // → e.g. 9e8a4f3c2b1d0e7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a
  ```
</CodeGroup>

Compute the digest at deploy time (CI / image build) and inject it into the agent's runtime environment as `MNEMOM_LOCKFILE_HASH`. Recomputing at every request adds I/O without changing the value between deploys.

## Step 2 — Set the headers on outbound requests

Both headers travel with the request to the Mnemom gateway. Workers handles header names case-insensitively; the casing below matches the gateway's `LOCKFILE_HASH_HEADER` and `SDK_VERSION_HEADER` constants in `mnemom-platform/gateway/src/substrate-fingerprint.ts`.

<CodeGroup>
  ```bash curl theme={null}
  curl -sS \
    -H "X-Mnemom-Api-Key: $MNEMOM_API_KEY" \
    -H "X-Mnemom-Sdk-Version: @anthropic-ai/sdk@0.65.0" \
    -H "X-Mnemom-Lockfile-Hash: $LOCKFILE_HASH" \
    -H "anthropic-version: 2023-06-01" \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"model":"claude-sonnet-4-6","messages":[...]}' \
    https://gateway.mnemom.ai/anthropic/v1/messages
  ```

  ```python Python (requests) theme={null}
  import os
  import requests

  response = requests.post(
      "https://gateway.mnemom.ai/anthropic/v1/messages",
      headers={
          "X-Mnemom-Api-Key": os.environ["MNEMOM_API_KEY"],
          "X-Mnemom-Sdk-Version": "@anthropic-ai/sdk@0.65.0",
          "X-Mnemom-Lockfile-Hash": os.environ["MNEMOM_LOCKFILE_HASH"],
          "anthropic-version": "2023-06-01",
          "Content-Type": "application/json",
      },
      json={"model": "claude-sonnet-4-6", "messages": [...]},
      timeout=60,
  )
  response.raise_for_status()
  ```

  ```typescript TypeScript (fetch) theme={null}
  const response = await fetch(
    "https://gateway.mnemom.ai/anthropic/v1/messages",
    {
      method: "POST",
      headers: {
        "X-Mnemom-Api-Key": process.env.MNEMOM_API_KEY!,
        "X-Mnemom-Sdk-Version": "@anthropic-ai/sdk@0.65.0",
        "X-Mnemom-Lockfile-Hash": process.env.MNEMOM_LOCKFILE_HASH!,
        "anthropic-version": "2023-06-01",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ model: "claude-sonnet-4-6", messages: [/* ... */] }),
    },
  );
  ```
</CodeGroup>

The two headers are independent opt-ins. Setting only the lockfile-hash header produces a 4-component fingerprint with an empty SDK slot (`<provider>:<model>::<lockfile-hash>`); setting only the SDK header produces a 3-component fingerprint (`<provider>:<model>:<sdk@ver>`); setting both produces the full 4-component form.

### What `X-Mnemom-Sdk-Version` adds

The SDK version is sourced from three places, in order:

1. The `X-Mnemom-Sdk-Version` header value, if present (this is the preferred path).
2. A User-Agent dispatch over seven known SDK families (`@anthropic-ai/sdk`, OpenAI Python, Anthropic Python, etc.), if the request's `User-Agent` matches one.
3. `null` (collapsing to the 3- or 2-component fingerprint form).

Setting `X-Mnemom-Sdk-Version` explicitly is the way to get reliable SDK attribution when you're sending requests from custom or non-canonical clients.

## Step 3 — What gets stored

| Attribute         | What the gateway records                                                                                                                                                                                                                     |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Hash              | Stored verbatim in `integrity_checkpoints.substrate_id` as the fourth component.                                                                                                                                                             |
| Manifest contents | **Never sent, never stored.** Only the hash crosses the wire.                                                                                                                                                                                |
| Validation        | Malformed (not 64 hex chars after normalization) → 400 + `X-Mnemom-Error: invalid-lockfile-hash`; request rejected before reaching the agent.                                                                                                |
| Lookup direction  | `substrate_id` is the cross-tenant axis identity AEGIS aggregates on. The cross-tenant aggregator buckets per `(substrate, vertical, pattern, source)`; your hash contributes to the substrate axis bucket for your specific deployed graph. |

### Privacy posture

The hash is a deterministic function of your lockfile contents. Reversing it to recover the lockfile requires brute-forcing the lockfile-content space, which is computationally infeasible for non-trivial lockfiles. For very small / trivial lockfiles (e.g., a single pinned dependency) the hash is in principle reversible to that content; if that matters for your threat model, do not opt in.

## Step 4 — Verify the substrate is being recorded

The canonical record is on `integrity_checkpoints.substrate_id`, readable via the customer-facing checkpoints endpoint:

```bash theme={null}
curl -sS \
  -H "X-Mnemom-Api-Key: $MNEMOM_API_KEY" \
  "https://api.mnemom.ai/v1/agents/$AGENT_ID/checkpoints?limit=1" \
  | jq '.[0].substrate_id'
```

After issuing one request with both headers set, the latest checkpoint's `substrate_id` should be the four-component form, for example:

```
"anthropic:claude-sonnet-4-6:@anthropic-ai/sdk@0.65.0:9e8a4f3c2b1d0e7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a"
```

There is no dedicated self-test endpoint — the checkpoint read is the verification path.

## When to use this

| Scenario                                                                                                                                                                                          | Recommendation                                                                                                                     |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| You want substrate attribution at the deployed-dependency-graph level — AEGIS can flag *your* deployed stack independently of other customers running on the same `<provider>:<model>:<sdk@ver>`. | Opt in.                                                                                                                            |
| You only want SDK-level attribution and the User-Agent dispatch handles your client.                                                                                                              | Skip. The default `<provider>:<model>` plus User-Agent-derived `<sdk@ver>` is sufficient.                                          |
| Your deployment is a single canonical image (lockfile is identical across the fleet).                                                                                                             | Opt in. The hash is stable across the fleet and contributes coherent signal.                                                       |
| Your lockfile changes on every deploy (lockfile churn).                                                                                                                                           | Opt in only if you can tolerate the L1 bucket fragmenting per deploy. The substrate axis is most useful when the bucket is stable. |
| Your lockfile is trivial enough that the hash is reversible.                                                                                                                                      | Skip if the reversal matters for your threat model.                                                                                |

The lockfile-hash dimension is the runtime-side companion to package-layer provenance — see [Supply-chain trust](/guides/supply-chain-trust) for how AEGIS substrate attribution and SLSA / Sigstore compose.

## See also

* [Substrate fingerprint](/concepts/substrate-fingerprint) — the conceptual page
* [Supply-chain trust](/guides/supply-chain-trust) — the package-layer + runtime-layer story
* [AEGIS](/concepts/aegis) — the protection-layer framing
* [Protection Network — L0 axis identity](/concepts/protection-network#l0--axis-identity) — where substrate-fingerprint sits in the L0-L5 model
* [Threat state response schema](/specifications/threat-state-response-schema) — the customer-readable read of the L1 aggregator
