diff --git a/buildpack.go b/buildpack.go
index 2814644..2a99a48 100644
--- a/buildpack.go
+++ b/buildpack.go
@@ -28,6 +28,7 @@ import (
 	"github.com/heroku/color"
 
 	"github.com/paketo-buildpacks/libpak/bard"
+	"github.com/paketo-buildpacks/libpak/sbom"
 )
 
 // BuildpackConfiguration represents a build or launch configuration parameter.
@@ -80,13 +81,19 @@ type BuildpackDependency struct {
 	// Stacks are the stacks the dependency is compatible with.
 	Stacks []string `toml:"stacks"`
 
-	// Licenses are the stacks the dependency is distributed under.
+	// Licenses are the licenses the dependency is distributed under.
 	Licenses []BuildpackDependencyLicense `toml:"licenses"`
+
+	// CPEs are the Common Platform Enumeration identifiers for the dependency
+	CPEs []string `toml:"cpes"`
+
+	// PURL is the package URL that identifies the dependency
+	PURL string `toml:"purl"`
 }
 
 // AsBOMEntry renders a bill of materials entry describing the dependency.
 //
-// Deprecated: as of Buildpacks RFC 95, use `sherpa.SBOMScanner` instead
+// Deprecated: as of Buildpacks RFC 95, use `BuildpackDependency.AsSyftArtifact` instead
 func (b BuildpackDependency) AsBOMEntry() libcnb.BOMEntry {
 	return libcnb.BOMEntry{
 		Name: b.ID,
@@ -101,6 +108,33 @@ func (b BuildpackDependency) AsBOMEntry() libcnb.BOMEntry {
 	}
 }
 
+// AsSyftArtifact renders a bill of materials entry describing the dependency as Syft.
+func (b BuildpackDependency) AsSyftArtifact() (sbom.SyftArtifact, error) {
+	licenses := []string{}
+	for _, license := range b.Licenses {
+		licenses = append(licenses, license.Type)
+	}
+
+	sbomArtifact := sbom.SyftArtifact{
+		Name:      b.Name,
+		Version:   b.Version,
+		Type:      "UnknownPackage",
+		FoundBy:   "libpak",
+		Licenses:  licenses,
+		Locations: []sbom.SyftLocation{{Path: "buildpack.toml"}},
+		CPEs:      b.CPEs,
+		PURL:      b.PURL,
+	}
+
+	var err error
+	sbomArtifact.ID, err = sbomArtifact.Hash()
+	if err != nil {
+		return sbom.SyftArtifact{}, fmt.Errorf("unable to generate hash\n%w", err)
+	}
+
+	return sbomArtifact, nil
+}
+
 // BuildpackMetadata is an extension to libcnb.Buildpack's metadata with opinions.
 type BuildpackMetadata struct {
 
@@ -196,6 +230,16 @@ func NewBuildpackMetadata(metadata map[string]interface{}) (BuildpackMetadata, e
 				}
 			}
 
+			if v, ok := v["cpes"].([]interface{}); ok {
+				for _, v := range v {
+					d.CPEs = append(d.CPEs, v.(string))
+				}
+			}
+
+			if v, ok := v["purl"].(string); ok {
+				d.PURL = v
+			}
+
 			m.Dependencies = append(m.Dependencies, d)
 		}
 	}
diff --git a/buildpack_test.go b/buildpack_test.go
index 3160f85..739a38e 100644
--- a/buildpack_test.go
+++ b/buildpack_test.go
@@ -26,6 +26,7 @@ import (
 	"github.com/sclevine/spec"
 
 	"github.com/paketo-buildpacks/libpak"
+	"github.com/paketo-buildpacks/libpak/sbom"
 )
 
 func testBuildpack(t *testing.T, context spec.G, it spec.S) {
@@ -62,6 +63,37 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) {
 		}))
 	})
 
+	it("renders dependency as a SyftArtifact", func() {
+		dependency := libpak.BuildpackDependency{
+			ID:      "test-id",
+			Name:    "test-name",
+			Version: "1.1.1",
+			URI:     "test-uri",
+			SHA256:  "test-sha256",
+			Stacks:  []string{"test-stack"},
+			Licenses: []libpak.BuildpackDependencyLicense{
+				{
+					Type: "test-type",
+					URI:  "test-uri",
+				},
+			},
+			CPEs: []string{"test-cpe1", "test-cpe2"},
+			PURL: "test-purl",
+		}
+
+		Expect(dependency.AsSyftArtifact()).To(Equal(sbom.SyftArtifact{
+			ID:        "46713835f08d90b7",
+			Name:      "test-name",
+			Version:   "1.1.1",
+			Type:      "UnknownPackage",
+			FoundBy:   "libpak",
+			Licenses:  []string{"test-type"},
+			Locations: []sbom.SyftLocation{{Path: "buildpack.toml"}},
+			CPEs:      []string{"test-cpe1", "test-cpe2"},
+			PURL:      "test-purl",
+		}))
+	})
+
 	context("NewBuildpackMetadata", func() {
 		it("deserializes metadata", func() {
 			actual := map[string]interface{}{
@@ -86,6 +118,8 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) {
 								"uri":  "test-uri",
 							},
 						},
+						"cpes": []interface{}{"cpe:2.3:a:test-id:1.1.1"},
+						"purl": "pkg:generic:test-id@1.1.1",
 					},
 				},
 				"include-files": []interface{}{"test-include-file"},
@@ -114,6 +148,8 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) {
 								URI:  "test-uri",
 							},
 						},
+						CPEs: []string{"cpe:2.3:a:test-id:1.1.1"},
+						PURL: "pkg:generic:test-id@1.1.1",
 					},
 				},
 				IncludeFiles: []string{"test-include-file"},
diff --git a/dependency_cache_test.go b/dependency_cache_test.go
index 1448a9c..7a7613b 100644
--- a/dependency_cache_test.go
+++ b/dependency_cache_test.go
@@ -153,6 +153,8 @@ func testDependencyCache(t *testing.T, context spec.G, it spec.S) {
 						URI:  "test-uri",
 					},
 				},
+				CPEs: []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
+				PURL: "pkg:generic/some-java11@11.0.2?arch=amd64",
 			}
 
 			dependencyCache = libpak.DependencyCache{
diff --git a/go.mod b/go.mod
index 0856213..bd971fc 100644
--- a/go.mod
+++ b/go.mod
@@ -5,10 +5,11 @@ go 1.15
 require (
 	github.com/CycloneDX/cyclonedx-go v0.4.0
 	github.com/Masterminds/semver/v3 v3.1.1
-	github.com/buildpacks/libcnb v1.24.1-0.20211118031525-6aa81e50810d
+	github.com/buildpacks/libcnb v1.25.0
 	github.com/creack/pty v1.1.17
 	github.com/heroku/color v0.0.6
 	github.com/imdario/mergo v0.3.12
+	github.com/mitchellh/hashstructure/v2 v2.0.2
 	github.com/onsi/gomega v1.17.0
 	github.com/pelletier/go-toml v1.9.4
 	github.com/sclevine/spec v1.4.0
diff --git a/go.sum b/go.sum
index 8a33242..6683915 100644
--- a/go.sum
+++ b/go.sum
@@ -6,8 +6,8 @@ github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030I
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs=
 github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
-github.com/buildpacks/libcnb v1.24.1-0.20211118031525-6aa81e50810d h1:rAJsgF0p6rtUPGSTOCnCt/ofXKQM34wN+XKMXH3bNBE=
-github.com/buildpacks/libcnb v1.24.1-0.20211118031525-6aa81e50810d/go.mod h1:XX0+zHW8CNLNwiiwowgydAgWWfyDt8Lj1NcuWtkkBJQ=
+github.com/buildpacks/libcnb v1.25.0 h1:f0UWYUbXQ/vTX6SztGn+sP/F6cVSAbBQO4B5/R1LEP8=
+github.com/buildpacks/libcnb v1.25.0/go.mod h1:XX0+zHW8CNLNwiiwowgydAgWWfyDt8Lj1NcuWtkkBJQ=
 github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
 github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,6 +41,8 @@ github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
+github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
diff --git a/layer.go b/layer.go
index 3d8a2a4..89f732f 100644
--- a/layer.go
+++ b/layer.go
@@ -27,6 +27,7 @@ import (
 
 	"github.com/buildpacks/libcnb"
 
+	"github.com/paketo-buildpacks/libpak/sbom"
 	"github.com/paketo-buildpacks/libpak/sherpa"
 
 	"github.com/paketo-buildpacks/libpak/bard"
@@ -139,15 +140,18 @@ func NewDependencyLayer(dependency BuildpackDependency, cache DependencyCache, t
 		ExpectedTypes:    types,
 	}
 
-	entry := dependency.AsBOMEntry()
-	entry.Metadata["layer"] = c.LayerName()
+	var entry libcnb.BOMEntry
+	if dependency.PURL == "" && len(dependency.CPEs) == 0 {
+		entry = dependency.AsBOMEntry()
+		entry.Metadata["layer"] = c.LayerName()
 
-	if types.Launch {
-		entry.Launch = true
-	}
-	if !(types.Launch && !types.Cache && !types.Build) {
-		// launch-only layers are the only layers NOT guaranteed to be present in the build environment
-		entry.Build = true
+		if types.Launch {
+			entry.Launch = true
+		}
+		if !(types.Launch && !types.Cache && !types.Build) {
+			// launch-only layers are the only layers NOT guaranteed to be present in the build environment
+			entry.Build = true
+		}
 	}
 
 	return c, entry
@@ -168,6 +172,18 @@ func (d *DependencyLayerContributor) Contribute(layer libcnb.Layer, f Dependency
 		}
 		defer artifact.Close()
 
+		sbomArtifact, err := d.Dependency.AsSyftArtifact()
+		if err != nil {
+			return libcnb.Layer{}, fmt.Errorf("unable to get SBOM artifact %s\n%w", d.Dependency.ID, err)
+		}
+
+		sbomPath := layer.SBOMPath(libcnb.SyftJSON)
+		dep := sbom.NewSyftDependency(layer.Path, []sbom.SyftArtifact{sbomArtifact})
+		d.Logger.Debugf("Writing Syft SBOM at %s: %+v", sbomPath, dep)
+		if err := dep.WriteTo(sbomPath); err != nil {
+			return libcnb.Layer{}, fmt.Errorf("unable to write SBOM\n%w", err)
+		}
+
 		return f(artifact)
 	})
 }
@@ -210,14 +226,17 @@ func NewHelperLayer(buildpack libcnb.Buildpack, names ...string) (HelperLayerCon
 		BuildpackInfo: buildpack.Info,
 	}
 
-	entry := libcnb.BOMEntry{
-		Name: "helper",
-		Metadata: map[string]interface{}{
-			"layer":   c.Name(),
-			"names":   names,
-			"version": buildpack.Info.Version,
-		},
-		Launch: true,
+	var entry libcnb.BOMEntry
+	if buildpack.API == "0.6" || buildpack.API == "0.5" || buildpack.API == "0.4" || buildpack.API == "0.3" || buildpack.API == "0.2" || buildpack.API == "0.1" {
+		entry = libcnb.BOMEntry{
+			Name: "helper",
+			Metadata: map[string]interface{}{
+				"layer":   c.Name(),
+				"names":   names,
+				"version": buildpack.Info.Version,
+			},
+			Launch: true,
+		}
 	}
 
 	return c, entry
@@ -261,6 +280,51 @@ func (h HelperLayerContributor) Contribute(layer libcnb.Layer) (libcnb.Layer, er
 			}
 		}
 
+		sbomArtifact, err := h.AsSyftArtifact()
+		if err != nil {
+			return libcnb.Layer{}, fmt.Errorf("unable to get SBOM artifact for helper\n%w", err)
+		}
+
+		sbomPath := layer.SBOMPath(libcnb.SyftJSON)
+		dep := sbom.NewSyftDependency(layer.Path, []sbom.SyftArtifact{sbomArtifact})
+		h.Logger.Debugf("Writing Syft SBOM at %s: %+v", sbomPath, dep)
+		if err := dep.WriteTo(sbomPath); err != nil {
+			return libcnb.Layer{}, fmt.Errorf("unable to write SBOM\n%w", err)
+		}
+
 		return layer, nil
 	})
 }
+
+func (h HelperLayerContributor) AsSyftArtifact() (sbom.SyftArtifact, error) {
+	licenses := []string{}
+	for _, license := range h.BuildpackInfo.Licenses {
+		licenses = append(licenses, license.Type)
+	}
+
+	locations := []sbom.SyftLocation{}
+	cpes := []string{}
+	for _, name := range h.Names {
+		locations = append(locations, sbom.SyftLocation{Path: name})
+		cpes = append(cpes, fmt.Sprintf("cpe:2.3:a:%s:%s:%s:*:*:*:*:*:*:*",
+			h.BuildpackInfo.ID, name, h.BuildpackInfo.Version))
+	}
+
+	artifact := sbom.SyftArtifact{
+		Name:      "helper",
+		Version:   h.BuildpackInfo.Version,
+		Type:      "UnknownPackage",
+		FoundBy:   "libpak",
+		Licenses:  licenses,
+		Locations: locations,
+		CPEs:      cpes,
+		PURL:      fmt.Sprintf("pkg:generic/%s@%s", h.BuildpackInfo.ID, h.BuildpackInfo.Version),
+	}
+	var err error
+	artifact.ID, err = artifact.Hash()
+	if err != nil {
+		return sbom.SyftArtifact{}, fmt.Errorf("unable to generate hash\n%w", err)
+	}
+
+	return artifact, nil
+}
diff --git a/layer_test.go b/layer_test.go
index bc10d77..23c8e43 100644
--- a/layer_test.go
+++ b/layer_test.go
@@ -215,6 +215,7 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 				},
 			}
 		})
+
 		it("returns a BOM entry for the layer", func() {
 			_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{})
 			Expect(entry.Name).To(Equal("test-id"))
@@ -229,6 +230,7 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 				},
 			}))
 		})
+
 		context("launch layer type", func() {
 			it("only sets launch on the entry", func() {
 				_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{
@@ -288,6 +290,23 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 				Expect(entry.Build).To(BeTrue())
 			})
 		})
+
+		context("no BOM entry when PURL is set", func() {
+			it("sets build on the entry", func() {
+				dep.PURL = "pkg:generic/fake@1.0.0"
+				_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{})
+				Expect(entry).To(Equal(libcnb.BOMEntry{}))
+			})
+
+			it("sets build on the entry", func() {
+				dep.CPEs = []string{
+					"cpe:1",
+					"cpe:2",
+				}
+				_, entry := libpak.NewDependencyLayer(dep, libpak.DependencyCache{}, libcnb.LayerTypes{})
+				Expect(entry).To(Equal(libcnb.BOMEntry{}))
+			})
+		})
 	})
 
 	context("DependencyLayerContributor", func() {
@@ -314,6 +333,8 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 						URI:  "test-uri",
 					},
 				},
+				CPEs: []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
+				PURL: "pkg:generic/some-java11@11.0.2?arch=amd64",
 			}
 
 			layer.Metadata = map[string]interface{}{}
@@ -394,6 +415,8 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 						"uri":  dependency.Licenses[0].URI,
 					},
 				},
+				"cpes": []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
+				"purl": "pkg:generic/some-java11@11.0.2?arch=amd64",
 			}
 
 			var called bool
@@ -442,6 +465,8 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 						"uri":  dependency.Licenses[0].URI,
 					},
 				},
+				"cpes": []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
+				"purl": "pkg:generic/some-java11@11.0.2?arch=amd64",
 			}))
 		})
 
@@ -459,6 +484,8 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 						"uri":  dependency.Licenses[0].URI,
 					},
 				},
+				"cpes": []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"},
+				"purl": "pkg:generic/some-java11@11.0.2?arch=amd64",
 			}
 			dlc.ExpectedTypes.Launch = true
 			dlc.ExpectedTypes.Cache = true
@@ -480,11 +507,34 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 			Expect(layer.LayerTypes.Cache).To(BeTrue())
 			Expect(layer.LayerTypes.Build).To(BeTrue())
 		})
+
+		it("adds expected Syft SBOM file", func() {
+			server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture"))
+
+			layer, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
+				defer artifact.Close()
+				return layer, nil
+			})
+			Expect(err).NotTo(HaveOccurred())
+
+			outputFile := layer.SBOMPath(libcnb.SyftJSON)
+			Expect(outputFile).To(BeARegularFile())
+
+			data, err := ioutil.ReadFile(outputFile)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(string(data)).To(ContainSubstring(`"Artifacts":[`))
+			Expect(string(data)).To(ContainSubstring(`"FoundBy":"libpak",`))
+			Expect(string(data)).To(ContainSubstring(`"PURL":"pkg:generic/some-java11@11.0.2?arch=amd64"`))
+			Expect(string(data)).To(ContainSubstring(`"Schema":{`))
+			Expect(string(data)).To(ContainSubstring(`"Descriptor":{`))
+			Expect(string(data)).To(ContainSubstring(`"Source":{`))
+		})
 	})
 
 	context("NewHelperLayer", func() {
 		it("returns a BOM entry with version equal to buildpack version", func() {
 			_, entry := libpak.NewHelperLayer(libcnb.Buildpack{
+				API: "0.6",
 				Info: libcnb.BuildpackInfo{
 					Version: "test-version",
 				},
@@ -502,6 +552,16 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 				},
 			))
 		})
+
+		it("returns empty BOM entry on API 0.7", func() {
+			_, entry := libpak.NewHelperLayer(libcnb.Buildpack{
+				API: "0.7",
+				Info: libcnb.BuildpackInfo{
+					Version: "test-version",
+				},
+			}, "test-name-1", "test-name-2")
+			Expect(entry).To(Equal(libcnb.BOMEntry{}))
+		})
 	})
 
 	context("HelperLayerContributor", func() {
@@ -618,5 +678,36 @@ func testLayer(t *testing.T, context spec.G, it spec.S) {
 			Expect(layer.LayerTypes.Cache).To(BeFalse())
 			Expect(layer.LayerTypes.Build).To(BeFalse())
 		})
+
+		it("adds expected Syft SBOM file", func() {
+			layer.Metadata = map[string]interface{}{
+				"id":          buildpack.Info.ID,
+				"name":        buildpack.Info.Name,
+				"version":     buildpack.Info.Version,
+				"homepage":    buildpack.Info.Homepage,
+				"clear-env":   buildpack.Info.ClearEnvironment,
+				"description": "",
+				"keywords":    []interface{}{},
+			}
+
+			_, err := hlc.Contribute(layer)
+			Expect(err).NotTo(HaveOccurred())
+
+			Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).NotTo(BeAnExistingFile())
+			Expect(filepath.Join(layer.Exec.FilePath("test-name-2"))).NotTo(BeAnExistingFile())
+
+			outputFile := layer.SBOMPath(libcnb.SyftJSON)
+			Expect(outputFile).To(BeARegularFile())
+
+			data, err := ioutil.ReadFile(outputFile)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(string(data)).To(ContainSubstring(`"Artifacts":[`))
+			Expect(string(data)).To(ContainSubstring(`"FoundBy":"libpak",`))
+			Expect(string(data)).To(ContainSubstring(`"PURL":"pkg:generic/test-id@test-version"`))
+			Expect(string(data)).To(ContainSubstring(`"CPEs":["cpe:2.3:a:test-id:test-name-1:test-version:*:*:*:*:*:*:*","cpe:2.3:a:test-id:test-name-2:test-version:*:*:*:*:*:*:*"]`))
+			Expect(string(data)).To(ContainSubstring(`"Schema":{`))
+			Expect(string(data)).To(ContainSubstring(`"Descriptor":{`))
+			Expect(string(data)).To(ContainSubstring(`"Source":{`))
+		})
 	})
 }
diff --git a/sbom/init_test.go b/sbom/init_test.go
new file mode 100644
index 0000000..01de6c6
--- /dev/null
+++ b/sbom/init_test.go
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sbom_test
+
+import (
+	"testing"
+
+	"github.com/sclevine/spec"
+	"github.com/sclevine/spec/report"
+)
+
+func TestUnit(t *testing.T) {
+	suite := spec.New("libpak/sbom", spec.Report(report.Terminal{}))
+	suite("SBOM", testSBOM)
+	suite.Run(t)
+}
diff --git a/sherpa/sbom.go b/sbom/sbom.go
similarity index 75%
rename from sherpa/sbom.go
rename to sbom/sbom.go
index 5b29834..68d1daf 100644
--- a/sherpa/sbom.go
+++ b/sbom/sbom.go
@@ -1,12 +1,15 @@
-package sherpa
+package sbom
 
 import (
+	"encoding/json"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"os"
 
 	"github.com/CycloneDX/cyclonedx-go"
 	"github.com/buildpacks/libcnb"
+	"github.com/mitchellh/hashstructure/v2"
 	"github.com/paketo-buildpacks/libpak/bard"
 	"github.com/paketo-buildpacks/libpak/effect"
 )
@@ -19,6 +22,89 @@ type SBOMScanner interface {
 	ScanLaunch(scanDir string, formats ...libcnb.SBOMFormat) error
 }
 
+type SyftDependency struct {
+	Artifacts  []SyftArtifact
+	Source     SyftSource
+	Descriptor SyftDescriptor
+	Schema     SyftSchema
+}
+
+func NewSyftDependency(dependencyPath string, artifacts []SyftArtifact) SyftDependency {
+	return SyftDependency{
+		Artifacts: artifacts,
+		Source: SyftSource{
+			Type:   "directory",
+			Target: dependencyPath,
+		},
+		Descriptor: SyftDescriptor{
+			Name:    "syft",
+			Version: "0.30.1",
+		},
+		Schema: SyftSchema{
+			Version: "1.1.0",
+			URL:     "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.1.0.json",
+		},
+	}
+}
+
+func (s SyftDependency) WriteTo(path string) error {
+	output, err := json.Marshal(&s)
+	if err != nil {
+		return fmt.Errorf("unable to marshal to JSON\n%w", err)
+	}
+
+	err = ioutil.WriteFile(path, output, 0644)
+	if err != nil {
+		return fmt.Errorf("unable to write to path %s\n%w", path, err)
+	}
+
+	return nil
+}
+
+type SyftArtifact struct {
+	ID        string
+	Name      string
+	Version   string
+	Type      string
+	FoundBy   string
+	Locations []SyftLocation
+	Licenses  []string
+	Language  string
+	CPEs      []string
+	PURL      string
+}
+
+func (s SyftArtifact) Hash() (string, error) {
+	f, err := hashstructure.Hash(s, hashstructure.FormatV2, &hashstructure.HashOptions{
+		ZeroNil:      true,
+		SlicesAsSets: true,
+	})
+	if err != nil {
+		return "", fmt.Errorf("could not build ID for artifact=%+v: %+v", s, err)
+	}
+
+	return fmt.Sprintf("%x", f), nil
+}
+
+type SyftLocation struct {
+	Path string
+}
+
+type SyftSource struct {
+	Type   string
+	Target string
+}
+
+type SyftDescriptor struct {
+	Name    string
+	Version string
+}
+
+type SyftSchema struct {
+	Version string
+	URL     string
+}
+
 type SyftCLISBOMScanner struct {
 	Executor effect.Executor
 	Layers   libcnb.Layers
@@ -155,7 +241,7 @@ func (b SyftCLISBOMScanner) runSyft(sbomOutputPath string, scanDir string, forma
 
 	err = b.Executor.Execute(effect.Execution{
 		Command: "syft",
-		Args:    []string{"packges", "-o", SBOMFormatToSyftOutputFormat(format), fmt.Sprintf("dir:%s", scanDir)},
+		Args:    []string{"packages", "-q", "-o", SBOMFormatToSyftOutputFormat(format), fmt.Sprintf("dir:%s", scanDir)},
 		Stdout:  writer,
 		Stderr:  b.Logger.TerminalErrorWriter(),
 	})
diff --git a/sherpa/sbom_test.go b/sbom/sbom_test.go
similarity index 63%
rename from sherpa/sbom_test.go
rename to sbom/sbom_test.go
index 436af9b..34aae96 100644
--- a/sherpa/sbom_test.go
+++ b/sbom/sbom_test.go
@@ -1,4 +1,4 @@
-package sherpa_test
+package sbom_test
 
 import (
 	"fmt"
@@ -13,7 +13,7 @@ import (
 	"github.com/paketo-buildpacks/libpak/bard"
 	"github.com/paketo-buildpacks/libpak/effect"
 	"github.com/paketo-buildpacks/libpak/effect/mocks"
-	"github.com/paketo-buildpacks/libpak/sherpa"
+	"github.com/paketo-buildpacks/libpak/sbom"
 	"github.com/sclevine/spec"
 	"github.com/stretchr/testify/mock"
 )
@@ -25,7 +25,7 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
 		layers   libcnb.Layers
 		layer    libcnb.Layer
 		executor mocks.Executor
-		scanner  sherpa.SBOMScanner
+		scanner  sbom.SBOMScanner
 	)
 
 	it.Before(func() {
@@ -49,21 +49,28 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
 	})
 
 	context("syft", func() {
+		it("generates artifact id", func() {
+			artifact := sbom.SyftArtifact{Name: "foo", Version: "1.2.3"}
+			ID, err := artifact.Hash()
+			Expect(err).ToNot(HaveOccurred())
+			Expect(ID).To(Equal("7f6c18a85645bd7c"))
+		})
+
 		it("runs syft once to generate JSON", func() {
 			format := libcnb.SyftJSON
 			outputPath := layers.BuildSBOMPath(format)
 
 			executor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool {
 				return e.Command == "syft" &&
-					len(e.Args) == 4 &&
-					e.Args[2] == "json" &&
-					e.Args[3] == "dir:something"
+					len(e.Args) == 5 &&
+					e.Args[3] == "json" &&
+					e.Args[4] == "dir:something"
 			})).Run(func(args mock.Arguments) {
 				Expect(ioutil.WriteFile(outputPath, []byte("succeed1"), 0644)).To(Succeed())
 			}).Return(nil)
 
 			// uses interface here intentionally, to force that inteface and implementation match
-			scanner = sherpa.NewSyftCLISBOMScanner(layers, &executor, bard.NewLogger(io.Discard))
+			scanner = sbom.NewSyftCLISBOMScanner(layers, &executor, bard.NewLogger(io.Discard))
 
 			Expect(scanner.ScanBuild("something", format)).To(Succeed())
 
@@ -78,14 +85,14 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
 
 			executor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool {
 				return e.Command == "syft" &&
-					len(e.Args) == 4 &&
-					e.Args[2] == "json" &&
-					e.Args[3] == "dir:something"
+					len(e.Args) == 5 &&
+					e.Args[3] == "json" &&
+					e.Args[4] == "dir:something"
 			})).Run(func(args mock.Arguments) {
 				Expect(ioutil.WriteFile(outputPath, []byte("succeed2"), 0644)).To(Succeed())
 			}).Return(nil)
 
-			scanner := sherpa.SyftCLISBOMScanner{
+			scanner := sbom.SyftCLISBOMScanner{
 				Executor: &executor,
 				Layers:   layers,
 				Logger:   bard.NewLogger(io.Discard),
@@ -107,14 +114,14 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
 			for format, outputPath := range outputPaths {
 				executor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool {
 					return e.Command == "syft" &&
-						len(e.Args) == 4 &&
-						e.Args[2] == sherpa.SBOMFormatToSyftOutputFormat(format) &&
-						e.Args[3] == "dir:something"
+						len(e.Args) == 5 &&
+						e.Args[3] == sbom.SBOMFormatToSyftOutputFormat(format) &&
+						e.Args[4] == "dir:something"
 				})).Run(func(args mock.Arguments) {
 					Expect(ioutil.WriteFile(outputPath, []byte("succeed3"), 0644)).To(Succeed())
 				}).Return(nil)
 
-				scanner := sherpa.SyftCLISBOMScanner{
+				scanner := sbom.SyftCLISBOMScanner{
 					Executor: &executor,
 					Layers:   layers,
 					Logger:   bard.NewLogger(io.Discard),
@@ -155,7 +162,7 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
   </components>
 </bom>`), 0644))
 
-			scanner := sherpa.SyftCLISBOMScanner{
+			scanner := sbom.SyftCLISBOMScanner{
 				Executor: &executor,
 				Layers:   layers,
 				Logger:   bard.NewLogger(io.Discard),
@@ -198,7 +205,7 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
   </components>
 </bom>`), 0644))
 
-			scanner := sherpa.SyftCLISBOMScanner{
+			scanner := sbom.SyftCLISBOMScanner{
 				Executor: &executor,
 				Layers:   layers,
 				Logger:   bard.NewLogger(io.Discard),
@@ -219,6 +226,87 @@ func testSBOM(t *testing.T, context spec.G, it spec.S) {
 			Expect(err).ToNot(HaveOccurred())
 			Expect(string(input)).To(ContainSubstring(`<bom xmlns="http://cyclonedx.org/schema/bom/1.2" version="1" serialNumber="urn:uuid:48051e17-8720-4503-a2ef-47efab3fc03f">`))
 		})
+
+		it("writes out a manual BOM entry", func() {
+			dep := sbom.SyftDependency{
+				Artifacts: []sbom.SyftArtifact{
+					{
+						ID:      "1234",
+						Name:    "test-dep",
+						Version: "1.2.3",
+						Type:    "UnknownPackage",
+						FoundBy: "java-buildpack",
+						Locations: []sbom.SyftLocation{
+							{Path: "/some/path"},
+						},
+						Licenses: []string{"GPL-2.0 WITH Classpath-exception-2.0"},
+						Language: "java",
+						CPEs: []string{
+							"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*",
+						},
+						PURL: "pkg:generic/some-java11@11.0.2?arch=amd64",
+					},
+				},
+				Source: sbom.SyftSource{
+					Type:   "directory",
+					Target: "path/to/layer",
+				},
+				Descriptor: sbom.SyftDescriptor{
+					Name:    "syft",
+					Version: "0.30.1",
+				},
+				Schema: sbom.SyftSchema{
+					Version: "1.1.0",
+					URL:     "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.1.0.json",
+				},
+			}
+			outputFile := filepath.Join(layers.Path, "test-bom.json")
+			Expect(dep.WriteTo(outputFile)).To(Succeed())
+			Expect(outputFile).To(BeARegularFile())
+
+			data, err := ioutil.ReadFile(outputFile)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(string(data)).To(ContainSubstring(`"Artifacts":[`))
+			Expect(string(data)).To(ContainSubstring(`"FoundBy":"java-buildpack",`))
+			Expect(string(data)).To(ContainSubstring(`"PURL":"pkg:generic/some-java11@11.0.2?arch=amd64"`))
+			Expect(string(data)).To(ContainSubstring(`"Schema":{`))
+			Expect(string(data)).To(ContainSubstring(`"Descriptor":{`))
+			Expect(string(data)).To(ContainSubstring(`"Source":{`))
+		})
+
+		it("writes out a manual BOM entry with help", func() {
+			dep := sbom.NewSyftDependency("path/to/layer", []sbom.SyftArtifact{
+				{
+					ID:      "1234",
+					Name:    "test-dep",
+					Version: "1.2.3",
+					Type:    "UnknownPackage",
+					FoundBy: "java-buildpack",
+					Locations: []sbom.SyftLocation{
+						{Path: "/some/path"},
+					},
+					Licenses: []string{"GPL-2.0 WITH Classpath-exception-2.0"},
+					Language: "java",
+					CPEs: []string{
+						"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*",
+					},
+					PURL: "pkg:generic/some-java11@11.0.2?arch=amd64",
+				},
+			})
+
+			outputFile := filepath.Join(layers.Path, "test-bom.json")
+			Expect(dep.WriteTo(outputFile)).To(Succeed())
+			Expect(outputFile).To(BeARegularFile())
+
+			data, err := ioutil.ReadFile(outputFile)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(string(data)).To(ContainSubstring(`"Artifacts":[`))
+			Expect(string(data)).To(ContainSubstring(`"FoundBy":"java-buildpack",`))
+			Expect(string(data)).To(ContainSubstring(`"PURL":"pkg:generic/some-java11@11.0.2?arch=amd64"`))
+			Expect(string(data)).To(ContainSubstring(`"Schema":{`))
+			Expect(string(data)).To(ContainSubstring(`"Descriptor":{`))
+			Expect(string(data)).To(ContainSubstring(`"Source":{`))
+		})
 	})
 
 }
diff --git a/sherpa/init_test.go b/sherpa/init_test.go
index a853d48..f524e5d 100644
--- a/sherpa/init_test.go
+++ b/sherpa/init_test.go
@@ -29,7 +29,6 @@ func TestUnit(t *testing.T) {
 	suite("EnvVar", testEnvVar)
 	suite("FileListing", testFileListing)
 	suite("NodeJS", testNodeJS)
-	suite("SBOM", testSBOM)
 	suite("Sherpa", testSherpa)
 	suite.Run(t)
 }