diff --git a/internal/constants.go b/internal/constants.go index 5829a5855..0068cdd0a 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.24" + JSONSchemaVersion = "16.0.25" ) diff --git a/schema/json/schema-16.0.25.json b/schema/json/schema-16.0.25.json new file mode 100644 index 000000000..f42cb5342 --- /dev/null +++ b/schema/json/schema-16.0.25.json @@ -0,0 +1,2914 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/16.0.25/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" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "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" + }, + "HaskellHackageStackEntry": { + "properties": { + "pkgHash": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackLockEntry": { + "properties": { + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "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" + } + }, + "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" + ] + }, + "NixStoreEntry": { + "properties": { + "outputHash": { + "type": "string" + }, + "output": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash", + "files" + ] + }, + "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/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/HaskellHackageStackEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackLockEntry" + }, + { + "$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/PhpComposerInstalledEntry" + }, + { + "$ref": "#/$defs/PhpComposerLockEntry" + }, + { + "$ref": "#/$defs/PhpPeclEntry" + }, + { + "$ref": "#/$defs/PortageDbEntry" + }, + { + "$ref": "#/$defs/PythonPackage" + }, + { + "$ref": "#/$defs/PythonPipRequirementsEntry" + }, + { + "$ref": "#/$defs/PythonPipfileLockEntry" + }, + { + "$ref": "#/$defs/PythonPoetryLockEntry" + }, + { + "$ref": "#/$defs/RDescription" + }, + { + "$ref": "#/$defs/RpmArchive" + }, + { + "$ref": "#/$defs/RpmDbEntry" + }, + { + "$ref": "#/$defs/RubyGemspec" + }, + { + "$ref": "#/$defs/RustCargoAuditEntry" + }, + { + "$ref": "#/$defs/RustCargoLockEntry" + }, + { + "$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" + ] + }, + "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" + ] + }, + "PhpPeclEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "PortageDbEntry": { + "properties": { + "installedSize": { + "type": "integer" + }, + "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" + ] + }, + "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" + }, + "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" + }, + "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" + ] + }, + "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" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "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 c106015ed..f42cb5342 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.24/document", + "$id": "anchore.io/schema/syft/json/16.0.25/document", "$ref": "#/$defs/Document", "$defs": { "AlpmDbEntry": { @@ -995,6 +995,20 @@ "size" ] }, + "GithubActionsUseStatement": { + "properties": { + "value": { + "type": "string" + }, + "comment": { + "type": "string" + } + }, + "type": "object", + "required": [ + "value" + ] + }, "GoModuleBuildinfoEntry": { "properties": { "goBuildSettings": { @@ -1804,6 +1818,9 @@ { "$ref": "#/$defs/ErlangRebarLockEntry" }, + { + "$ref": "#/$defs/GithubActionsUseStatement" + }, { "$ref": "#/$defs/GoModuleBuildinfoEntry" }, diff --git a/syft/format/internal/spdxutil/helpers/originator_supplier.go b/syft/format/internal/spdxutil/helpers/originator_supplier.go index e86f3735f..95bfe5f76 100644 --- a/syft/format/internal/spdxutil/helpers/originator_supplier.go +++ b/syft/format/internal/spdxutil/helpers/originator_supplier.go @@ -34,7 +34,7 @@ const ( // // Available options are: , NOASSERTION, Person: , Organization: // return values are: , -func Originator(p pkg.Package) (typ string, author string) { //nolint: funlen +func Originator(p pkg.Package) (typ string, author string) { //nolint: gocyclo,funlen if !hasMetadata(p) { return typ, author } @@ -57,6 +57,15 @@ func Originator(p pkg.Package) (typ string, author string) { //nolint: funlen case pkg.DpkgArchiveEntry: author = metadata.Maintainer + case pkg.GitHubActionsUseStatement: + typ = orgType + org := strings.Split(metadata.Value, "/")[0] + if org == "actions" { + // this is a GitHub action, so the org is GitHub + org = "GitHub" + } + author = org + case pkg.JavaArchive: if metadata.Manifest != nil { author = metadata.Manifest.Main.MustGet("Specification-Vendor") diff --git a/syft/format/internal/spdxutil/helpers/originator_supplier_test.go b/syft/format/internal/spdxutil/helpers/originator_supplier_test.go index 1b700b4cb..ee65023fb 100644 --- a/syft/format/internal/spdxutil/helpers/originator_supplier_test.go +++ b/syft/format/internal/spdxutil/helpers/originator_supplier_test.go @@ -45,6 +45,7 @@ func Test_OriginatorSupplier(t *testing.T) { pkg.SwiplPackEntry{}, pkg.OpamPackage{}, pkg.YarnLockEntry{}, + pkg.TerraformLockProviderEntry{}, ) tests := []struct { name string @@ -384,20 +385,24 @@ func Test_OriginatorSupplier(t *testing.T) { supplier: "Person: me (me@auth.com)", }, { - name: "from ocaml opam", + name: "from github actions workflow/action", input: pkg.Package{ - Metadata: pkg.OpamPackage{}, + Metadata: pkg.GitHubActionsUseStatement{ + Value: "actions/checkout@v4", + }, }, - originator: "", - supplier: "", + originator: "Organization: GitHub", + supplier: "Organization: GitHub", }, { - name: "from terraform lock", + name: "from github actions workflow/action", input: pkg.Package{ - Metadata: pkg.TerraformLockProviderEntry{}, + Metadata: pkg.GitHubActionsUseStatement{ + Value: "google/something@v6", + }, }, - originator: "", - supplier: "", + originator: "Organization: google", + supplier: "Organization: google", }, } for _, test := range tests { diff --git a/syft/internal/packagemetadata/generated.go b/syft/internal/packagemetadata/generated.go index 7353b471d..a543afc99 100644 --- a/syft/internal/packagemetadata/generated.go +++ b/syft/internal/packagemetadata/generated.go @@ -25,6 +25,7 @@ func AllTypes() []any { pkg.ELFBinaryPackageNoteJSONPayload{}, pkg.ElixirMixLockEntry{}, pkg.ErlangRebarLockEntry{}, + pkg.GitHubActionsUseStatement{}, pkg.GolangBinaryBuildinfoEntry{}, pkg.GolangModuleEntry{}, pkg.HackageStackYamlEntry{}, diff --git a/syft/internal/packagemetadata/names.go b/syft/internal/packagemetadata/names.go index 7ef63c7ac..dd4fb2c0b 100644 --- a/syft/internal/packagemetadata/names.go +++ b/syft/internal/packagemetadata/names.go @@ -78,6 +78,7 @@ var jsonTypes = makeJSONTypes( jsonNames(pkg.DpkgDBEntry{}, "dpkg-db-entry", "DpkgMetadata"), jsonNames(pkg.ELFBinaryPackageNoteJSONPayload{}, "elf-binary-package-note-json-payload"), jsonNames(pkg.RubyGemspec{}, "ruby-gemspec", "GemMetadata"), + jsonNames(pkg.GitHubActionsUseStatement{}, "github-actions-use-statement"), jsonNames(pkg.GolangBinaryBuildinfoEntry{}, "go-module-buildinfo-entry", "GolangBinMetadata", "GolangMetadata"), jsonNames(pkg.GolangModuleEntry{}, "go-module-entry", "GolangModMetadata"), jsonNames(pkg.HackageStackYamlLockEntry{}, "haskell-hackage-stack-lock-entry", "HackageMetadataType"), diff --git a/syft/pkg/cataloger/githubactions/package.go b/syft/pkg/cataloger/githubactions/package.go index ff1567a91..cba6cea89 100644 --- a/syft/pkg/cataloger/githubactions/package.go +++ b/syft/pkg/cataloger/githubactions/package.go @@ -2,6 +2,7 @@ package githubactions import ( "fmt" + "regexp" "strings" "github.com/anchore/packageurl-go" @@ -10,8 +11,8 @@ import ( "github.com/anchore/syft/syft/pkg" ) -func newPackageFromUsageStatement(use string, location file.Location) (*pkg.Package, error) { - name, version := parseStepUsageStatement(use) +func newPackageFromUsageStatement(use, comment string, location file.Location) (*pkg.Package, error) { + name, version := parseStepUsageStatement(use, comment) if name == "" { log.WithFields("file", location.RealPath, "statement", use).Trace("unable to parse github action usage statement") @@ -19,19 +20,20 @@ func newPackageFromUsageStatement(use string, location file.Location) (*pkg.Pack } if strings.Contains(name, ".github/workflows/") { - return newGithubActionWorkflowPackageUsage(name, version, location), nil + return newGithubActionWorkflowPackageUsage(name, version, location, pkg.GitHubActionsUseStatement{Value: use, Comment: comment}), nil } - return newGithubActionPackageUsage(name, version, location), nil + return newGithubActionPackageUsage(name, version, location, pkg.GitHubActionsUseStatement{Value: use, Comment: comment}), nil } -func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package { +func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location, m pkg.GitHubActionsUseStatement) *pkg.Package { p := &pkg.Package{ Name: name, Version: version, Locations: file.NewLocationSet(workflowLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(name, version), Type: pkg.GithubActionWorkflowPkg, + Metadata: m, } p.SetID() @@ -39,13 +41,14 @@ func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation return p } -func newGithubActionPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package { +func newGithubActionPackageUsage(name, version string, workflowLocation file.Location, m pkg.GitHubActionsUseStatement) *pkg.Package { p := &pkg.Package{ Name: name, Version: version, Locations: file.NewLocationSet(workflowLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(name, version), Type: pkg.GithubActionPkg, + Metadata: m, } p.SetID() @@ -53,20 +56,32 @@ func newGithubActionPackageUsage(name, version string, workflowLocation file.Loc return p } -func parseStepUsageStatement(use string) (string, string) { - // from octo-org/another-repo/.github/workflows/workflow.yml@v1 get octo-org/another-repo/.github/workflows/workflow.yml and v1 - // from ./.github/workflows/workflow-2.yml interpret as only the name - - // from actions/cache@v3 get actions/cache and v3 +func parseStepUsageStatement(use, comment string) (string, string) { + // from "octo-org/another-repo/.github/workflows/workflow.yml@v1" get octo-org/another-repo/.github/workflows/workflow.yml and v1 + // from "./.github/workflows/workflow-2.yml" interpret as only the name + // from "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2" get actions/checkout and v4.2.2 + // from "actions/cache@v3" get actions/cache and v3 fields := strings.Split(use, "@") - switch len(fields) { - case 1: - return use, "" - case 2: - return fields[0], fields[1] + name := use + version := "" + + if len(fields) == 2 { + name = fields[0] + version = fields[1] } - return "", "" + + // if version looks like a commit hash and we have a comment, try to extract version from comment + if version != "" && regexp.MustCompile(`^[0-9a-f]{7,}$`).MatchString(version) && comment != "" { + versionRegex := regexp.MustCompile(`v?\d+\.\d+\.\d+`) + matches := versionRegex.FindStringSubmatch(comment) + + if len(matches) >= 1 { + return name, matches[0] + } + } + + return name, version } func packageURL(name, version string) string { diff --git a/syft/pkg/cataloger/githubactions/parse_composite_action.go b/syft/pkg/cataloger/githubactions/parse_composite_action.go index 3236679de..87075e099 100644 --- a/syft/pkg/cataloger/githubactions/parse_composite_action.go +++ b/syft/pkg/cataloger/githubactions/parse_composite_action.go @@ -43,7 +43,7 @@ func parseCompositeActionForActionUsage(_ context.Context, _ file.Resolver, _ *g continue } - p, err := newPackageFromUsageStatement(step.Uses, reader.Location) + p, err := newPackageFromUsageStatement(step.Uses, step.UsesComment, reader.Location) if err != nil { errs = unknown.Append(errs, reader, err) } diff --git a/syft/pkg/cataloger/githubactions/parse_composite_action_test.go b/syft/pkg/cataloger/githubactions/parse_composite_action_test.go index 080d80816..ad718f2fe 100644 --- a/syft/pkg/cataloger/githubactions/parse_composite_action_test.go +++ b/syft/pkg/cataloger/githubactions/parse_composite_action_test.go @@ -20,6 +20,7 @@ func Test_parseCompositeActionForActionUsage(t *testing.T) { Type: pkg.GithubActionPkg, Locations: fixtureLocationSet, PURL: "pkg:github/actions/setup-go@v4", + Metadata: pkg.GitHubActionsUseStatement{Value: "actions/setup-go@v4"}, }, { Name: "actions/cache", @@ -27,6 +28,7 @@ func Test_parseCompositeActionForActionUsage(t *testing.T) { Type: pkg.GithubActionPkg, Locations: fixtureLocationSet, PURL: "pkg:github/actions/cache@v3", + Metadata: pkg.GitHubActionsUseStatement{Value: "actions/cache@v3"}, }, } diff --git a/syft/pkg/cataloger/githubactions/parse_workflow.go b/syft/pkg/cataloger/githubactions/parse_workflow.go index 2480caf08..9862b7ee8 100644 --- a/syft/pkg/cataloger/githubactions/parse_workflow.go +++ b/syft/pkg/cataloger/githubactions/parse_workflow.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "regexp" "gopkg.in/yaml.v3" @@ -24,14 +25,16 @@ type workflowDef struct { } type workflowJobDef struct { - Uses string `yaml:"uses"` - Steps []stepDef `yaml:"steps"` + Uses string `yaml:"uses"` + UsesComment string `yaml:"-"` + Steps []stepDef `yaml:"steps"` } type stepDef struct { - Name string `yaml:"name"` - Uses string `yaml:"uses"` - With struct { + Name string `yaml:"name"` + Uses string `yaml:"uses"` + UsesComment string `yaml:"-"` + With struct { Path string `yaml:"path"` Key string `yaml:"key"` } `yaml:"with"` @@ -43,17 +46,26 @@ func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generi return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs) } - var wf workflowDef - if errs = yaml.Unmarshal(contents, &wf); errs != nil { + // parse the yaml file into a generic node to preserve comments + var node yaml.Node + if errs = yaml.Unmarshal(contents, &node); errs != nil { return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs) } + // unmarshal the node into a workflowDef struct + var wf workflowDef + if errs = node.Decode(&wf); errs != nil { + return nil, nil, fmt.Errorf("unable to decode workflow: %w", errs) + } + + attachUsageComments(&node, &wf) + // we use a collection to help with deduplication before raising to higher level processing pkgs := pkg.NewCollection() for _, job := range wf.Jobs { if job.Uses != "" { - p, err := newPackageFromUsageStatement(job.Uses, reader.Location) + p, err := newPackageFromUsageStatement(job.Uses, job.UsesComment, reader.Location) if err != nil { errs = unknown.Append(errs, reader, err) } @@ -72,11 +84,20 @@ func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic. return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs) } - var wf workflowDef - if errs = yaml.Unmarshal(contents, &wf); errs != nil { + // parse the yaml file into a generic node to preserve comments + var node yaml.Node + if errs = yaml.Unmarshal(contents, &node); errs != nil { return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs) } + // unmarshal the node into a workflowDef struct + var wf workflowDef + if errs = node.Decode(&wf); errs != nil { + return nil, nil, fmt.Errorf("unable to decode workflow: %w", errs) + } + + attachUsageComments(&node, &wf) + // we use a collection to help with deduplication before raising to higher level processing pkgs := pkg.NewCollection() @@ -85,7 +106,7 @@ func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic. if step.Uses == "" { continue } - p, err := newPackageFromUsageStatement(step.Uses, reader.Location) + p, err := newPackageFromUsageStatement(step.Uses, step.UsesComment, reader.Location) if err != nil { errs = unknown.Append(errs, reader, err) } @@ -97,3 +118,101 @@ func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic. return pkgs.Sorted(), nil, errs } + +// attachUsageComments traverses the yaml node tree and attaches usage comments to the workflowDef job strcuts and step structs. +// This is a best-effort approach to attach comments to the correct job or step. +func attachUsageComments(node *yaml.Node, wf *workflowDef) { + // for a document node, process its content (usually a single mapping node) + if node.Kind == yaml.DocumentNode && len(node.Content) > 0 { + processNode(node.Content[0], wf, nil, nil, nil) + } else { + processNode(node, wf, nil, nil, nil) + } +} + +func processNode(node *yaml.Node, wf *workflowDef, currentJob *string, currentStep *int, inJobsSection *bool) { + switch node.Kind { + case yaml.MappingNode: + for i := 0; i < len(node.Content); i += 2 { + key := node.Content[i] + value := node.Content[i+1] + + // track if we're in the jobs section... + if key.Value == "jobs" && inJobsSection == nil { + inJobs := true + inJobsSection = &inJobs + processNode(value, wf, nil, nil, inJobsSection) + continue + } + + // if we're in jobs section, and this is a job key... + if inJobsSection != nil && *inJobsSection && currentJob == nil { + job := key.Value + currentJob = &job + processNode(value, wf, currentJob, nil, inJobsSection) + currentJob = nil + continue + } + + // if this is a "uses" key... + if key.Value == "uses" { + processUsesNode(value, wf, currentJob, currentStep) + } + + // if this is a "steps" key inside a job... + if key.Value == "steps" && currentJob != nil { + for j, stepNode := range value.Content { + stepIndex := j + processNode(stepNode, wf, currentJob, &stepIndex, inJobsSection) + } + continue + } + + processNode(key, wf, currentJob, currentStep, inJobsSection) + processNode(value, wf, currentJob, currentStep, inJobsSection) + } + + case yaml.SequenceNode: + for i, item := range node.Content { + idx := i + processNode(item, wf, currentJob, &idx, inJobsSection) + } + } +} + +func processUsesNode(node *yaml.Node, wf *workflowDef, currentJob *string, currentStep *int) { + if node.Kind != yaml.ScalarNode { + return + } + + comment := node.LineComment + if comment == "" { + comment = node.HeadComment + } + if comment == "" { + comment = node.FootComment + } + + if comment != "" { + versionRegex := regexp.MustCompile(`v?\d+(\.\d+)*`) + versionMatch := versionRegex.FindString(comment) + + if versionMatch != "" { + if currentJob != nil && currentStep == nil { + // this is a job level "uses" + if job, ok := wf.Jobs[*currentJob]; ok { + job.UsesComment = versionMatch + wf.Jobs[*currentJob] = job + } + } else if currentJob != nil && currentStep != nil { + // this is a step level "uses" + if job, ok := wf.Jobs[*currentJob]; ok { + if *currentStep < len(job.Steps) { + job.Steps[*currentStep].UsesComment = versionMatch + wf.Jobs[*currentJob] = job + } + } + } + } + } +} diff --git a/syft/pkg/cataloger/githubactions/parse_workflow_test.go b/syft/pkg/cataloger/githubactions/parse_workflow_test.go index 52c4e1ebd..96d35586d 100644 --- a/syft/pkg/cataloger/githubactions/parse_workflow_test.go +++ b/syft/pkg/cataloger/githubactions/parse_workflow_test.go @@ -20,6 +20,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) { Type: pkg.GithubActionPkg, Locations: fixtureLocationSet, PURL: "", // don't have enough context without parsing the git origin, which still may not be accurate + Metadata: pkg.GitHubActionsUseStatement{Value: "./.github/actions/bootstrap"}, }, { Name: "actions/cache", @@ -27,6 +28,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) { Type: pkg.GithubActionPkg, Locations: fixtureLocationSet, PURL: "pkg:github/actions/cache@v3", + Metadata: pkg.GitHubActionsUseStatement{Value: "actions/cache@v3"}, }, { Name: "actions/cache/restore", @@ -34,6 +36,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) { Type: pkg.GithubActionPkg, Locations: fixtureLocationSet, PURL: "pkg:github/actions/cache@v3#restore", + Metadata: pkg.GitHubActionsUseStatement{Value: "actions/cache/restore@v3"}, }, { Name: "actions/cache/save", @@ -41,6 +44,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) { Type: pkg.GithubActionPkg, Locations: fixtureLocationSet, PURL: "pkg:github/actions/cache@v3#save", + Metadata: pkg.GitHubActionsUseStatement{Value: "actions/cache/save@v3"}, }, { Name: "actions/checkout", @@ -48,6 +52,7 @@ func Test_parseWorkflowForActionUsage(t *testing.T) { Type: pkg.GithubActionPkg, Locations: fixtureLocationSet, PURL: "pkg:github/actions/checkout@v4", + Metadata: pkg.GitHubActionsUseStatement{Value: "actions/checkout@v4"}, }, } @@ -66,6 +71,9 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) { Type: pkg.GithubActionWorkflowPkg, Locations: fixtureLocationSet, PURL: "pkg:github/octo-org/this-repo@172239021f7ba04fe7327647b213799853a9eb89#.github/workflows/workflow-1.yml", + Metadata: pkg.GitHubActionsUseStatement{ + Value: "octo-org/this-repo/.github/workflows/workflow-1.yml@172239021f7ba04fe7327647b213799853a9eb89", + }, }, { Name: "./.github/workflows/workflow-2.yml", @@ -73,6 +81,7 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) { Type: pkg.GithubActionWorkflowPkg, Locations: fixtureLocationSet, PURL: "", // don't have enough context without parsing the git origin, which still may not be accurate + Metadata: pkg.GitHubActionsUseStatement{Value: "./.github/workflows/workflow-2.yml"}, }, { Name: "octo-org/another-repo/.github/workflows/workflow.yml", @@ -80,6 +89,7 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) { Type: pkg.GithubActionWorkflowPkg, Locations: fixtureLocationSet, PURL: "pkg:github/octo-org/another-repo@v1#.github/workflows/workflow.yml", + Metadata: pkg.GitHubActionsUseStatement{Value: "octo-org/another-repo/.github/workflows/workflow.yml@v1"}, }, } @@ -87,6 +97,38 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) { pkgtest.TestFileParser(t, fixture, parseWorkflowForWorkflowUsage, expected, expectedRelationships) } +func Test_parseWorkflowForVersionComments(t *testing.T) { + fixture := "test-fixtures/workflow-with-version-comments.yaml" + fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) + + expected := []pkg.Package{ + { + Name: "./.github/actions/bootstrap", + Version: "", + Type: pkg.GithubActionPkg, + Locations: fixtureLocationSet, + PURL: "", // don't have enough context without parsing the git origin, which still may not be accurate + Metadata: pkg.GitHubActionsUseStatement{ + Value: "./.github/actions/bootstrap", + }, + }, + { + Name: "actions/checkout", + Version: "v4.2.2", + Type: pkg.GithubActionPkg, + Locations: fixtureLocationSet, + PURL: "pkg:github/actions/checkout@v4.2.2", + Metadata: pkg.GitHubActionsUseStatement{ + Value: "actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683", + Comment: "v4.2.2", + }, + }, + } + + var expectedRelationships []artifact.Relationship + pkgtest.TestFileParser(t, fixture, parseWorkflowForActionUsage, expected, expectedRelationships) +} + func Test_corruptActionWorkflow(t *testing.T) { pkgtest.NewCatalogTester(). FromFile(t, "test-fixtures/corrupt/workflow-multi-job.yaml"). diff --git a/syft/pkg/cataloger/githubactions/test-fixtures/workflow-with-version-comments.yaml b/syft/pkg/cataloger/githubactions/test-fixtures/workflow-with-version-comments.yaml new file mode 100644 index 000000000..88ea28aa1 --- /dev/null +++ b/syft/pkg/cataloger/githubactions/test-fixtures/workflow-with-version-comments.yaml @@ -0,0 +1,28 @@ +name: "Validations" + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + + call-workflow-1-in-local-repo: + uses: octo-org/this-repo/.github/workflows/workflow-1.yml@172239021f7ba04fe7327647b213799853a9eb89 #v1.0.0 + + Static-Analysis: + name: "Static analysis" + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + + - name: Bootstrap environment + uses: ./.github/actions/bootstrap + + - name: Run static analysis + run: make static-analysis diff --git a/syft/pkg/github.go b/syft/pkg/github.go new file mode 100644 index 000000000..8ffabea3b --- /dev/null +++ b/syft/pkg/github.go @@ -0,0 +1,6 @@ +package pkg + +type GitHubActionsUseStatement struct { + Value string `json:"value"` + Comment string `json:"comment,omitempty"` +}