diff --git a/contrib/bootstrap b/contrib/bootstrap
index ec2fdf2d1f5459cb8f980741bd6a974447d6b58a..bf1778767b88d0392d9d83dfeb3aa6602be65378 100755
--- a/contrib/bootstrap
+++ b/contrib/bootstrap
@@ -311,6 +311,7 @@ Other targets:
  * make mostlyclean   clean everything except source tarballs
  * make clean         clean everything
  * make package       prepare prebuilt packages
+ * make cyclonedx     generate a CycloneDX SBOM file
 EOF
 
 mkdir -p ../tarballs || exit $?
diff --git a/contrib/src/cyclonedx.sh b/contrib/src/cyclonedx.sh
new file mode 100755
index 0000000000000000000000000000000000000000..8ada2ac6e9149f068f17346a3562ffbe38dfa19d
--- /dev/null
+++ b/contrib/src/cyclonedx.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+#
+# Take as input a list of CPE id (string), parse them and output a minimal CycloneDX SBOM file in JSON format
+#
+# Copyright (C) 2024 Savoir-faire Linux, Inc.
+
+set -euo pipefail # Enable error checking
+
+
+function main() {
+    local list_cpe=$1
+    local output="common-jami-daemon.cdx.json"
+
+    cat <<EOF > $output
+{
+    "bomFormat": "CycloneDX",
+    "specVersion": "1.5",
+    "serialNumber": "urn:uuid:$(uuidgen)",
+    "version": 1,
+    "metadata": {
+        "timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
+    },
+    "components": [
+EOF
+
+    local already_done=()
+    local components_writed=0
+
+    for cpe in $list_cpe; do
+        # Skip duplicates
+        # shellcheck disable=SC2076 # CPE is not a regex
+        if [[ " ${already_done[*]} " =~ " ${cpe} " ]]; then
+            continue
+        fi
+
+        # Split CPE v2.3 string to extract vendor, product, and version
+        IFS=':' read -r -a cpe_parts <<< "$cpe"
+        # Assuming standard CPE v2.3 format: cpe:2.3:a:vendor:product:version:...
+        vendor="${cpe_parts[3]}"
+        product="${cpe_parts[4]}"
+        version="${cpe_parts[5]}"
+
+        case "${cpe_parts[2]}" in
+            o)
+                kind="operating-system"
+                ;;
+            h)
+                kind="device"
+                ;;
+            *)
+                kind="library"
+                ;;
+        esac
+
+        if (( components_writed >= 1 )); then
+            echo "        }," >> $output
+        fi
+
+        cat <<EOF >> $output
+        {
+            "type": "$kind",
+            "bom-ref": "$cpe",
+            "publisher": "$vendor",
+            "name": "$product",
+            "version": "$version",
+            "cpe": "$cpe"
+EOF
+
+        already_done+=("$cpe")
+        components_writed=$((components_writed + 1))
+    done
+
+    if (( components_writed >= 1 )); then
+        echo "        }" >> $output
+    fi
+
+    cat <<EOF >> $output
+    ]
+}
+EOF
+
+    echo "CycloneDX SBOM file generated: $output (contains $components_writed components)"
+}
+
+main "$@"
diff --git a/contrib/src/main.mak b/contrib/src/main.mak
index 8c0a2fbb4a7261d68cf0528b03438e2540e1849b..b46e85f2dab8191453b20590798a557b3095d714 100644
--- a/contrib/src/main.mak
+++ b/contrib/src/main.mak
@@ -506,6 +506,9 @@ package: install
 
 pprint = @echo '  $(or $(sort $1), None)' | fmt
 
+cyclonedx:
+	@$(SRC)/cyclonedx.sh "$(PKG_CPE)"
+
 list:
 	@echo All packages:
 	$(call pprint,$(PKGS_ALL))