Piotr Paradziński

Haskell supply-chain security with cabal-plan-submit

2026-05-31

Making cabal-audit reports actionable with dependency paths, SARIF enrichment, GitHub Dependency Submission, and deprecated package detection using cabal-plan-submit.

Problem

cabal-audit reports vulnerabilities in dependencies based on security advisories like Haskell advisories, but open-source software maintainers often need to know:

Demo target

persistent a realistic datastore interface for Haskell.

Example cabal-audit with cabal-plan-submit

With locally cloned and built cabal-audit and persistent one can run:

~/persistent$ ~/cabal-audit/result/bin/cabal-audit

and get report including:

Hackage package cryptonite at version 0.30 is vulnerable for:
  HSEC-2025-0002 "Double Public Key Signing Function Oracle Attack on Ed25519"
  published: 2025-11-14 14:45:34 UTC
  https://haskell.github.io/security-advisories/advisory/HSEC-2025-0002
  No fix version available
  crypto

There are 2 issues here:

We can check in Haskell advisory for HSEC-2025-0002 that problem exists also in crypton in versions >=0.31 && <1.0.3.

To investigate further using cabal-plan-submit why this dependency is present:

~/cabal-plan-submit$ cabal run cabal-plan-submit -- why ~/persistent/dist-newstyle/cache/plan.json cryptonite

produces:

cryptonite
paths:
  persistent-mongoDB-2.13.1.0
   -> mongoDB-2.7.1.4
   -> cryptohash-0.11.9
   -> cryptonite-0.30

and combine this with analysis of deprecated dependencies:

curl -L \
  https://raw.githubusercontent.com/commercialhaskell/all-cabal-metadata/master/deprecated.yaml \
  -o deprecated.yaml

~/cabal-plan-submit$ cabal run cabal-plan-submit -- inspect-deprecated --production-only ~/persistent/dist-newstyle/cache/plan.json deprecated.yaml

that shows:

deprecated packages:
  cryptonite-0.30
    relationship: indirect
    replacements:
      - cryptohash-md5
      - cryptohash-sha1
      - cryptohash-sha256
      - cryptohash-sha512
      - crypton
    used by path: persistent-mongoDB-2.13.1.0 -> mongoDB-2.7.1.4 -> cryptohash-0.11.9 -> cryptonite-0.30
  data-binary-ieee754-0.4.4
    relationship: indirect
    replacements:
      - binary
      - cereal
    used by path: persistent-mongoDB-2.13.1.0 -> bson-0.4.0.1 -> data-binary-ieee754-0.4.4

We discovered that cryptonite is deprecated and potential replacements are crypton and vulnerability is fixed there. We even know that we should target mongoDB or cryptohash. Story continues in mongodb #161

Filtering noisy test/benchmark dependencies

Previously we used -production-only to ignore local modules that are most likely relevant for benchmarking and tests, we can get all of them by:

~/cabal-plan-submit$ cabal run cabal-plan-submit -- inspect-deprecated ~/persistent/dist-newstyle/cache/plan.json deprecated.yaml

which is a bit more noisy:

deprecated packages:
  cryptonite-0.30
    relationship: indirect
    replacements:
      - cryptohash-md5
      - cryptohash-sha1
      - cryptohash-sha256
      - cryptohash-sha512
      - crypton
    used by path: persistent-mongoDB-2.13.1.0 -> mongoDB-2.7.1.4 -> cryptohash-0.11.9 -> cryptonite-0.30
  data-binary-ieee754-0.4.4
    relationship: indirect
    replacements:
      - binary
      - cereal
    used by path: persistent-mongoDB-2.13.1.0 -> bson-0.4.0.1 -> data-binary-ieee754-0.4.4
  old-time-1.1.1.0
    relationship: indirect
    replacement: time
    used by path: persistent-2.18.1.0 -> quickcheck-instances-0.4 -> old-time-1.1.1.0
  system-fileio-0.3.16.7
    relationship: direct
    replacement: directory
    used by path: persistent-sqlite-2.13.3.1 -> system-fileio-0.3.16.7
  system-filepath-0.4.14.1
    relationship: direct
    replacement: filepath
    used by path: persistent-sqlite-2.13.3.1 -> system-filepath-0.4.14.1

Originally report mentioned also old-time that was pulled through unix-time:

cabal run cabal-plan-submit -- why --production-only ~/persistent/dist-newstyle/cache/plan.json old-time
old-time
paths:
  persistent-2.18.1.0 -> fast-logger-3.2.6 -> unix-time-0.4.17 -> old-time-1.1.1.0
  persistent-mongoDB-2.13.1.0 -> mongoDB-2.7.1.4 -> tls-2.4.1 -> unix-time-0.4.17 -> old-time-1.1.1.0
  persistent-mysql-2.13.1.6 -> monad-logger-0.3.42 -> fast-logger-3.2.6 -> unix-time-0.4.17 -> old-time-1.1.1.0
  persistent-postgresql-2.14.3.0 -> monad-logger-0.3.42 -> fast-logger-3.2.6 -> unix-time-0.4.17 -> old-time-1.1.1.0
  persistent-qq-2.12.0.7 -> persistent-2.18.1.0 -> fast-logger-3.2.6 -> unix-time-0.4.17 -> old-time-1.1.1.0
  persistent-redis-2.13.0.2 -> hedis-0.15.2 -> tls-2.4.1 -> unix-time-0.4.17 -> old-time-1.1.1.0
  persistent-sqlite-2.13.3.1 -> monad-logger-0.3.42 -> fast-logger-3.2.6 -> unix-time-0.4.17 -> old-time-1.1.1.0

but after merging https://github.com/kazu-yamamoto/unix-time/pull/68 this is no longer the case:

cabal run cabal-plan-submit -- why --production-only ~/persistent/dist-newstyle/cache/plan.json old-time
no path found to old-time

Note we used here again --production-only as without it we have:

cabal run cabal-plan-submit -- why ~/persistent/dist-newstyle/cache/plan.json old-time
old-time
paths:
  persistent-2.18.1.0 -> quickcheck-instances-0.4 -> old-time-1.1.1.0
  persistent-mongoDB-2.13.1.0 -> persistent-test-2.13.2.1 -> quickcheck-instances-0.4 -> old-time-1.1.1.0
  persistent-mysql-2.13.1.6 -> quickcheck-instances-0.4 -> old-time-1.1.1.0
  persistent-postgresql-2.14.3.0 -> quickcheck-instances-0.4 -> old-time-1.1.1.0
  persistent-sqlite-2.13.3.1 -> persistent-test-2.13.2.1 -> quickcheck-instances-0.4 -> old-time-1.1.1.0
  persistent-test-2.13.2.1 -> quickcheck-instances-0.4 -> old-time-1.1.1.0

Supply-chain security on Github and Haskell

According to Zero Day Clock, 74% of newly disclosed vulnerabilities in 2026 are estimated to have no known fix at disclosure time. Public package registries also have real infrastructure costs, so “just update everything immediately” is not always the best operational answer (see 1, 2).

For many ecosystems Github can automatically figure out what are dependencies of given project, so information from security advisories can be used by Dependabot and automatic PRs for dependencies where known vulnerabilities are can be created.

This works for Rust, Python, Java unfortunately GitHub does not understand dependencies in Haskell and Scala. See discussions here: security-advisories #11 and security-advisories #205.

Can we do better? cabal-plan-submit aims to improve this in two ways

Use GitHub dependency submission

cabal-plan-submit reads resolved cabal plan and convert it into snapshot accepted by GitHub dependency submission API This can be configured on workflow like so:

  - name: Checkout cabal-plan-submit
    uses: actions/checkout@v6
    with:
      repository: dancewithheart/cabal-plan-submit
      path: cabal-plan-submit
      
  - name: Submit dependency snapshot
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      REPO: ${{ github.repository }}
    run: |
      owner="${REPO%/*}"
      repo="${REPO#*/}"

      response="$(
        curl \
          --fail-with-body \
          -X POST \
          -H "Accept: application/vnd.github+json" \
          -H "Authorization: Bearer $GITHUB_TOKEN" \
          -H "X-GitHub-Api-Version: 2022-11-28" \
          "https://api.github.com/repos/$owner/$repo/dependency-graph/snapshots" \
          --data-binary @snapshot.json
      )"

      echo "$response" | jq .

After this on you project Insights > Dependency graph should be populated with dependencies:

Enrich SARIF output from cabal-audit

Report with vulnerabilities from dependencies can be exported in SARIF format and submitted to GitHub as code scan. This is very nicely automated thanks to haskell-security-action.

cabal-plan-submit thanks to knowledge of resolved dependencies can enrich this SARIF report with: - precise locations (see haskell-security-action #1 and haskell-security-action #5) - information if dependency is direct or transitive (cabal-audit #68)

on the details for vulnerability you can see GitHub compute severity from CVSS vector (recently improved via security-advisories #322 and links CVEs thanks to exposed tags (see cabal-audit #75).

cabal-plan-submit can also generate information about deprecated dependencies, and tags to search:

Future directions

Current workflow:

could be further expanded to static analysis from stan - see stan #483

Known limitations of cabal-plan-submit

PRs are very welcome :) There is planned work to resolve tech debt created while preparing the MVP.