diff --git a/go.mod b/go.mod index e5084a4ff..22eb8c365 100644 --- a/go.mod +++ b/go.mod @@ -281,7 +281,7 @@ require ( google.golang.org/grpc v1.67.3 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v3 v3.0.1 modernc.org/libc v1.66.3 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/internal/constants.go b/internal/constants.go index 3fe72b948..d41a7bf59 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -3,5 +3,5 @@ package internal const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "16.0.39" + JSONSchemaVersion = "16.0.40" ) diff --git a/internal/task/package_tasks.go b/internal/task/package_tasks.go index 6ba932724..bd7e4bce5 100644 --- a/internal/task/package_tasks.go +++ b/internal/task/package_tasks.go @@ -32,6 +32,7 @@ import ( "github.com/anchore/syft/syft/pkg/cataloger/ruby" "github.com/anchore/syft/syft/pkg/cataloger/rust" sbomCataloger "github.com/anchore/syft/syft/pkg/cataloger/sbom" + "github.com/anchore/syft/syft/pkg/cataloger/snap" "github.com/anchore/syft/syft/pkg/cataloger/swift" "github.com/anchore/syft/syft/pkg/cataloger/swipl" "github.com/anchore/syft/syft/pkg/cataloger/terraform" @@ -173,6 +174,7 @@ func DefaultPackageTaskFactories() Factories { newSimplePackageTaskFactory(terraform.NewLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, "terraform"), newSimplePackageTaskFactory(homebrew.NewCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "homebrew"), newSimplePackageTaskFactory(conda.NewCondaMetaCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.PackageTag, "conda"), + newSimplePackageTaskFactory(snap.NewCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "snap"), // deprecated catalogers //////////////////////////////////////// // these are catalogers that should not be selectable other than specific inclusion via name or "deprecated" tag (to remain backwards compatible) diff --git a/schema/json/schema-16.0.40.json b/schema/json/schema-16.0.40.json new file mode 100644 index 000000000..3135b0f17 --- /dev/null +++ b/schema/json/schema-16.0.40.json @@ -0,0 +1,3375 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/16.0.40/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmDbEntry": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "ApkDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "BinarySignature": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "BitnamiSbomEntry": { + "properties": { + "name": { + "type": "string" + }, + "arch": { + "type": "string" + }, + "distro": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "arch", + "distro", + "revision", + "version", + "path", + "files" + ] + }, + "CConanFileEntry": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanInfoEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "build_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "py_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "$ref": "#/$defs/KeyValues" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockV2Entry": { + "properties": { + "ref": { + "type": "string" + }, + "packageID": { + "type": "string" + }, + "username": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "recipeRevision": { + "type": "string" + }, + "packageRevision": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CPE": { + "properties": { + "cpe": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "cpe" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoaPodfileLockEntry": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "CondaLink": { + "properties": { + "source": { + "type": "string" + }, + "type": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "source", + "type" + ] + }, + "CondaMetadataEntry": { + "properties": { + "arch": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "build": { + "type": "string" + }, + "build_number": { + "type": "integer" + }, + "channel": { + "type": "string" + }, + "subdir": { + "type": "string" + }, + "noarch": { + "type": "string" + }, + "license": { + "type": "string" + }, + "license_family": { + "type": "string" + }, + "md5": { + "type": "string" + }, + "sha256": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "timestamp": { + "type": "integer" + }, + "fn": { + "type": "string" + }, + "url": { + "type": "string" + }, + "extracted_package_dir": { + "type": "string" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "paths_data": { + "$ref": "#/$defs/CondaPathsData" + }, + "link": { + "$ref": "#/$defs/CondaLink" + } + }, + "type": "object", + "required": [ + "name", + "version", + "build", + "build_number" + ] + }, + "CondaPathData": { + "properties": { + "_path": { + "type": "string" + }, + "path_type": { + "type": "string" + }, + "sha256": { + "type": "string" + }, + "sha256_in_prefix": { + "type": "string" + }, + "size_in_bytes": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "_path", + "path_type", + "sha256", + "sha256_in_prefix", + "size_in_bytes" + ] + }, + "CondaPathsData": { + "properties": { + "paths_version": { + "type": "integer" + }, + "paths": { + "items": { + "$ref": "#/$defs/CondaPathData" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "paths_version", + "paths" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubspec": { + "properties": { + "homepage": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "documentation": { + "type": "string" + }, + "publish_to": { + "type": "string" + }, + "environment": { + "$ref": "#/$defs/DartPubspecEnvironment" + }, + "platforms": { + "items": { + "type": "string" + }, + "type": "array" + }, + "ignored_advisories": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "DartPubspecEnvironment": { + "properties": { + "sdk": { + "type": "string" + }, + "flutter": { + "type": "string" + } + }, + "type": "object" + }, + "DartPubspecLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + }, + "executables": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DotnetPackagesLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "contentHash": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "contentHash", + "type" + ] + }, + "DotnetPortableExecutableEntry": { + "properties": { + "assemblyVersion": { + "type": "string" + }, + "legalCopyright": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "internalName": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "productVersion": { + "type": "string" + } + }, + "type": "object", + "required": [ + "assemblyVersion", + "legalCopyright", + "companyName", + "productName", + "productVersion" + ] + }, + "DpkgArchiveEntry": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "DpkgDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "ELFSecurityFeatures": { + "properties": { + "symbolTableStripped": { + "type": "boolean" + }, + "stackCanary": { + "type": "boolean" + }, + "nx": { + "type": "boolean" + }, + "relRO": { + "type": "string" + }, + "pie": { + "type": "boolean" + }, + "dso": { + "type": "boolean" + }, + "safeStack": { + "type": "boolean" + }, + "cfi": { + "type": "boolean" + }, + "fortify": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "symbolTableStripped", + "nx", + "relRO", + "pie", + "dso" + ] + }, + "ElfBinaryPackageNoteJsonPayload": { + "properties": { + "type": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "osCPE": { + "type": "string" + }, + "os": { + "type": "string" + }, + "osVersion": { + "type": "string" + }, + "system": { + "type": "string" + }, + "vendor": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "commit": { + "type": "string" + } + }, + "type": "object" + }, + "ElixirMixLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "ErlangRebarLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "Executable": { + "properties": { + "format": { + "type": "string" + }, + "hasExports": { + "type": "boolean" + }, + "hasEntrypoint": { + "type": "boolean" + }, + "importedLibraries": { + "items": { + "type": "string" + }, + "type": "array" + }, + "elfSecurityFeatures": { + "$ref": "#/$defs/ELFSecurityFeatures" + } + }, + "type": "object", + "required": [ + "format", + "hasExports", + "hasEntrypoint", + "importedLibraries" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/FileLicense" + }, + "type": "array" + }, + "executable": { + "$ref": "#/$defs/Executable" + }, + "unknowns": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileLicense": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "evidence": { + "$ref": "#/$defs/FileLicenseEvidence" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type" + ] + }, + "FileLicenseEvidence": { + "properties": { + "confidence": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "extent": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "confidence", + "offset", + "extent" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ] + }, + "GithubActionsUseStatement": { + "properties": { + "value": { + "type": "string" + }, + "comment": { + "type": "string" + } + }, + "type": "object", + "required": [ + "value" + ] + }, + "GoModuleBuildinfoEntry": { + "properties": { + "goBuildSettings": { + "$ref": "#/$defs/KeyValues" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + }, + "goCryptoSettings": { + "items": { + "type": "string" + }, + "type": "array" + }, + "goExperiments": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GoModuleEntry": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "GoSourceEntry": { + "properties": { + "h1Digest": { + "type": "string" + }, + "os": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "buildTags": { + "type": "string" + }, + "cgoEnabled": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "cgoEnabled" + ] + }, + "HaskellHackageStackEntry": { + "properties": { + "pkgHash": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackLockEntry": { + "properties": { + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object" + }, + "HomebrewFormula": { + "properties": { + "tap": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaArchive": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/JavaPomProperties" + }, + "pomProject": { + "$ref": "#/$defs/JavaPomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "JavaJvmInstallation": { + "properties": { + "release": { + "$ref": "#/$defs/JavaVMRelease" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "release", + "files" + ] + }, + "JavaManifest": { + "properties": { + "main": { + "$ref": "#/$defs/KeyValues" + }, + "sections": { + "items": { + "$ref": "#/$defs/KeyValues" + }, + "type": "array" + } + }, + "type": "object" + }, + "JavaPomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "JavaPomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/JavaPomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "JavaPomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "JavaVMRelease": { + "properties": { + "implementor": { + "type": "string" + }, + "implementorVersion": { + "type": "string" + }, + "javaRuntimeVersion": { + "type": "string" + }, + "javaVersion": { + "type": "string" + }, + "javaVersionDate": { + "type": "string" + }, + "libc": { + "type": "string" + }, + "modules": { + "items": { + "type": "string" + }, + "type": "array" + }, + "osArch": { + "type": "string" + }, + "osName": { + "type": "string" + }, + "osVersion": { + "type": "string" + }, + "source": { + "type": "string" + }, + "buildSource": { + "type": "string" + }, + "buildSourceRepo": { + "type": "string" + }, + "sourceRepo": { + "type": "string" + }, + "fullVersion": { + "type": "string" + }, + "semanticVersion": { + "type": "string" + }, + "buildInfo": { + "type": "string" + }, + "jvmVariant": { + "type": "string" + }, + "jvmVersion": { + "type": "string" + }, + "imageType": { + "type": "string" + }, + "buildType": { + "type": "string" + } + }, + "type": "object" + }, + "JavascriptNpmPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "homepage", + "description", + "url", + "private" + ] + }, + "JavascriptNpmPackageLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "JavascriptYarnLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "KeyValue": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "key", + "value" + ] + }, + "KeyValues": { + "items": { + "$ref": "#/$defs/KeyValue" + }, + "type": "array" + }, + "License": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "contents": { + "type": "string" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type", + "urls", + "locations" + ] + }, + "LinuxKernelArchive": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModule": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + }, + "extendedSupport": { + "type": "boolean" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "accessPath": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "accessPath" + ] + }, + "LuarocksPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "dependencies": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "version", + "license", + "homepage", + "description", + "url", + "dependencies" + ] + }, + "MicrosoftKbPatch": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "NixDerivation": { + "properties": { + "path": { + "type": "string" + }, + "system": { + "type": "string" + }, + "inputDerivations": { + "items": { + "$ref": "#/$defs/NixDerivationReference" + }, + "type": "array" + }, + "inputSources": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "NixDerivationReference": { + "properties": { + "path": { + "type": "string" + }, + "outputs": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "NixStoreEntry": { + "properties": { + "path": { + "type": "string" + }, + "output": { + "type": "string" + }, + "outputHash": { + "type": "string" + }, + "derivation": { + "$ref": "#/$defs/NixDerivation" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash" + ] + }, + "OpamPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "licenses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "url": { + "type": "string" + }, + "checksum": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "licenses", + "url", + "checksum", + "homepage", + "dependencies" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "$ref": "#/$defs/licenses" + }, + "language": { + "type": "string" + }, + "cpes": { + "$ref": "#/$defs/cpes" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmDbEntry" + }, + { + "$ref": "#/$defs/ApkDbEntry" + }, + { + "$ref": "#/$defs/BinarySignature" + }, + { + "$ref": "#/$defs/BitnamiSbomEntry" + }, + { + "$ref": "#/$defs/CConanFileEntry" + }, + { + "$ref": "#/$defs/CConanInfoEntry" + }, + { + "$ref": "#/$defs/CConanLockEntry" + }, + { + "$ref": "#/$defs/CConanLockV2Entry" + }, + { + "$ref": "#/$defs/CocoaPodfileLockEntry" + }, + { + "$ref": "#/$defs/CondaMetadataEntry" + }, + { + "$ref": "#/$defs/DartPubspec" + }, + { + "$ref": "#/$defs/DartPubspecLockEntry" + }, + { + "$ref": "#/$defs/DotnetDepsEntry" + }, + { + "$ref": "#/$defs/DotnetPackagesLockEntry" + }, + { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + }, + { + "$ref": "#/$defs/DpkgArchiveEntry" + }, + { + "$ref": "#/$defs/DpkgDbEntry" + }, + { + "$ref": "#/$defs/ElfBinaryPackageNoteJsonPayload" + }, + { + "$ref": "#/$defs/ElixirMixLockEntry" + }, + { + "$ref": "#/$defs/ErlangRebarLockEntry" + }, + { + "$ref": "#/$defs/GithubActionsUseStatement" + }, + { + "$ref": "#/$defs/GoModuleBuildinfoEntry" + }, + { + "$ref": "#/$defs/GoModuleEntry" + }, + { + "$ref": "#/$defs/GoSourceEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackLockEntry" + }, + { + "$ref": "#/$defs/HomebrewFormula" + }, + { + "$ref": "#/$defs/JavaArchive" + }, + { + "$ref": "#/$defs/JavaJvmInstallation" + }, + { + "$ref": "#/$defs/JavascriptNpmPackage" + }, + { + "$ref": "#/$defs/JavascriptNpmPackageLockEntry" + }, + { + "$ref": "#/$defs/JavascriptYarnLockEntry" + }, + { + "$ref": "#/$defs/LinuxKernelArchive" + }, + { + "$ref": "#/$defs/LinuxKernelModule" + }, + { + "$ref": "#/$defs/LuarocksPackage" + }, + { + "$ref": "#/$defs/MicrosoftKbPatch" + }, + { + "$ref": "#/$defs/NixStoreEntry" + }, + { + "$ref": "#/$defs/OpamPackage" + }, + { + "$ref": "#/$defs/PeBinary" + }, + { + "$ref": "#/$defs/PhpComposerInstalledEntry" + }, + { + "$ref": "#/$defs/PhpComposerLockEntry" + }, + { + "$ref": "#/$defs/PhpPearEntry" + }, + { + "$ref": "#/$defs/PhpPeclEntry" + }, + { + "$ref": "#/$defs/PortageDbEntry" + }, + { + "$ref": "#/$defs/PythonPackage" + }, + { + "$ref": "#/$defs/PythonPipRequirementsEntry" + }, + { + "$ref": "#/$defs/PythonPipfileLockEntry" + }, + { + "$ref": "#/$defs/PythonPoetryLockEntry" + }, + { + "$ref": "#/$defs/PythonUvLockEntry" + }, + { + "$ref": "#/$defs/RDescription" + }, + { + "$ref": "#/$defs/RpmArchive" + }, + { + "$ref": "#/$defs/RpmDbEntry" + }, + { + "$ref": "#/$defs/RubyGemspec" + }, + { + "$ref": "#/$defs/RustCargoAuditEntry" + }, + { + "$ref": "#/$defs/RustCargoLockEntry" + }, + { + "$ref": "#/$defs/SnapEntry" + }, + { + "$ref": "#/$defs/SwiftPackageManagerLockEntry" + }, + { + "$ref": "#/$defs/SwiplpackPackage" + }, + { + "$ref": "#/$defs/TerraformLockProviderEntry" + }, + { + "$ref": "#/$defs/WordpressPluginEntry" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ] + }, + "PeBinary": { + "properties": { + "VersionResources": { + "$ref": "#/$defs/KeyValues" + } + }, + "type": "object", + "required": [ + "VersionResources" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerInstalledEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpComposerLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpPearEntry": { + "properties": { + "name": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "PhpPeclEntry": { + "properties": { + "name": { + "type": "string" + }, + "channel": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "PortageDbEntry": { + "properties": { + "installedSize": { + "type": "integer" + }, + "licenses": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + }, + "requiresPython": { + "type": "string" + }, + "requiresDist": { + "items": { + "type": "string" + }, + "type": "array" + }, + "providesExtra": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipRequirementsEntry": { + "properties": { + "name": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + }, + "versionConstraint": { + "type": "string" + }, + "url": { + "type": "string" + }, + "markers": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "versionConstraint" + ] + }, + "PythonPipfileLockEntry": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "PythonPoetryLockDependencyEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "markers": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "optional" + ] + }, + "PythonPoetryLockEntry": { + "properties": { + "index": { + "type": "string" + }, + "dependencies": { + "items": { + "$ref": "#/$defs/PythonPoetryLockDependencyEntry" + }, + "type": "array" + }, + "extras": { + "items": { + "$ref": "#/$defs/PythonPoetryLockExtraEntry" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "index", + "dependencies" + ] + }, + "PythonPoetryLockExtraEntry": { + "properties": { + "name": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "dependencies" + ] + }, + "PythonUvLockDependencyEntry": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "markers": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "optional" + ] + }, + "PythonUvLockEntry": { + "properties": { + "index": { + "type": "string" + }, + "dependencies": { + "items": { + "$ref": "#/$defs/PythonUvLockDependencyEntry" + }, + "type": "array" + }, + "extras": { + "items": { + "$ref": "#/$defs/PythonUvLockExtraEntry" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "index", + "dependencies" + ] + }, + "PythonUvLockExtraEntry": { + "properties": { + "name": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "dependencies" + ] + }, + "RDescription": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "url": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "built": { + "type": "string" + }, + "needsCompilation": { + "type": "boolean" + }, + "imports": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "suggests": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmArchive": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "signatures": { + "items": { + "$ref": "#/$defs/RpmSignature" + }, + "type": "array" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmDbEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "signatures": { + "items": { + "$ref": "#/$defs/RpmSignature" + }, + "type": "array" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "RpmSignature": { + "properties": { + "algo": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "created": { + "type": "string" + }, + "issuer": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algo", + "hash", + "created", + "issuer" + ] + }, + "RubyGemspec": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "RustCargoAuditEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source" + ] + }, + "RustCargoLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "SnapEntry": { + "properties": { + "snapType": { + "type": "string" + }, + "base": { + "type": "string" + }, + "snapName": { + "type": "string" + }, + "snapVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + } + }, + "type": "object", + "required": [ + "snapType", + "base", + "snapName", + "snapVersion", + "architecture" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "supplier": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "metadata" + ] + }, + "SwiftPackageManagerLockEntry": { + "properties": { + "revision": { + "type": "string" + } + }, + "type": "object", + "required": [ + "revision" + ] + }, + "SwiplpackPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "packager": { + "type": "string" + }, + "packagerEmail": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "packager", + "packagerEmail", + "homepage", + "dependencies" + ] + }, + "TerraformLockProviderEntry": { + "properties": { + "url": { + "type": "string" + }, + "constraints": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "url", + "constraints", + "version", + "hashes" + ] + }, + "WordpressPluginEntry": { + "properties": { + "pluginInstallDirectory": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorUri": { + "type": "string" + } + }, + "type": "object", + "required": [ + "pluginInstallDirectory" + ] + }, + "cpes": { + "items": { + "$ref": "#/$defs/CPE" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/License" + }, + "type": "array" + } + } +} diff --git a/schema/json/schema-latest.json b/schema/json/schema-latest.json index ce190f885..3135b0f17 100644 --- a/schema/json/schema-latest.json +++ b/schema/json/schema-latest.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "anchore.io/schema/syft/json/16.0.39/document", + "$id": "anchore.io/schema/syft/json/16.0.40/document", "$ref": "#/$defs/Document", "$defs": { "AlpmDbEntry": { @@ -2194,6 +2194,9 @@ { "$ref": "#/$defs/RustCargoLockEntry" }, + { + "$ref": "#/$defs/SnapEntry" + }, { "$ref": "#/$defs/SwiftPackageManagerLockEntry" }, @@ -3205,6 +3208,33 @@ "url" ] }, + "SnapEntry": { + "properties": { + "snapType": { + "type": "string" + }, + "base": { + "type": "string" + }, + "snapName": { + "type": "string" + }, + "snapVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + } + }, + "type": "object", + "required": [ + "snapType", + "base", + "snapName", + "snapVersion", + "architecture" + ] + }, "Source": { "properties": { "id": { diff --git a/syft/format/internal/spdxutil/helpers/originator_supplier_test.go b/syft/format/internal/spdxutil/helpers/originator_supplier_test.go index 7f61e52be..b2ab258a4 100644 --- a/syft/format/internal/spdxutil/helpers/originator_supplier_test.go +++ b/syft/format/internal/spdxutil/helpers/originator_supplier_test.go @@ -47,6 +47,7 @@ func Test_OriginatorSupplier(t *testing.T) { pkg.PythonUvLockEntry{}, pkg.RustBinaryAuditEntry{}, pkg.RustCargoLockEntry{}, + pkg.SnapEntry{}, pkg.SwiftPackageManagerResolvedEntry{}, pkg.SwiplPackEntry{}, pkg.OpamPackage{}, diff --git a/syft/internal/packagemetadata/generated.go b/syft/internal/packagemetadata/generated.go index c265fbd4d..f04ea2720 100644 --- a/syft/internal/packagemetadata/generated.go +++ b/syft/internal/packagemetadata/generated.go @@ -61,6 +61,7 @@ func AllTypes() []any { pkg.RubyGemspec{}, pkg.RustBinaryAuditEntry{}, pkg.RustCargoLockEntry{}, + pkg.SnapEntry{}, pkg.SwiftPackageManagerResolvedEntry{}, pkg.SwiplPackEntry{}, pkg.TerraformLockProviderEntry{}, diff --git a/syft/internal/packagemetadata/names.go b/syft/internal/packagemetadata/names.go index b5d723bbc..79eed0d4f 100644 --- a/syft/internal/packagemetadata/names.go +++ b/syft/internal/packagemetadata/names.go @@ -115,6 +115,7 @@ var jsonTypes = makeJSONTypes( jsonNames(pkg.OpamPackage{}, "opam-package"), jsonNames(pkg.RustCargoLockEntry{}, "rust-cargo-lock-entry", "RustCargoPackageMetadata"), jsonNamesWithoutLookup(pkg.RustBinaryAuditEntry{}, "rust-cargo-audit-entry", "RustCargoPackageMetadata"), // the legacy value is split into two types, where the other is preferred + jsonNames(pkg.SnapEntry{}, "snap-entry"), jsonNames(pkg.WordpressPluginEntry{}, "wordpress-plugin-entry", "WordpressMetadata"), jsonNames(pkg.HomebrewFormula{}, "homebrew-formula"), jsonNames(pkg.LuaRocksPackage{}, "luarocks-package"), diff --git a/syft/pkg/cataloger/snap/cataloger.go b/syft/pkg/cataloger/snap/cataloger.go new file mode 100644 index 000000000..5afdd3ef4 --- /dev/null +++ b/syft/pkg/cataloger/snap/cataloger.go @@ -0,0 +1,28 @@ +/* +Package snap provides a concrete Cataloger implementation for snap packages, extracting metadata +from different types of snap files (base, kernel, system/gadget, snapd) rather than just scanning +the filesystem. +*/ +package snap + +import ( + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +const catalogerName = "snap-cataloger" + +// NewCataloger returns a new Snap cataloger object that can parse snap package metadata. +func NewCataloger() pkg.Cataloger { + return generic.NewCataloger(catalogerName). + // Look for snap.yaml to identify snap type and base snap info + WithParserByGlobs(parseSnapYaml, "**/meta/snap.yaml"). + // Base snaps: dpkg.yaml files containing package manifests + WithParserByGlobs(parseBaseDpkgYaml, "**/usr/share/snappy/dpkg.yaml"). + // Kernel snaps: changelog files for kernel version info + WithParserByGlobs(parseKernelChangelog, "**/doc/linux-modules-*/changelog.Debian.gz"). + // System/Gadget snaps: manifest files with primed-stage-packages + WithParserByGlobs(parseSystemManifest, "**/snap/manifest.yaml"). + // Snapd snaps: snapcraft.yaml files + WithParserByGlobs(parseSnapdSnapcraft, "**/snap/snapcraft.yaml") +} diff --git a/syft/pkg/cataloger/snap/cataloger_test.go b/syft/pkg/cataloger/snap/cataloger_test.go new file mode 100644 index 000000000..bc4addace --- /dev/null +++ b/syft/pkg/cataloger/snap/cataloger_test.go @@ -0,0 +1,36 @@ +package snap + +import ( + "testing" + + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" +) + +func TestCataloger_Globs(t *testing.T) { + tests := []struct { + name string + fixture string + }{ + { + name: "base snap with dpkg.yaml", + fixture: "test-fixtures/glob-paths/base", + }, + { + name: "system snap with manifest.yaml", + fixture: "test-fixtures/glob-paths/system", + }, + { + name: "snap with meta/snap.yaml", + fixture: "test-fixtures/glob-paths/meta", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + pkgtest.NewCatalogTester(). + FromDirectory(t, test.fixture). + IgnoreUnfulfilledPathResponses("**/meta/snap.yaml", "**/usr/share/snappy/dpkg.yaml", "**/doc/linux-modules-*/changelog.Debian.gz", "**/snap/manifest.yaml", "**/snap/snapcraft.yaml"). + TestCataloger(t, NewCataloger()) + }) + } +} diff --git a/syft/pkg/cataloger/snap/integration_test.go b/syft/pkg/cataloger/snap/integration_test.go new file mode 100644 index 000000000..128aad402 --- /dev/null +++ b/syft/pkg/cataloger/snap/integration_test.go @@ -0,0 +1,55 @@ +package snap + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +func TestRealDpkgYamlParsing(t *testing.T) { + fixture := "test-fixtures/real-dpkg.yaml" + + // Open the file + f, err := os.Open(fixture) + require.NoError(t, err) + defer f.Close() + + reader := file.LocationReadCloser{ + Location: file.NewLocation(fixture), + ReadCloser: f, + } + + // Parse using our function + packages, relationships, err := parseBaseDpkgYaml(context.Background(), nil, &generic.Environment{}, reader) + + require.NoError(t, err) + assert.Nil(t, relationships) // relationships should be nil for this parser + + // We should have 10 packages from the fixture + assert.Equal(t, 10, len(packages)) + + // Check some specific packages + foundPackages := make(map[string]pkg.Package) + for _, p := range packages { + foundPackages[p.Name] = p + } + + // Verify key packages exist + require.Contains(t, foundPackages, "adduser") + require.Contains(t, foundPackages, "systemd") + require.Contains(t, foundPackages, "gcc-10-base") + + // Check that architecture is parsed correctly from package names + gccPkg := foundPackages["gcc-10-base"] + metadata, ok := gccPkg.Metadata.(pkg.SnapEntry) + require.True(t, ok) + assert.Equal(t, "amd64", metadata.Architecture) + assert.Equal(t, pkg.SnapTypeBase, metadata.SnapType) +} diff --git a/syft/pkg/cataloger/snap/package.go b/syft/pkg/cataloger/snap/package.go new file mode 100644 index 000000000..997906c72 --- /dev/null +++ b/syft/pkg/cataloger/snap/package.go @@ -0,0 +1,94 @@ +package snap + +import ( + "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" +) + +// newPackage creates a new Package from snap metadata +func newPackage(name, version string, metadata pkg.SnapEntry, locations ...file.Location) pkg.Package { + p := pkg.Package{ + Name: name, + Version: version, + Locations: file.NewLocationSet(locations...), + PURL: packageURL(name, version, metadata), + Type: pkg.DebPkg, // Use Debian package type for compatibility + Metadata: metadata, + } + + p.SetID() + + return p +} + +// packageURL returns the PURL for a snap package +func packageURL(name, version string, metadata pkg.SnapEntry) string { + var qualifiers packageurl.Qualifiers + + if metadata.Architecture != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "arch", + Value: metadata.Architecture, + }) + } + + if metadata.Base != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "base", + Value: metadata.Base, + }) + } + + if metadata.SnapType != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "type", + Value: metadata.SnapType, + }) + } + + return packageurl.NewPackageURL( + packageurl.TypeGeneric, + "snap", + name, + version, + qualifiers, + "", + ).ToString() +} + +// newDebianPackageFromSnap creates a Debian-style package entry from snap manifest data +func newDebianPackageFromSnap(name, version string, snapMetadata pkg.SnapEntry, locations ...file.Location) pkg.Package { + p := pkg.Package{ + Name: name, + Version: version, + Locations: file.NewLocationSet(locations...), + Type: pkg.DebPkg, + PURL: debianPackageURL(name, version, snapMetadata.Architecture), + Metadata: snapMetadata, + } + + p.SetID() + return p +} + +// debianPackageURL creates a PURL for Debian packages found in snaps +func debianPackageURL(name, version, architecture string) string { + var qualifiers packageurl.Qualifiers + + if architecture != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "arch", + Value: architecture, + }) + } + + return packageurl.NewPackageURL( + packageurl.TypeDebian, + "ubuntu", // Assume Ubuntu since most snaps are built on Ubuntu + name, + version, + qualifiers, + "", + ).ToString() +} diff --git a/syft/pkg/cataloger/snap/parse_base_dpkg.go b/syft/pkg/cataloger/snap/parse_base_dpkg.go new file mode 100644 index 000000000..b73f82555 --- /dev/null +++ b/syft/pkg/cataloger/snap/parse_base_dpkg.go @@ -0,0 +1,80 @@ +package snap + +import ( + "context" + "fmt" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +// dpkgYaml represents the structure of dpkg.yaml files found in base snaps +type dpkgYaml struct { + PackageRepositories []packageRepository `yaml:"package-repositories"` + Packages []string `yaml:"packages"` +} + +type packageRepository struct { + Type string `yaml:"type"` + PPA string `yaml:"ppa,omitempty"` + URL string `yaml:"url,omitempty"` +} + +// parseBaseDpkgYaml parses dpkg.yaml files from base snaps +func parseBaseDpkgYaml(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + var dpkg dpkgYaml + + decoder := yaml.NewDecoder(reader) + if err := decoder.Decode(&dpkg); err != nil { + return nil, nil, fmt.Errorf("failed to parse dpkg.yaml: %w", err) + } + + var packages []pkg.Package + + snapMetadata := pkg.SnapEntry{ + SnapType: pkg.SnapTypeBase, + } + + // Parse each package entry in "name=version" format + for _, pkgEntry := range dpkg.Packages { + if !strings.Contains(pkgEntry, "=") { + continue // Skip malformed entries + } + + parts := strings.SplitN(pkgEntry, "=", 2) + if len(parts) != 2 { + continue + } + + name := strings.TrimSpace(parts[0]) + version := strings.TrimSpace(parts[1]) + + // Skip empty names or versions + if name == "" || version == "" { + continue + } + + // Handle architecture suffixes (e.g., "libssl1.1:amd64") + if strings.Contains(name, ":") { + archParts := strings.SplitN(name, ":", 2) + name = archParts[0] + snapMetadata.Architecture = archParts[1] + } + + debPkg := newDebianPackageFromSnap( + name, + version, + snapMetadata, + reader.Location, + ) + + packages = append(packages, debPkg) + } + + return packages, nil, nil +} diff --git a/syft/pkg/cataloger/snap/parse_base_dpkg_test.go b/syft/pkg/cataloger/snap/parse_base_dpkg_test.go new file mode 100644 index 000000000..b9e4c7ab6 --- /dev/null +++ b/syft/pkg/cataloger/snap/parse_base_dpkg_test.go @@ -0,0 +1,50 @@ +package snap + +import ( + "testing" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" +) + +func TestParseBaseDpkgYaml(t *testing.T) { + fixture := "test-fixtures/dpkg.yaml" + locations := file.NewLocationSet(file.NewLocation(fixture)) + + expected := []pkg.Package{ + { + Name: "adduser", + Version: "3.118ubuntu2", + Type: pkg.DebPkg, + PURL: "pkg:deb/ubuntu/adduser@3.118ubuntu2", + Locations: locations, + Metadata: pkg.SnapEntry{ + SnapType: pkg.SnapTypeBase, + }, + }, + { + Name: "apparmor", + Version: "2.13.3-7ubuntu5.3", + Type: pkg.DebPkg, + PURL: "pkg:deb/ubuntu/apparmor@2.13.3-7ubuntu5.3", + Locations: locations, + Metadata: pkg.SnapEntry{ + SnapType: pkg.SnapTypeBase, + }, + }, + { + Name: "gcc-10-base", + Version: "10.5.0-1ubuntu1~20.04", + Type: pkg.DebPkg, + PURL: "pkg:deb/ubuntu/gcc-10-base@10.5.0-1ubuntu1~20.04?arch=amd64", + Locations: locations, + Metadata: pkg.SnapEntry{ + SnapType: pkg.SnapTypeBase, + Architecture: "amd64", // from package name suffix + }, + }, + } + + pkgtest.TestFileParser(t, fixture, parseBaseDpkgYaml, expected, nil) +} diff --git a/syft/pkg/cataloger/snap/parse_integration_test.go b/syft/pkg/cataloger/snap/parse_integration_test.go new file mode 100644 index 000000000..b7039ba28 --- /dev/null +++ b/syft/pkg/cataloger/snap/parse_integration_test.go @@ -0,0 +1,71 @@ +package snap + +import ( + "testing" + + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" +) + +func TestParseSnapYaml(t *testing.T) { + fixture := "test-fixtures/snap.yaml" + locations := file.NewLocationSet(file.NewLocation(fixture)) + + expected := []pkg.Package{ + { + Name: "test-snap", + Version: "1.0.0", + Type: pkg.DebPkg, + PURL: "pkg:generic/snap/test-snap@1.0.0?arch=amd64&base=core20&type=app", + Locations: locations, + Metadata: pkg.SnapEntry{ + SnapType: pkg.SnapTypeApp, + Base: "core20", + SnapName: "test-snap", + SnapVersion: "1.0.0", + Architecture: "amd64", + }, + }, + } + + pkgtest.TestFileParser(t, fixture, parseSnapYaml, expected, nil) +} + +func TestParseSystemManifest(t *testing.T) { + fixture := "test-fixtures/manifest.yaml" + locations := file.NewLocationSet(file.NewLocation(fixture)) + + expected := []pkg.Package{ + { + Name: "grub-efi-amd64-signed", + Version: "1.202+2.12-1ubuntu7", + Type: pkg.DebPkg, + PURL: "pkg:deb/ubuntu/grub-efi-amd64-signed@1.202%2B2.12-1ubuntu7?arch=amd64", // URL encoded + Locations: locations, + Metadata: pkg.SnapEntry{ + SnapType: pkg.SnapTypeApp, // Default type when gadget not detected from name + Base: "core24", + SnapName: "pc", + SnapVersion: "24-0.1", + Architecture: "amd64", // From architectures array + }, + }, + { + Name: "shim-signed", + Version: "1.56+15.7-0ubuntu1", + Type: pkg.DebPkg, + PURL: "pkg:deb/ubuntu/shim-signed@1.56%2B15.7-0ubuntu1?arch=amd64", // URL encoded + Locations: locations, + Metadata: pkg.SnapEntry{ + SnapType: pkg.SnapTypeApp, // Default type when gadget not detected from name + Base: "core24", + SnapName: "pc", + SnapVersion: "24-0.1", + Architecture: "amd64", // From architectures array + }, + }, + } + + pkgtest.TestFileParser(t, fixture, parseSystemManifest, expected, nil) +} diff --git a/syft/pkg/cataloger/snap/parse_kernel_changelog.go b/syft/pkg/cataloger/snap/parse_kernel_changelog.go new file mode 100644 index 000000000..06700981d --- /dev/null +++ b/syft/pkg/cataloger/snap/parse_kernel_changelog.go @@ -0,0 +1,160 @@ +package snap + +import ( + "compress/gzip" + "context" + "fmt" + "regexp" + "strings" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +// kernelVersionInfo holds parsed kernel version information +type kernelVersionInfo struct { + baseVersion string // e.g., "5.4.0-195" + releaseVersion string // e.g., "215" + fullVersion string // e.g., "5.4.0-195.215" + majorVersion string // e.g., "5.4" +} + +// parseKernelChangelog parses changelog files from kernel snaps to extract kernel version +func parseKernelChangelog(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + // The file should be gzipped + lines, err := readChangelogLines(reader) + if err != nil { + return nil, nil, err + } + + // pull from first line + versionInfo, err := extractKernelVersion(lines[0]) + if err != nil { + return nil, nil, err + } + + snapMetadata := pkg.SnapEntry{ + SnapType: pkg.SnapTypeKernel, + } + + packages := createMainKernelPackage(versionInfo, snapMetadata, reader.Location) + + // Check for base kernel package + basePackage := findBaseKernelPackage(lines, versionInfo, snapMetadata, reader.Location) + if basePackage != nil { + packages = append(packages, *basePackage) + } + + return packages, nil, nil +} + +// readChangelogLines reads and decompresses the changelog content +func readChangelogLines(reader file.LocationReadCloser) ([]string, error) { + gzReader, err := gzip.NewReader(reader) + if err != nil { + return nil, fmt.Errorf("failed to create gzip reader for changelog: %w", err) + } + defer gzReader.Close() + + content, err := readAll(gzReader) + if err != nil { + return nil, fmt.Errorf("failed to read changelog content: %w", err) + } + + lines := strings.Split(string(content), "\n") + if len(lines) == 0 { + return nil, fmt.Errorf("changelog file is empty") + } + + // Parse the first line to extract kernel version information + // Format: "linux (5.4.0-195.215) focal; urgency=medium" + return lines, nil +} + +// extractKernelVersion parses version information from the first changelog line +func extractKernelVersion(firstLine string) (*kernelVersionInfo, error) { + // Format: "linux (5.4.0-195.215) focal; urgency=medium" + kernelVersionRegex := regexp.MustCompile(`linux \(([0-9]+\.[0-9]+\.[0-9]+-[0-9]+)\.([0-9]+)\)`) + matches := kernelVersionRegex.FindStringSubmatch(firstLine) + + if len(matches) < 3 { + return nil, fmt.Errorf("could not parse kernel version from changelog: %s", firstLine) + } + + info := &kernelVersionInfo{ + baseVersion: matches[1], // e.g., "5.4.0-195" + releaseVersion: matches[2], // eg., "215" + } + // eg "5.4.0-195.215" + info.fullVersion = fmt.Sprintf("%s.%s", info.baseVersion, info.releaseVersion) + + // Extract major version; package naming + majorVersionRegex := regexp.MustCompile(`([0-9]+\.[0-9]+)\.[0-9]+-[0-9]+`) + majorMatches := majorVersionRegex.FindStringSubmatch(info.baseVersion) + + if len(majorMatches) >= 2 { + info.majorVersion = majorMatches[1] + } else { + info.majorVersion = info.baseVersion + } + + return info, nil +} + +// createMainKernelPackage creates the main kernel package +func createMainKernelPackage(versionInfo *kernelVersionInfo, snapMetadata pkg.SnapEntry, location file.Location) []pkg.Package { + kernelPackageName := fmt.Sprintf("linux-image-%s-generic", versionInfo.baseVersion) + kernelPkg := newDebianPackageFromSnap( + kernelPackageName, + versionInfo.fullVersion, + snapMetadata, + location, + ) + + return []pkg.Package{kernelPkg} +} + +// findBaseKernelPackage searches for and creates base kernel package if present +func findBaseKernelPackage(lines []string, versionInfo *kernelVersionInfo, snapMetadata pkg.SnapEntry, location file.Location) *pkg.Package { + baseKernelEntry := fmt.Sprintf("%s/linux:", strings.ReplaceAll(versionInfo.releaseVersion, ";", "/")) + + for _, line := range lines { + if strings.Contains(line, baseKernelEntry) { + return parseBaseKernelLine(line, versionInfo.majorVersion, snapMetadata, location) + } + } + + return nil +} + +// parseBaseKernelLine extracts base kernel version from a changelog line +func parseBaseKernelLine(line string, majorVersion string, snapMetadata pkg.SnapEntry, location file.Location) *pkg.Package { + baseKernelRegex := regexp.MustCompile(fmt.Sprintf(`(%s-[0-9]+)\.?[0-9]*`, regexp.QuoteMeta(majorVersion))) + baseMatches := baseKernelRegex.FindStringSubmatch(line) + + if len(baseMatches) < 2 { + return nil + } + + baseKernelVersion := baseMatches[1] + baseKernelFullRegex := regexp.MustCompile(fmt.Sprintf(`(%s-[0-9]+\.[0-9]+)`, regexp.QuoteMeta(majorVersion))) + baseFullMatches := baseKernelFullRegex.FindStringSubmatch(line) + + var baseFullVersion string + if len(baseFullMatches) >= 2 { + baseFullVersion = baseFullMatches[1] + } else { + baseFullVersion = baseKernelVersion + } + + baseKernelPkg := newDebianPackageFromSnap( + fmt.Sprintf("linux-image-%s-generic", baseKernelVersion), + baseFullVersion, + snapMetadata, + location, + ) + + return &baseKernelPkg +} diff --git a/syft/pkg/cataloger/snap/parse_snap_yaml.go b/syft/pkg/cataloger/snap/parse_snap_yaml.go new file mode 100644 index 000000000..e9897dafa --- /dev/null +++ b/syft/pkg/cataloger/snap/parse_snap_yaml.go @@ -0,0 +1,68 @@ +package snap + +import ( + "context" + "fmt" + "io" + + "gopkg.in/yaml.v3" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +// snapYaml represents the structure of meta/snap.yaml files +type snapYaml struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Base string `yaml:"base"` + Type string `yaml:"type"` + Architecture string `yaml:"architecture"` + Summary string `yaml:"summary"` + Description string `yaml:"description"` +} + +// parseSnapYaml parses meta/snap.yaml files to identify snap type and basic metadata +func parseSnapYaml(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + var snap snapYaml + + decoder := yaml.NewDecoder(reader) + if err := decoder.Decode(&snap); err != nil { + return nil, nil, fmt.Errorf("failed to parse snap.yaml: %w", err) + } + + if snap.Name == "" { + return nil, nil, fmt.Errorf("snap.yaml missing required 'name' field") + } + + // Determine snap type - default to "app" if not specified + snapType := snap.Type + if snapType == "" { + snapType = pkg.SnapTypeApp + } + + metadata := pkg.SnapEntry{ + SnapType: snapType, + Base: snap.Base, + SnapName: snap.Name, + SnapVersion: snap.Version, + Architecture: snap.Architecture, + } + + // Create a package representing the snap itself + snapPkg := newPackage( + snap.Name, + snap.Version, + metadata, + reader.Location, + ) + + return []pkg.Package{snapPkg}, nil, nil +} + +// readAll reads all content from a reader and returns it as bytes +func readAll(r io.Reader) ([]byte, error) { + return io.ReadAll(r) +} diff --git a/syft/pkg/cataloger/snap/parse_snapd_snapcraft.go b/syft/pkg/cataloger/snap/parse_snapd_snapcraft.go new file mode 100644 index 000000000..8a085f9ab --- /dev/null +++ b/syft/pkg/cataloger/snap/parse_snapd_snapcraft.go @@ -0,0 +1,188 @@ +package snap + +import ( + "context" + "fmt" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +// snapcraftYaml represents the structure of snapcraft.yaml files found in snapd snaps +type snapcraftYaml struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Summary string `yaml:"summary"` + Description string `yaml:"description"` + Base string `yaml:"base"` + Grade string `yaml:"grade"` + Confinement string `yaml:"confinement"` + Architectures []string `yaml:"architectures"` + Parts map[string]snapcraftPart `yaml:"parts"` +} + +// snapcraftPart represents a part in a snapcraft.yaml file +type snapcraftPart struct { + Plugin string `yaml:"plugin"` + Source string `yaml:"source"` + SourceType string `yaml:"source-type"` + SourceTag string `yaml:"source-tag"` + SourceCommit string `yaml:"source-commit"` + BuildPackages []string `yaml:"build-packages"` + StagePackages []string `yaml:"stage-packages"` + BuildSnaps []string `yaml:"build-snaps"` + StageSnaps []string `yaml:"stage-snaps"` + BuildEnvironment []map[string]string `yaml:"build-environment"` + Override map[string]string `yaml:"override-build"` +} + +// parseSnapdSnapcraft parses snapcraft.yaml files from snapd snaps +func parseSnapdSnapcraft(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + var snapcraft snapcraftYaml + + decoder := yaml.NewDecoder(reader) + if err := decoder.Decode(&snapcraft); err != nil { + return nil, nil, fmt.Errorf("failed to parse snapcraft.yaml: %w", err) + } + + snapMetadata := createMetadata(snapcraft) + packages := extractPackagesFromParts(snapcraft, snapMetadata, reader.Location) + + return packages, nil, nil +} + +// createMetadata creates metadata from snapcraft.yaml +func createMetadata(snapcraft snapcraftYaml) pkg.SnapEntry { + metadata := pkg.SnapEntry{ + SnapType: pkg.SnapTypeSnapd, + Base: snapcraft.Base, + SnapName: snapcraft.Name, + SnapVersion: snapcraft.Version, + } + + if len(snapcraft.Architectures) > 0 { + metadata.Architecture = snapcraft.Architectures[0] + } + + return metadata +} + +// extractPackagesFromParts processes all parts to extract packages +func extractPackagesFromParts(snapcraft snapcraftYaml, baseMetadata pkg.SnapEntry, location file.Location) []pkg.Package { + var packages []pkg.Package + + for _, part := range snapcraft.Parts { + buildPackages := processBuildPackages(part.BuildPackages, baseMetadata, location) + packages = append(packages, buildPackages...) + + stagePackages := processStagePackages(part.StagePackages, baseMetadata, location) + packages = append(packages, stagePackages...) + + snapPackages := processSnapPackages(part.BuildSnaps, part.StageSnaps, baseMetadata, location) + packages = append(packages, snapPackages...) + } + + return packages +} + +// processBuildPackages creates packages from build-packages list +func processBuildPackages(buildPackages []string, metadata pkg.SnapEntry, location file.Location) []pkg.Package { + var packages []pkg.Package + + for _, pkgName := range buildPackages { + if pkgName == "" { + continue + } + + buildPkg := newDebianPackageFromSnap( + pkgName, + "unknown", + metadata, + location, + ) + packages = append(packages, buildPkg) + } + + return packages +} + +// processStagePackages creates packages from stage-packages list with version parsing +func processStagePackages(stagePackages []string, metadata pkg.SnapEntry, location file.Location) []pkg.Package { + var packages []pkg.Package + + for _, pkgEntry := range stagePackages { + if pkgEntry == "" { + continue + } + + name, version := parsePackageWithVersion(pkgEntry) + stagePkg := newDebianPackageFromSnap( + name, + version, + metadata, + location, + ) + packages = append(packages, stagePkg) + } + + return packages +} + +// parsePackageWithVersion extracts package name and version from version-constrained entries +func parsePackageWithVersion(pkgEntry string) (string, string) { + name := pkgEntry + version := "unknown" + + if !strings.ContainsAny(pkgEntry, "=<>") { + return name, version + } + + // Try to split on version operators + operators := []string{">=", "<=", "==", "!=", "=", ">", "<"} + for _, op := range operators { + if strings.Contains(pkgEntry, op) { + parts := strings.SplitN(pkgEntry, op, 2) + if len(parts) == 2 { + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) + } + } + } + + return name, version +} + +// processSnapPackages creates packages from snap dependencies +func processSnapPackages(buildSnaps, stageSnaps []string, baseMetadata pkg.SnapEntry, location file.Location) []pkg.Package { + var packages []pkg.Package + allSnaps := make([]string, 0, len(buildSnaps)+len(stageSnaps)) + allSnaps = append(allSnaps, buildSnaps...) + allSnaps = append(allSnaps, stageSnaps...) + + for _, snapName := range allSnaps { + if snapName == "" { + continue + } + + snapMetadata := pkg.SnapEntry{ + SnapType: pkg.SnapTypeApp, + SnapName: snapName, + SnapVersion: "unknown", + Architecture: baseMetadata.Architecture, + } + + snapPkg := newPackage( + snapName, + "unknown", + snapMetadata, + location, + ) + packages = append(packages, snapPkg) + } + + return packages +} diff --git a/syft/pkg/cataloger/snap/parse_system_manifest.go b/syft/pkg/cataloger/snap/parse_system_manifest.go new file mode 100644 index 000000000..ee7159c83 --- /dev/null +++ b/syft/pkg/cataloger/snap/parse_system_manifest.go @@ -0,0 +1,102 @@ +package snap + +import ( + "context" + "fmt" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/generic" +) + +// systemManifest represents the structure of manifest.yaml files found in system/gadget snaps +type systemManifest struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Base string `yaml:"base"` + Grade string `yaml:"grade"` + Confinement string `yaml:"confinement"` + PrimedStagePackages []string `yaml:"primed-stage-packages"` + Architectures []string `yaml:"architectures"` + SnapcraftVersion string `yaml:"snapcraft-version"` + SnapcraftOSReleaseID string `yaml:"snapcraft-os-release-id"` +} + +// parseSystemManifest parses manifest.yaml files from system/gadget snaps +func parseSystemManifest(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + var manifest systemManifest + + decoder := yaml.NewDecoder(reader) + if err := decoder.Decode(&manifest); err != nil { + return nil, nil, fmt.Errorf("failed to parse manifest.yaml: %w", err) + } + + var packages []pkg.Package + + // Determine snap type - could be system, gadget, or app + snapType := pkg.SnapTypeApp // Default + if manifest.Name != "" { + // Try to infer type from name patterns or content + switch { + case strings.Contains(strings.ToLower(manifest.Name), "gadget"): + snapType = pkg.SnapTypeGadget + default: + snapType = pkg.SnapTypeApp // System snaps are often just regular apps + } + } + + snapMetadata := pkg.SnapEntry{ + SnapType: snapType, + Base: manifest.Base, + SnapName: manifest.Name, + SnapVersion: manifest.Version, + } + + // Set architecture if available + if len(manifest.Architectures) > 0 { + snapMetadata.Architecture = manifest.Architectures[0] + } + + // Parse primed-stage-packages entries + for _, pkgEntry := range manifest.PrimedStagePackages { + if !strings.Contains(pkgEntry, "=") { + continue // Skip malformed entries + } + + parts := strings.SplitN(pkgEntry, "=", 2) + if len(parts) != 2 { + continue + } + + name := strings.TrimSpace(parts[0]) + version := strings.TrimSpace(parts[1]) + + // Skip empty names or versions + if name == "" || version == "" { + continue + } + + // Handle architecture suffixes if present + currentMetadata := snapMetadata + if strings.Contains(name, ":") { + archParts := strings.SplitN(name, ":", 2) + name = archParts[0] + currentMetadata.Architecture = archParts[1] + } + + debPkg := newDebianPackageFromSnap( + name, + version, + currentMetadata, + reader.Location, + ) + + packages = append(packages, debPkg) + } + + return packages, nil, nil +} diff --git a/syft/pkg/cataloger/snap/test-fixtures/dpkg.yaml b/syft/pkg/cataloger/snap/test-fixtures/dpkg.yaml new file mode 100644 index 000000000..497e7cada --- /dev/null +++ b/syft/pkg/cataloger/snap/test-fixtures/dpkg.yaml @@ -0,0 +1,7 @@ +package-repositories: +- type: apt + ppa: ucdev/ubuntu/base-ppa +packages: +- adduser=3.118ubuntu2 +- apparmor=2.13.3-7ubuntu5.3 +- gcc-10-base:amd64=10.5.0-1ubuntu1~20.04 \ No newline at end of file diff --git a/syft/pkg/cataloger/snap/test-fixtures/glob-paths/base/usr/share/snappy/dpkg.yaml b/syft/pkg/cataloger/snap/test-fixtures/glob-paths/base/usr/share/snappy/dpkg.yaml new file mode 100644 index 000000000..14a2c1305 --- /dev/null +++ b/syft/pkg/cataloger/snap/test-fixtures/glob-paths/base/usr/share/snappy/dpkg.yaml @@ -0,0 +1,2 @@ +packages: +- test-package=1.0.0 \ No newline at end of file diff --git a/syft/pkg/cataloger/snap/test-fixtures/glob-paths/meta/meta/snap.yaml b/syft/pkg/cataloger/snap/test-fixtures/glob-paths/meta/meta/snap.yaml new file mode 100644 index 000000000..a160d3522 --- /dev/null +++ b/syft/pkg/cataloger/snap/test-fixtures/glob-paths/meta/meta/snap.yaml @@ -0,0 +1,3 @@ +name: test-snap +version: 1.0 +type: app \ No newline at end of file diff --git a/syft/pkg/cataloger/snap/test-fixtures/glob-paths/system/snap/manifest.yaml b/syft/pkg/cataloger/snap/test-fixtures/glob-paths/system/snap/manifest.yaml new file mode 100644 index 000000000..8d7f87e7b --- /dev/null +++ b/syft/pkg/cataloger/snap/test-fixtures/glob-paths/system/snap/manifest.yaml @@ -0,0 +1,4 @@ +name: test-snap +version: 1.0 +primed-stage-packages: +- test-package=1.0.0 \ No newline at end of file diff --git a/syft/pkg/cataloger/snap/test-fixtures/manifest.yaml b/syft/pkg/cataloger/snap/test-fixtures/manifest.yaml new file mode 100644 index 000000000..fbe8c932c --- /dev/null +++ b/syft/pkg/cataloger/snap/test-fixtures/manifest.yaml @@ -0,0 +1,17 @@ +snapcraft-version: 8.2.1.post5+git07b4ee15 +snapcraft-started-at: '2024-04-29T06:47:56.067540Z' +snapcraft-os-release-id: ubuntu +snapcraft-os-release-version-id: '24.04' +name: pc +version: 24-0.1 +summary: PC gadget for generic devices +description: | + This gadget enables generic pc devices to work with Ubuntu Core +base: core24 +grade: stable +confinement: strict +architectures: +- amd64 +primed-stage-packages: +- grub-efi-amd64-signed=1.202+2.12-1ubuntu7 +- shim-signed=1.56+15.7-0ubuntu1 \ No newline at end of file diff --git a/syft/pkg/cataloger/snap/test-fixtures/real-dpkg.yaml b/syft/pkg/cataloger/snap/test-fixtures/real-dpkg.yaml new file mode 100644 index 000000000..ab95331bb --- /dev/null +++ b/syft/pkg/cataloger/snap/test-fixtures/real-dpkg.yaml @@ -0,0 +1,14 @@ +package-repositories: +- type: apt + ppa: ucdev/ubuntu/base-ppa +packages: +- adduser=3.118ubuntu2 +- apparmor=2.13.3-7ubuntu5.3 +- apt=2.0.10 +- bash=5.0-6ubuntu1.2 +- coreutils=8.30-3ubuntu2 +- dpkg=1.19.7ubuntu3.2 +- gcc-10-base:amd64=10.5.0-1ubuntu1~20.04 +- libc6:amd64=2.31-0ubuntu9.16 +- systemd=245.4-4ubuntu3.23 +- ubuntu-keyring=2020.02.11.4 \ No newline at end of file diff --git a/syft/pkg/cataloger/snap/test-fixtures/snap.yaml b/syft/pkg/cataloger/snap/test-fixtures/snap.yaml new file mode 100644 index 000000000..b377efce4 --- /dev/null +++ b/syft/pkg/cataloger/snap/test-fixtures/snap.yaml @@ -0,0 +1,7 @@ +name: test-snap +version: 1.0.0 +summary: A test snap +description: This is a test snap for testing purposes +base: core20 +type: app +architecture: amd64 \ No newline at end of file diff --git a/syft/pkg/snap.go b/syft/pkg/snap.go new file mode 100644 index 000000000..02e5f8528 --- /dev/null +++ b/syft/pkg/snap.go @@ -0,0 +1,18 @@ +package pkg + +const ( + SnapTypeBase = "base" + SnapTypeKernel = "kernel" + SnapTypeApp = "app" + SnapTypeGadget = "gadget" + SnapTypeSnapd = "snapd" +) + +type SnapEntry struct { + SnapType string `json:"snapType" yaml:"snapType"` // base, kernel, system, gadget, snapd + Base string `json:"base" yaml:"base"` // base snap name (e.g., core20, core22) + SnapName string `json:"snapName" yaml:"snapName"` // name of the snap + SnapVersion string `json:"snapVersion" yaml:"snapVersion"` // version of the snap + Architecture string `json:"architecture" yaml:"architecture"` // architecture (amd64, arm64, etc.) + +}