# haskell-build attestor

Source: https://www.testifysec.com/docs/cilock/attestors/haskell-build/

The cilock haskell-build attestor lists the Haskell units (cabal and stack unit ids) each GHC-built product links, read from the product's own bytes, plus the GHC version that compiled it.

Reads each of the step's products and records which Haskell units are linked into it, and the GHC version that compiled it.

 

| Name | `haskell-build` |
| --- | --- |
| Predicate type | `https://aflock.ai/attestations/haskell-build/v0.1` |
| Lifecycle | `postproduct` |
| Default binary? | No |
| Category | build (primary) |
| Recommended trace | full — benefits from full eBPF trace |
| Auto-attaches when | - preargv\_prefix: cabal build<br>- preargv\_prefix: cabal install<br>- preargv\_prefix: stack build<br>- preargv\_prefix: stack install<br>- preargv\_prefix: ghc<br>- prefile\_exists: cabal.project<br>- prefile\_exists: stack.yaml |

The facts in this box are generated from the CI/lock binary's own catalog (`cilock tools list`). Do not hand-edit — run `npm run gen:catalog`.

 

## What it captures

 

The attestor runs after products are recorded. A cabal or stack build reads every library in its build plan, so a list of files the build opened says what was available, not what ended up in the program. GHC writes the unit id of each linked library into the executable in two places:

 

- constructor descriptions, `<unit id>:<Module>:<Constructor>`, which `strip` keeps;
 - z-encoded symbol names such as `aesonzm2zi3zi2zi0zm<hash>_DataziAeson_...`, which `strip` removes.

 

This version reads the constructor descriptions only. Symbol-table evidence is planned.

 

Each product is read once and parsed as ELF; the same bytes are parsed and digested, so the unit list is bound to the subject digest. Constructor descriptions are read only from allocated, non-executable data sections, and the GHC version only from the `.ident` string GHC writes into `.comment`. A file that is not ELF is never evidence, whatever strings it holds.

 

Fields (the `json` tags on `Attestor`, `BinaryInfo` and `Unit`):

 

- `binaries[]`, sorted by `path`, one per product that carries at least one unit id: 
  
  - `path`: the product, relative to the working directory.
   - `ghc_version`: from GHC's `.ident` string in `.comment`, e.g. `9.6.7`; empty when absent.
   - `units[]`, sorted by `id`: 
    
    - `id`: the full unit id, e.g. `aeson-2.3.2.0-a931bef6…` (cabal store), `aeson-2.1.2.1-7OPvzW6QotrC573yNTjjB7` (stack), or `containers-0.6.7` (a GHC boot package).
     - `name`, `version`: parsed from the id.
     - `evidence`: `con_desc`, the only kind this version reads.
 - `skipped_non_haskell[]`: products that are not ELF executables or shared objects, or ELF files with no unit id.
 - `skipped_unsupported_format[]`: Mach-O and PE products, which this version does not parse.
 - `skipped_unreadable[]`: products that could not be read.
 - `skipped_too_large[]`: products over 2 GiB, not scanned.

 

Wired-in units that GHC records without a version (`base`, `ghc-prim`, `ghc-bignum`) are not listed.

 

## When to use

 

On any step that runs `cabal build`, `cabal install`, `stack build` or `ghc`. `strip` does not change what it reads. Auto-detection attaches it on those commands, or when the working directory has a `cabal.project` or `stack.yaml`.

 

## Flags

 

None.

 

## Output shape

 

```json
{
  "binaries": [
    {
      "path": "dist/app",
      "ghc_version": "9.6.7",
      "units": [
        { "id": "aeson-2.3.2.0-a931bef6…", "name": "aeson", "version": "2.3.2.0", "evidence": ["con_desc"] },
        { "id": "containers-0.6.7", "name": "containers", "version": "0.6.7", "evidence": ["con_desc"] }
      ]
    }
  ],
  "skipped_non_haskell": ["dist/app.sha256"]
}
```

 

Subjects: `binary:<path>` for each product listed in `binaries`.

 

## Gotchas

 

- **It needs products.** With no products (for example under `cilock attest`) it records an empty predicate and no error.
 - **Products only.** A cabal store or Nix store outside the working directory is not a product, so a build that only populated a store records nothing here.
 - **Linux binaries only.** macOS (Mach-O) and Windows (PE) products are listed in `skipped_unsupported_format`, not read.
 - **A unit with no constructors is invisible.** A library that contributes only functions leaves no constructor description, so this version does not list it. In a measured aeson build, six of the store units (QuickCheck among them) appear only in symbol names.

 

---

**This page is generated from the CI/lock tool catalog.** Don't edit it here — the source is [`attestation/detection/docs/haskell-build.doc.md`](https://github.com/aflock-ai/rookery/edit/main/attestation/detection/docs/haskell-build.doc.md) in [aflock-ai/rookery](https://github.com/aflock-ai/rookery). The same catalog powers `cilock tools show haskell-build` in the CLI, so a single edit keeps the terminal and the website in sync.

[Edit this page →](https://github.com/aflock-ai/rookery/edit/main/attestation/detection/docs/haskell-build.doc.md) ·  [Add a new tool →](https://github.com/aflock-ai/rookery/new/main/attestation/detection/docs)

When you open the pull request: if you are an **AI agent**, add the `ai-agent` label. If you are a **human**, add the `human` label.

Reference generated from the product documentation. Match commands and support details to your installed release.
