diff --git a/app/cli/documentation/cli-reference.mdx b/app/cli/documentation/cli-reference.mdx index be9aaeb96..013853ab0 100755 --- a/app/cli/documentation/cli-reference.mdx +++ b/app/cli/documentation/cli-reference.mdx @@ -268,7 +268,7 @@ Options --append reserved for a future release: will control whether --policy-input and --policy-input-from-file append to (rather than replace) the contract-declared value; has no effect yet --attestation-id string Unique identifier of the in-progress attestation -h, --help help for add ---kind string kind of the material to be recorded: ["ARTIFACT" "ASYNCAPI_SPEC" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CERTCC_DRANZER" "CHAINLOOP_AI_AGENT_CONFIG" "CHAINLOOP_AI_CODING_SESSION" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CHECKMARX_JSON" "COBERTURA_XML" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "GRAPHQL_SPEC" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENAPI_SPEC" "OPENVEX" "OSSF_SCORECARD_JSON" "RADAMSA_CRASHES" "RADAMSA_REPORT" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "SYSINTERNALS_ACCESSCHK" "SYSINTERNALS_SIGCHECK" "TRUFFLEHOG_JSON" "TWISTCLI_SCAN_JSON" "YELP_DETECT_SECRETS_BASELINE" "ZAP_DAST_ZIP"] +--kind string kind of the material to be recorded: ["ARTIFACT" "ASYNCAPI_SPEC" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CERTCC_DRANZER" "CHAINLOOP_AI_AGENT_CONFIG" "CHAINLOOP_AI_CODING_SESSION" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CHECKMARX_JSON" "COBERTURA_XML" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "GRAPHQL_SPEC" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENAPI_SPEC" "OPENVEX" "OSSF_SCORECARD_JSON" "OVERSECURED_JSON" "RADAMSA_CRASHES" "RADAMSA_REPORT" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "SYSINTERNALS_ACCESSCHK" "SYSINTERNALS_SIGCHECK" "TRUFFLEHOG_JSON" "TWISTCLI_SCAN_JSON" "YELP_DETECT_SECRETS_BASELINE" "ZAP_DAST_ZIP"] --max-extract-entries int max number of files to extract when --value is an archive (default 10000) --max-extract-size string max total uncompressed size to extract when --value is an archive (default "1GiB") --name string name of the material as shown in the contract @@ -3045,7 +3045,7 @@ Options --annotation strings Key-value pairs of material annotations (key=value) -h, --help help for eval --input stringArray Key-value pairs of policy inputs (key=value) ---kind string Kind of the material: ["ARTIFACT" "ASYNCAPI_SPEC" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CERTCC_DRANZER" "CHAINLOOP_AI_AGENT_CONFIG" "CHAINLOOP_AI_CODING_SESSION" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CHECKMARX_JSON" "COBERTURA_XML" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "GRAPHQL_SPEC" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENAPI_SPEC" "OPENVEX" "OSSF_SCORECARD_JSON" "RADAMSA_CRASHES" "RADAMSA_REPORT" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "SYSINTERNALS_ACCESSCHK" "SYSINTERNALS_SIGCHECK" "TRUFFLEHOG_JSON" "TWISTCLI_SCAN_JSON" "YELP_DETECT_SECRETS_BASELINE" "ZAP_DAST_ZIP"] +--kind string Kind of the material: ["ARTIFACT" "ASYNCAPI_SPEC" "ATTESTATION" "BLACKDUCK_SCA_JSON" "CERTCC_DRANZER" "CHAINLOOP_AI_AGENT_CONFIG" "CHAINLOOP_AI_CODING_SESSION" "CHAINLOOP_PR_INFO" "CHAINLOOP_RUNNER_CONTEXT" "CHECKMARX_JSON" "COBERTURA_XML" "CONTAINER_IMAGE" "CSAF_INFORMATIONAL_ADVISORY" "CSAF_SECURITY_ADVISORY" "CSAF_SECURITY_INCIDENT_RESPONSE" "CSAF_VEX" "EVIDENCE" "GHAS_CODE_SCAN" "GHAS_DEPENDENCY_SCAN" "GHAS_SECRET_SCAN" "GITLAB_SECURITY_REPORT" "GITLEAKS_JSON" "GRAPHQL_SPEC" "HELM_CHART" "JACOCO_XML" "JUNIT_XML" "OPENAPI_SPEC" "OPENVEX" "OSSF_SCORECARD_JSON" "OVERSECURED_JSON" "RADAMSA_CRASHES" "RADAMSA_REPORT" "SARIF" "SBOM_CYCLONEDX_JSON" "SBOM_SPDX_JSON" "SLSA_PROVENANCE" "STRING" "SYSINTERNALS_ACCESSCHK" "SYSINTERNALS_SIGCHECK" "TRUFFLEHOG_JSON" "TWISTCLI_SCAN_JSON" "YELP_DETECT_SECRETS_BASELINE" "ZAP_DAST_ZIP"] --material string Path to material or attestation file -p, --policy string Policy reference (./my-policy.yaml, https://my-domain.com/my-policy.yaml, chainloop://my-stored-policy) (default "policy.yaml") --project string Project name to use as engine context for chainloop.* built-ins diff --git a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts index 89cebfbdb..0db137e9c 100644 --- a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts +++ b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts @@ -326,6 +326,11 @@ export enum CraftingSchema_Material_MaterialType { * https://github.com/Checkmarx/ast-cli/blob/main/internal/wrappers/results-json.go */ CHECKMARX_JSON = 42, + /** + * OVERSECURED_JSON - Oversecured mobile (Android/iOS) scan report, whole-scan JSON export + * https://docs.oversecured.com/docs/guide-exporting-reports + */ + OVERSECURED_JSON = 43, UNRECOGNIZED = -1, } @@ -460,6 +465,9 @@ export function craftingSchema_Material_MaterialTypeFromJSON(object: any): Craft case 42: case "CHECKMARX_JSON": return CraftingSchema_Material_MaterialType.CHECKMARX_JSON; + case 43: + case "OVERSECURED_JSON": + return CraftingSchema_Material_MaterialType.OVERSECURED_JSON; case -1: case "UNRECOGNIZED": default: @@ -555,6 +563,8 @@ export function craftingSchema_Material_MaterialTypeToJSON(object: CraftingSchem return "COBERTURA_XML"; case CraftingSchema_Material_MaterialType.CHECKMARX_JSON: return "CHECKMARX_JSON"; + case CraftingSchema_Material_MaterialType.OVERSECURED_JSON: + return "OVERSECURED_JSON"; case CraftingSchema_Material_MaterialType.UNRECOGNIZED: default: return "UNRECOGNIZED"; diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.jsonschema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.jsonschema.json index 9d2e5f485..de5b8934d 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.jsonschema.json @@ -59,7 +59,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" @@ -154,7 +155,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.schema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.schema.json index 19ff7a2ef..57fc9dab0 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.schema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.Attestation.Material.schema.json @@ -59,7 +59,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" @@ -154,7 +155,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json index ccd767abf..90ae287bd 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json @@ -170,7 +170,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json b/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json index dfcdb0356..caf1ed28e 100644 --- a/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json +++ b/app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json @@ -170,7 +170,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.jsonschema.json index 4bffd8f75..9d8c4b900 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.jsonschema.json @@ -80,7 +80,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.schema.json index 60086425f..7a4f7cd56 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.CraftingSchema.Material.schema.json @@ -80,7 +80,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.jsonschema.json index be4538598..cfc3c4bf8 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.jsonschema.json @@ -104,7 +104,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.schema.json index d624de007..f0c60fb38 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyGroup.Material.schema.json @@ -104,7 +104,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.jsonschema.json index 4f685dcae..303314a9d 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.jsonschema.json @@ -78,7 +78,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.schema.json index 18132852c..1e3bfad5f 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpec.schema.json @@ -78,7 +78,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json index 6de12fa1b..da6bd022f 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json @@ -99,7 +99,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json index f83f3e706..38d9edc1d 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json @@ -99,7 +99,8 @@ "RADAMSA_CRASHES", "TRUFFLEHOG_JSON", "COBERTURA_XML", - "CHECKMARX_JSON" + "CHECKMARX_JSON", + "OVERSECURED_JSON" ], "title": "Material Type", "type": "string" diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go index ecf94cffc..809fa9032 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go @@ -250,6 +250,9 @@ const ( // Checkmarx One native JSON report (ScanResultsCollection) // https://github.com/Checkmarx/ast-cli/blob/main/internal/wrappers/results-json.go CraftingSchema_Material_CHECKMARX_JSON CraftingSchema_Material_MaterialType = 42 + // Oversecured mobile (Android/iOS) scan report, whole-scan JSON export + // https://docs.oversecured.com/docs/guide-exporting-reports + CraftingSchema_Material_OVERSECURED_JSON CraftingSchema_Material_MaterialType = 43 ) // Enum value maps for CraftingSchema_Material_MaterialType. @@ -298,6 +301,7 @@ var ( 40: "TRUFFLEHOG_JSON", 41: "COBERTURA_XML", 42: "CHECKMARX_JSON", + 43: "OVERSECURED_JSON", } CraftingSchema_Material_MaterialType_value = map[string]int32{ "MATERIAL_TYPE_UNSPECIFIED": 0, @@ -343,6 +347,7 @@ var ( "TRUFFLEHOG_JSON": 40, "COBERTURA_XML": 41, "CHECKMARX_JSON": 42, + "OVERSECURED_JSON": 43, } ) @@ -2091,7 +2096,7 @@ var File_workflowcontract_v1_crafting_schema_proto protoreflect.FileDescriptor const file_workflowcontract_v1_crafting_schema_proto_rawDesc = "" + "\n" + - ")workflowcontract/v1/crafting_schema.proto\x12\x13workflowcontract.v1\x1a\x1bbuf/validate/validate.proto\"\xd0\x12\n" + + ")workflowcontract/v1/crafting_schema.proto\x12\x13workflowcontract.v1\x1a\x1bbuf/validate/validate.proto\"\xe6\x12\n" + "\x0eCraftingSchema\x122\n" + "\x0eschema_version\x18\x01 \x01(\tB\v\xbaH\x06r\x04\n" + "\x02v1\x18\x01R\rschemaVersion\x12N\n" + @@ -2114,7 +2119,7 @@ const file_workflowcontract_v1_crafting_schema_proto_rawDesc = "" + "\x0fDAGGER_PIPELINE\x10\x06\x12\x15\n" + "\x11TEAMCITY_PIPELINE\x10\a\x12\x13\n" + "\x0fTEKTON_PIPELINE\x10\b\x12\x15\n" + - "\x11CHAINLOOP_SANDBOX\x10\t:\x02\x18\x01\x1a\x9b\f\n" + + "\x11CHAINLOOP_SANDBOX\x10\t:\x02\x18\x01\x1a\xb1\f\n" + "\bMaterial\x12[\n" + "\x04type\x18\x01 \x01(\x0e29.workflowcontract.v1.CraftingSchema.Material.MaterialTypeB\f\xbaH\a\x82\x01\x04\x10\x01 \x00\x18\x01R\x04type\x12\x99\x01\n" + "\x04name\x18\x02 \x01(\tB\x84\x01\xbaH\x7f\xba\x01|\n" + @@ -2125,7 +2130,7 @@ const file_workflowcontract_v1_crafting_schema_proto_rawDesc = "" + "\vskip_upload\x18\x06 \x01(\bR\n" + "skipUpload\x12\xaa\x01\n" + "\x05group\x18\a \x01(\tB\x93\x01\xbaH\x8f\x01\xba\x01\x8b\x01\n" + - "\x0egroup.dns-1123\x12:must contain only lowercase letters, numbers, and hyphens.\x1a=this == '' || this.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')R\x05group\"\xc0\a\n" + + "\x0egroup.dns-1123\x12:must contain only lowercase letters, numbers, and hyphens.\x1a=this == '' || this.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')R\x05group\"\xd6\a\n" + "\fMaterialType\x12\x1d\n" + "\x19MATERIAL_TYPE_UNSPECIFIED\x10\x00\x12\n" + "\n" + @@ -2173,7 +2178,8 @@ const file_workflowcontract_v1_crafting_schema_proto_rawDesc = "" + "\x0fRADAMSA_CRASHES\x10'\x12\x13\n" + "\x0fTRUFFLEHOG_JSON\x10(\x12\x11\n" + "\rCOBERTURA_XML\x10)\x12\x12\n" + - "\x0eCHECKMARX_JSON\x10*:\x02\x18\x01:\x02\x18\x01\"\xfb\x01\n" + + "\x0eCHECKMARX_JSON\x10*\x12\x14\n" + + "\x10OVERSECURED_JSON\x10+:\x02\x18\x01:\x02\x18\x01\"\xfb\x01\n" + "\x10CraftingSchemaV2\x128\n" + "\vapi_version\x18\x01 \x01(\tB\x17\xbaH\x14r\x12\n" + "\x10chainloop.dev/v1R\n" + diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto index 4e662fcbf..96f9994a1 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto @@ -199,6 +199,9 @@ message CraftingSchema { // Checkmarx One native JSON report (ScanResultsCollection) // https://github.com/Checkmarx/ast-cli/blob/main/internal/wrappers/results-json.go CHECKMARX_JSON = 42; + // Oversecured mobile (Android/iOS) scan report, whole-scan JSON export + // https://docs.oversecured.com/docs/guide-exporting-reports + OVERSECURED_JSON = 43; } } } diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema_test.go b/app/controlplane/api/workflowcontract/v1/crafting_schema_test.go index 422b5d860..d0b3734e2 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema_test.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema_test.go @@ -307,3 +307,38 @@ func TestValidateRefs(t *testing.T) { }) } } + +// Kinds deliberately kept out of CraftingMaterialInValidationOrder, the list +// walked to guess a kind when `chainloop attestation add` is given no --kind. +// Each is excluded for a documented reason (see the NOTEs beside the list), so +// pin them here: adding one back would silently start probing every +// contract-free material against it. +func TestMaterialKindsExcludedFromAutoDetection(t *testing.T) { + testCases := []struct { + name string + kind v1.CraftingSchema_Material_MaterialType + }{ + { + name: "checkmarx report is generic JSON", + kind: v1.CraftingSchema_Material_CHECKMARX_JSON, + }, + { + name: "oversecured export envelope is not published by the vendor", + kind: v1.CraftingSchema_Material_OVERSECURED_JSON, + }, + { + name: "radamsa report", + kind: v1.CraftingSchema_Material_RADAMSA_REPORT, + }, + { + name: "radamsa crashes accept almost any non-empty file", + kind: v1.CraftingSchema_Material_RADAMSA_CRASHES, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.NotContains(t, v1.CraftingMaterialInValidationOrder, tc.kind) + }) + } +} diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema_validations.go b/app/controlplane/api/workflowcontract/v1/crafting_schema_validations.go index 34e51b144..6c5dfab31 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema_validations.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema_validations.go @@ -30,6 +30,20 @@ import ( // CraftingMaterialInValidationOrder all type of CraftingMaterial that are available for automatic // detection. The order of the list is important as it defines the order of the // detection process. Normally from most common one to the least common one and weaker validation method. +// +// Kinds deliberately left out, each because auto-detecting it would misfire on +// other kinds' files. They all work when referenced with an explicit --kind or +// from a workflow contract: +// - RADAMSA_CRASHES: single-file mode accepts almost any non-empty file, so it +// would eagerly shadow every other type. RADAMSA_REPORT goes with it. +// - CHECKMARX_JSON: generic JSON that risks shadowing (or being shadowed by) +// other JSON kinds. Revisit once the fingerprint is proven strong. +// - OVERSECURED_JSON: a stronger fingerprint (a header carrying a scan id and +// an app platform), but the export envelope is not published by the vendor, +// so pinning it here would bet every other JSON kind on a shape inferred +// from a sample. +// +// TestMaterialKindsExcludedFromAutoDetection pins these exclusions. var CraftingMaterialInValidationOrder = []CraftingSchema_Material_MaterialType{ CraftingSchema_Material_OPENVEX, CraftingSchema_Material_SBOM_CYCLONEDX_JSON, @@ -49,14 +63,6 @@ var CraftingMaterialInValidationOrder = []CraftingSchema_Material_MaterialType{ CraftingSchema_Material_JUNIT_XML, CraftingSchema_Material_JACOCO_XML, CraftingSchema_Material_COBERTURA_XML, - // NOTE: RADAMSA_REPORT and RADAMSA_CRASHES are intentionally omitted from - // auto-detection. RADAMSA_CRASHES single-file mode accepts almost any - // non-empty file and would eagerly shadow other types; both work fine when - // referenced with an explicit kind in a workflow contract. - // NOTE: CHECKMARX_JSON is intentionally omitted from auto-detection. The - // Checkmarx native report is generic JSON that risks shadowing (or being - // shadowed by) other JSON kinds; it must be referenced with an explicit - // --kind CHECKMARX_JSON. Revisit once the fingerprint is proven strong. CraftingSchema_Material_HELM_CHART, CraftingSchema_Material_SARIF, CraftingSchema_Material_BLACKDUCK_SCA_JSON, diff --git a/pkg/attestation/crafter/materials/materials.go b/pkg/attestation/crafter/materials/materials.go index 89337c6c0..b5bb017bd 100644 --- a/pkg/attestation/crafter/materials/materials.go +++ b/pkg/attestation/crafter/materials/materials.go @@ -424,6 +424,8 @@ func Craft(ctx context.Context, materialSchema *schemaapi.CraftingSchema_Materia crafter, err = NewTrufflehogCrafter(materialSchema, casBackend, logger) case schemaapi.CraftingSchema_Material_CHECKMARX_JSON: crafter, err = NewCheckmarxCrafter(materialSchema, casBackend, logger) + case schemaapi.CraftingSchema_Material_OVERSECURED_JSON: + crafter, err = NewOversecuredCrafter(materialSchema, casBackend, logger) default: return nil, fmt.Errorf("material of type %q not supported yet", materialSchema.Type) } diff --git a/pkg/attestation/crafter/materials/oversecured.go b/pkg/attestation/crafter/materials/oversecured.go new file mode 100644 index 000000000..ac9cf0e5a --- /dev/null +++ b/pkg/attestation/crafter/materials/oversecured.go @@ -0,0 +1,156 @@ +// +// Copyright 2026 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package materials + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "strings" + + schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1" + "github.com/chainloop-dev/chainloop/pkg/casclient" + "github.com/rs/zerolog" +) + +type OversecuredCrafter struct { + *crafterCommon + backend *casclient.CASBackend +} + +// oversecuredReport is the subset of the Oversecured whole-scan JSON export +// (`oversecured report --app --format json`, or +// `GET …/scans/{scanId}/export?format=json`) used to validate its structure. +// The vendor does not publish this envelope, so it is pinned from a real export. +// https://docs.oversecured.com/docs/guide-exporting-reports +// +// Vulnerabilities is kept as a json.RawMessage for two reasons. It tells an +// absent "vulnerabilities" key (look-alike JSON) apart from a present-but-null +// or empty one (a clean scan) — encoding/json decodes both an absent key and an +// explicit null into a nil typed slice, so a typed field alone cannot +// distinguish them. It also leaves the findings themselves unparsed: they carry +// decompiled sources and DAST heap snapshots and make up nearly all of the +// file, while identifying the tool no better than the header already does. +type oversecuredReport struct { + Header oversecuredHeader `json:"header"` + Vulnerabilities json.RawMessage `json:"vulnerabilities"` +} + +type oversecuredHeader struct { + Scan oversecuredScan `json:"scan"` + App oversecuredApp `json:"app"` +} + +type oversecuredScan struct { + ID string `json:"id"` +} + +type oversecuredApp struct { + Platform string `json:"platform"` +} + +// The platforms Oversecured scans today. An export naming something else is +// still accepted — see Craft — so a new vendor platform does not need a +// Chainloop release to unblock pipelines. +const ( + oversecuredPlatformAndroid = "android" + oversecuredPlatformIOS = "ios" +) + +func NewOversecuredCrafter(schema *schemaapi.CraftingSchema_Material, backend *casclient.CASBackend, l *zerolog.Logger) (*OversecuredCrafter, error) { + if schema.Type != schemaapi.CraftingSchema_Material_OVERSECURED_JSON { + return nil, fmt.Errorf("material type is not an Oversecured JSON report") + } + craftCommon := &crafterCommon{logger: l, input: schema} + return &OversecuredCrafter{backend: backend, crafterCommon: craftCommon}, nil +} + +func (i *OversecuredCrafter) Craft(ctx context.Context, filePath string) (*api.Attestation_Material, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("can't open the file: %w", err) + } + + var report oversecuredReport + if err = json.Unmarshal(data, &report); err != nil { + return nil, fmt.Errorf("invalid Oversecured JSON report: %w", ErrInvalidMaterialType) + } + + // Structural fingerprint check, deliberately limited to the export envelope: + // a scan id, an app platform, and a findings array. Unlike the findings, the + // envelope costs the same to check no matter how large the report is. + // Auto-detection is intentionally disabled for this kind, but we still reject + // look-alike JSON so an explicit --kind OVERSECURED_JSON fails loudly on the + // wrong file — including on the paginated findings list, which carries no + // header and holds a single page of findings (20 by default). + if report.Header.Scan.ID == "" { + return nil, fmt.Errorf("missing scan id in Oversecured report: %w", ErrInvalidMaterialType) + } + + // Only the presence of a platform is required, not its value: Oversecured + // scans mobile binaries exclusively, so an export always names one, but + // hard-coding the vocabulary would mean a new vendor platform breaks every + // pipeline until Chainloop ships a release. + platform := strings.ToLower(report.Header.App.Platform) + if platform == "" { + return nil, fmt.Errorf("missing app platform in Oversecured report: %w", ErrInvalidMaterialType) + } + + if platform != oversecuredPlatformAndroid && platform != oversecuredPlatformIOS { + i.logger.Debug().Str("platform", report.Header.App.Platform).Msg("unfamiliar Oversecured app platform") + } + + if report.Vulnerabilities == nil { + return nil, fmt.Errorf("missing vulnerabilities in Oversecured report: %w", ErrInvalidMaterialType) + } + + // The findings must be an array, or null for a clean scan — rejecting a clean + // scan would break pipelines whose app simply has no findings, the common + // case in CI. The unmarshal above already validated the whole document, so + // the first byte settles the shape without parsing the findings a second + // time: they are decompiled sources and DAST heap snapshots, nearly all of a + // multi-MB export. Valid JSON leaves only one value starting with 'n'. + if v := bytes.TrimLeft(report.Vulnerabilities, " \t\r\n"); len(v) == 0 || (v[0] != '[' && v[0] != 'n') { + return nil, fmt.Errorf("vulnerabilities is not an array in Oversecured report: %w", ErrInvalidMaterialType) + } + + m, err := uploadAndCraft(ctx, i.input, i.backend, filePath, i.logger) + if err != nil { + return nil, err + } + + i.injectAnnotations(m) + + return m, nil +} + +func (i *OversecuredCrafter) injectAnnotations(m *api.Attestation_Material) { + if m.Annotations == nil { + m.Annotations = make(map[string]string) + } + m.Annotations[AnnotationToolNameKey] = "oversecured" + + // No tool version and no scan.types annotation: the export carries neither a + // scanner version (header.scan.version is the *app* version) nor the + // per-finding hasSast/hasDast flags that would say which engines produced the + // findings. Those flags exist only on the findings-list and single-finding API + // responses. Nothing in the export states which engines ran either, so a DAST + // scan that found nothing is indistinguishable from one that never ran — + // asserting a scan type here would be a guess on a compliance signal. +} diff --git a/pkg/attestation/crafter/materials/oversecured_test.go b/pkg/attestation/crafter/materials/oversecured_test.go new file mode 100644 index 000000000..8dce9c46e --- /dev/null +++ b/pkg/attestation/crafter/materials/oversecured_test.go @@ -0,0 +1,205 @@ +// +// Copyright 2026 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//nolint:dupl +package materials_test + +import ( + "context" + "testing" + + contractAPI "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" + "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/materials" + "github.com/chainloop-dev/chainloop/pkg/casclient" + mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestNewOversecuredCrafter(t *testing.T) { + testCases := []struct { + name string + input *contractAPI.CraftingSchema_Material + wantErr bool + }{ + { + name: "happy path", + input: &contractAPI.CraftingSchema_Material{ + Type: contractAPI.CraftingSchema_Material_OVERSECURED_JSON, + }, + }, + { + name: "wrong type", + input: &contractAPI.CraftingSchema_Material{ + Type: contractAPI.CraftingSchema_Material_CONTAINER_IMAGE, + }, + wantErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := materials.NewOversecuredCrafter(tc.input, nil, nil) + if tc.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + }) + } +} + +func TestOversecuredCrafter_Craft(t *testing.T) { + testCases := []struct { + name string + filePath string + wantErr string + annotations map[string]string + // absentAnnotations lists annotation keys that must NOT be set. The export + // carries no per-finding hasSast/hasDast flags and no scanner version, so + // neither scan.types nor tool.version can be derived from it. + absentAnnotations []string + }{ + { + name: "invalid path", + filePath: "./testdata/non-existing.json", + wantErr: "no such file or directory", + }, + { + name: "empty file", + filePath: "./testdata/empty.txt", + wantErr: "invalid Oversecured JSON report", + }, + { + name: "wrong content", + filePath: "./testdata/sbom-spdx.json", + wantErr: "missing scan id", + }, + { + // The real vendor response most likely to be mistaken for an export. + name: "paginated findings list, not an export", + filePath: "./testdata/oversecured-findings-list.json", + wantErr: "missing scan id", + }, + { + name: "header without vulnerabilities", + filePath: "./testdata/oversecured-no-vulnerabilities.json", + wantErr: "missing vulnerabilities", + }, + { + name: "vulnerabilities is not an array", + filePath: "./testdata/oversecured-vulnerabilities-not-array.json", + wantErr: "vulnerabilities is not an array", + }, + { + // Every Oversecured export names the platform it scanned, so its + // absence means this is not one of them. + name: "no app platform", + filePath: "./testdata/oversecured-no-platform.json", + wantErr: "missing app platform", + }, + { + // A platform outside the android/ios the vendor scans today is logged + // but accepted: pinning the vocabulary would make a new vendor platform + // break every pipeline until Chainloop ships a release. + name: "unfamiliar app platform is accepted", + filePath: "./testdata/oversecured-unfamiliar-platform.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "oversecured", + }, + }, + { + name: "clean scan (empty vulnerabilities array)", + filePath: "./testdata/oversecured-clean.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "oversecured", + }, + absentAnnotations: []string{ + "chainloop.material.scan.types", + "chainloop.material.tool.version", + }, + }, + { + name: "clean scan (null vulnerabilities)", + filePath: "./testdata/oversecured-null-vulnerabilities.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "oversecured", + }, + absentAnnotations: []string{ + "chainloop.material.scan.types", + "chainloop.material.tool.version", + }, + }, + { + // Trimmed real export (`oversecured report --app + // --format json`). Only the envelope is validated, so the findings it + // keeps are there to pin the shape, not to add coverage: one SAST + // finding with an AI write-up, one DAST finding carrying runtime stack + // traces and no code[], and one plain low-severity one. + name: "real export", + filePath: "./testdata/oversecured.json", + annotations: map[string]string{ + "chainloop.material.tool.name": "oversecured", + }, + absentAnnotations: []string{ + "chainloop.material.scan.types", + "chainloop.material.tool.version", + }, + }, + } + + schema := &contractAPI.CraftingSchema_Material{ + Name: "test", + Type: contractAPI.CraftingSchema_Material_OVERSECURED_JSON, + } + + l := zerolog.Nop() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Mock uploader + uploader := mUploader.NewUploader(t) + if tc.wantErr == "" { + uploader.On("Upload", context.TODO(), mock.Anything, mock.Anything, mock.Anything). + Return(&casclient.UpDownStatus{}, nil) + } + + backend := &casclient.CASBackend{Uploader: uploader} + crafter, err := materials.NewOversecuredCrafter(schema, backend, &l) + require.NoError(t, err) + + got, err := crafter.Craft(context.TODO(), tc.filePath) + if tc.wantErr != "" { + assert.ErrorContains(t, err, tc.wantErr) + return + } + + require.NoError(t, err) + assert.Equal(t, contractAPI.CraftingSchema_Material_OVERSECURED_JSON.String(), got.MaterialType.String()) + assert.True(t, got.UploadedToCas) + + for k, v := range tc.annotations { + assert.Equal(t, v, got.Annotations[k]) + } + + for _, k := range tc.absentAnnotations { + _, ok := got.Annotations[k] + assert.False(t, ok, "annotation %q must not be set", k) + } + }) + } +} diff --git a/pkg/attestation/crafter/materials/testdata/oversecured-clean.json b/pkg/attestation/crafter/materials/testdata/oversecured-clean.json new file mode 100644 index 000000000..28e45ee76 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/oversecured-clean.json @@ -0,0 +1,33 @@ +{ + "header": { + "scan": { + "id": "e62f4c1b-8ca7-4c7a-8ca8-7b7fd33a6b14", + "fileName": "sampleapp-1.0.0.apk", + "version": "3.4.0", + "status": "completed", + "startedAt": null, + "completedAt": "2026-08-26T10:32:08.054Z", + "enrichmentStatus": "completed", + "vulnerabilityCount": 0 + }, + "app": { + "id": "6c69e813-32f5-49f1-bf53-568d63fc309d", + "name": "SampleApp", + "packageName": "com.oversecured.sample", + "platform": "ios" + }, + "severityCounts": {}, + "ai": { + "technicalSummary": "", + "nonTechnicalSummary": "" + }, + "compliance": { + "totalFindings": 0, + "requirements": [] + }, + "singleVulnerability": false, + "subjectTitle": null, + "generatedAt": "2026-08-26T10:33:35.816Z" + }, + "vulnerabilities": [] +} diff --git a/pkg/attestation/crafter/materials/testdata/oversecured-findings-list.json b/pkg/attestation/crafter/materials/testdata/oversecured-findings-list.json new file mode 100644 index 000000000..2eedb92f5 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/oversecured-findings-list.json @@ -0,0 +1,25 @@ +{ + "items": [ + { + "id": "b1c2d3e4-1111-4222-8333-444455556666", + "folderId": null, + "category": { + "id": "834b67a2-f80e-4e88-a164-ea3559abfbe2", + "severity": "high", + "descriptionTitle": "Hardcoded cryptographic key" + }, + "chainType": null, + "codeOrigin": "custom", + "thirdPartyLabel": null, + "hasSast": true, + "hasDast": false, + "hasLlmDescription": false, + "sources": [], + "sink": null, + "isLocked": false + } + ], + "total": 28, + "page": 1, + "pageSize": 20 +} diff --git a/pkg/attestation/crafter/materials/testdata/oversecured-no-platform.json b/pkg/attestation/crafter/materials/testdata/oversecured-no-platform.json new file mode 100644 index 000000000..e30945d60 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/oversecured-no-platform.json @@ -0,0 +1,19 @@ +{ + "header": { + "scan": { + "id": "e62f4c1b-8ca7-4c7a-8ca8-7b7fd33a6b14", + "fileName": "sampleapp-1.0.0.apk", + "version": "3.4.0", + "status": "completed", + "vulnerabilityCount": 0 + }, + "app": { + "id": "6c69e813-32f5-49f1-bf53-568d63fc309d", + "name": "SampleApp", + "packageName": "com.oversecured.sample" + }, + "severityCounts": {}, + "generatedAt": "2026-08-26T10:33:35.816Z" + }, + "vulnerabilities": [] +} diff --git a/pkg/attestation/crafter/materials/testdata/oversecured-no-vulnerabilities.json b/pkg/attestation/crafter/materials/testdata/oversecured-no-vulnerabilities.json new file mode 100644 index 000000000..6b37088b4 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/oversecured-no-vulnerabilities.json @@ -0,0 +1,24 @@ +{ + "header": { + "scan": { + "id": "e62f4c1b-8ca7-4c7a-8ca8-7b7fd33a6b14", + "fileName": "sampleapp-1.0.0.apk", + "version": "3.4.0", + "status": "completed", + "enrichmentStatus": "completed", + "vulnerabilityCount": 28 + }, + "app": { + "id": "6c69e813-32f5-49f1-bf53-568d63fc309d", + "name": "SampleApp", + "packageName": "com.oversecured.sample", + "platform": "android" + }, + "severityCounts": { + "high": 6, + "low": 9, + "medium": 13 + }, + "generatedAt": "2026-08-26T10:33:35.816Z" + } +} diff --git a/pkg/attestation/crafter/materials/testdata/oversecured-null-vulnerabilities.json b/pkg/attestation/crafter/materials/testdata/oversecured-null-vulnerabilities.json new file mode 100644 index 000000000..8ac134835 --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/oversecured-null-vulnerabilities.json @@ -0,0 +1,29 @@ +{ + "header": { + "scan": { + "id": "e62f4c1b-8ca7-4c7a-8ca8-7b7fd33a6b14", + "fileName": "sampleapp-1.0.0.apk", + "version": "3.4.0", + "status": "completed", + "startedAt": null, + "completedAt": "2026-08-26T10:32:08.054Z", + "enrichmentStatus": "completed", + "vulnerabilityCount": 0 + }, + "app": { + "id": "6c69e813-32f5-49f1-bf53-568d63fc309d", + "name": "SampleApp", + "packageName": "com.oversecured.sample", + "platform": "android" + }, + "severityCounts": {}, + "compliance": { + "totalFindings": 0, + "requirements": [] + }, + "singleVulnerability": false, + "subjectTitle": null, + "generatedAt": "2026-08-26T10:33:35.816Z" + }, + "vulnerabilities": null +} diff --git a/pkg/attestation/crafter/materials/testdata/oversecured-unfamiliar-platform.json b/pkg/attestation/crafter/materials/testdata/oversecured-unfamiliar-platform.json new file mode 100644 index 000000000..41535d18e --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/oversecured-unfamiliar-platform.json @@ -0,0 +1,21 @@ +{ + "header": { + "scan": { + "id": "e62f4c1b-8ca7-4c7a-8ca8-7b7fd33a6b14", + "fileName": "sampleapp-1.0.0.apk", + "version": "3.4.0", + "status": "completed", + "enrichmentStatus": "completed", + "vulnerabilityCount": 0 + }, + "app": { + "id": "6c69e813-32f5-49f1-bf53-568d63fc309d", + "name": "SampleApp", + "packageName": "com.oversecured.sample", + "platform": "web" + }, + "severityCounts": {}, + "generatedAt": "2026-08-26T10:33:35.816Z" + }, + "vulnerabilities": [] +} diff --git a/pkg/attestation/crafter/materials/testdata/oversecured-vulnerabilities-not-array.json b/pkg/attestation/crafter/materials/testdata/oversecured-vulnerabilities-not-array.json new file mode 100644 index 000000000..3cb92263e --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/oversecured-vulnerabilities-not-array.json @@ -0,0 +1,22 @@ +{ + "header": { + "scan": { + "id": "e62f4c1b-8ca7-4c7a-8ca8-7b7fd33a6b14", + "fileName": "sampleapp-1.0.0.apk", + "version": "3.4.0", + "status": "completed", + "vulnerabilityCount": 1 + }, + "app": { + "id": "6c69e813-32f5-49f1-bf53-568d63fc309d", + "name": "SampleApp", + "packageName": "com.oversecured.sample", + "platform": "android" + }, + "severityCounts": {}, + "generatedAt": "2026-08-26T10:33:35.816Z" + }, + "vulnerabilities": { + "items": [] + } +} diff --git a/pkg/attestation/crafter/materials/testdata/oversecured.json b/pkg/attestation/crafter/materials/testdata/oversecured.json new file mode 100644 index 000000000..555ea32df --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/oversecured.json @@ -0,0 +1,384 @@ +{ + "header": { + "scan": { + "id": "e62f4c1b-8ca7-4c7a-8ca8-7b7fd33a6b14", + "fileName": "sampleapp-1.0.0.apk", + "version": "3.4.0", + "status": "completed", + "startedAt": null, + "completedAt": "2026-08-26T10:32:08.054Z", + "enrichmentStatus": "completed", + "vulnerabilityCount": 3 + }, + "app": { + "id": "6c69e813-32f5-49f1-bf53-568d63fc309d", + "name": "SampleApp", + "packageName": "com.oversecured.sample", + "platform": "android" + }, + "severityCounts": { + "high": 1, + "medium": 1, + "low": 1 + }, + "ai": { + "technicalSummary": "The scan found a mix of directly exploitable and chain-only weaknesses. The most important path combines attacker-controlled file writing with dynamic code loading, while the individual findings also demonstrate source-to-sink data flow across SAST and DAST results.", + "nonTechnicalSummary": "The application contains security issues that can be exploited individually and others that become dangerous only when combined. The report shows how several lower-level weaknesses can form a more serious attack path." + }, + "compliance": { + "totalFindings": 0, + "requirements": [] + }, + "singleVulnerability": false, + "subjectTitle": null, + "generatedAt": "2026-08-26T10:33:35.816Z" + }, + "vulnerabilities": [ + { + "id": "2F65F30A-F06F-4CD3-B3C6-9B72DAD151A9", + "hash": "3b64b44322b8c23727728541dc80d8907b18c80624caa957c9d4a1024af68114", + "folderId": null, + "folder": null, + "falsePositive": false, + "category": { + "id": "f3bd3a2b-95ae-455e-917b-bc19908dc520", + "severity": "high", + "descriptionTitle": "Implicit broadcast intent discloses sensitive data", + "descriptionText": "An implicit intent is used to broadcast data without a permission with the \"signature\" protection level. The use of implicit intents allows any third-party application installed on the same device to intercept these intents by registering an appropriate broadcast receiver, which can lead to the disclosure of sensitive information contained in these intents.", + "descriptionRemediation": "The developer should always use explicit intents when broadcasting them. If this is not possible, the developer should protect the broadcast with a permission whose \"protection-level\" is set to \"signature\".", + "platform": "android" + }, + "code": [ + { + "file": "dev/fluttercommunity/plus/androidintent/IntentSender.java", + "source_type": "java", + "lines": [ + { + "line_number": 29, + "is_matched": false, + "content": " send(android.content.Intent.createChooser(intent, str));", + "descriptions": [] + }, + { + "line_number": 30, + "is_matched": false, + "content": " }", + "descriptions": [] + }, + { + "line_number": 31, + "is_matched": false, + "content": "", + "descriptions": [] + }, + { + "line_number": 32, + "is_matched": true, + "content": " public void sendBroadcast(android.content.Intent intent) {", + "descriptions": [] + } + ] + }, + { + "file": "dev/fluttercommunity/plus/androidintent/MethodCallHandlerImpl.java", + "source_type": "java", + "lines": [ + { + "line_number": 62, + "is_matched": false, + "content": " this.sender.launchChooser(intentBuildIntent, (java.lang.String) methodCall.argument(\"chooserTitle\"));", + "descriptions": [] + }, + { + "line_number": 63, + "is_matched": false, + "content": " result.success(null);", + "descriptions": [] + }, + { + "line_number": 64, + "is_matched": false, + "content": " } else if (\"sendBroadcast\".equalsIgnoreCase(methodCall.method)) {", + "descriptions": [] + }, + { + "line_number": 65, + "is_matched": true, + "content": " this.sender.sendBroadcast(intentBuildIntent);", + "descriptions": [] + } + ] + }, + { + "file": "dev/fluttercommunity/plus/androidintent/MethodCallHandlerImpl.java", + "source_type": "java", + "lines": [ + { + "line_number": 31, + "is_matched": false, + "content": "", + "descriptions": [] + }, + { + "line_number": 32, + "is_matched": false, + "content": " @Override", + "descriptions": [] + }, + { + "line_number": 33, + "is_matched": false, + "content": " public void onMethodCall(io.flutter.plugin.common.MethodCall methodCall, io.flutter.plugin.common.MethodChannel.Result result) {", + "descriptions": [] + }, + { + "line_number": 34, + "is_matched": true, + "content": " java.lang.String strConvertAction = convertAction((java.lang.String) methodCall.argument(\"action\"));", + "descriptions": [] + } + ] + } + ], + "stackTraces": [], + "pocs": [], + "attachments": [], + "llmDescription": { + "summary": "The app uses an implicit broadcast intent with a hardcoded action string to transmit sensitive login credentials, exposing them to any app on the device that registers a receiver for the same action, bypassing Android's intended intent security model.", + "businessImpact": "Attackers can steal user login credentials, leading to account compromise and loss of user trust. This exposes the organization to regulatory penalties under GDPR or CCPA for mishandling PII and may trigger reputational damage and support costs from breached users.", + "technicalImpact": "An attacker can intercept sensitive login data including usernames, passwords, or tokens by registering a broadcast receiver for the unprotected intent action. This enables credential harvesting, session hijacking, or lateral movement within compromised devices.", + "technicalDetailsProse": "The vulnerability resides in MainActivity.java at line 50–52, where an implicit broadcast is sent with unencrypted login data via an unprotected intent action. The data crosses the trust boundary from the app’s private context to the global broadcast namespace, with no permission enforcement or explicit recipient targeting.", + "howToFix": [ + { + "title": "Replace implicit broadcast with explicit intent", + "details": [ + "Use setComponent() to target a specific receiver within the app's package.", + "Avoid hardcoded action strings; use constants defined in a secure utility class." + ] + }, + { + "title": "Use LocalBroadcastManager or modern alternatives", + "details": [ + "Replace sendBroadcast() with LocalBroadcastManager.getInstance(this).sendBroadcastSync(i) for intra-app communication.", + "For Android 8+, use JobScheduler or WorkManager for background data transfer." + ] + }, + { + "title": "Encrypt and minimize data in transit", + "details": [ + "Do not send raw credentials; send authentication tokens instead.", + "Use Android Keystore to encrypt sensitive payloads before transmission." + ] + } + ], + "technicalDetails": { + "attackVector": "network", + "attackLocality": "local", + "privilegesRequired": "none", + "userInteraction": "none", + "scope": "unchanged", + "confidentialityImpact": "high", + "integrityImpact": "none", + "availabilityImpact": "none" + }, + "exploitability": { + "difficulty": "trivial", + "requiredKnowledge": "Android broadcast intent mechanics and basic ADB usage" + }, + "remediation": "Replace implicit broadcast with explicit intent or LocalBroadcastManager and encrypt sensitive data.", + "tags": [ + "Android", + "BroadcastIntent", + "CredentialExposure", + "SecurityMisconfiguration", + "HighSeverity", + "LocalAttack", + "DataLeak", + "ImplicitIntent" + ] + } + }, + { + "id": "02D40C3D-D2B6-4587-AE09-480B1FBCBAC2", + "hash": "29672c1ef669b7fd82293f4937c09f82ed8df2083bf3b2fdd9a8a6bb8999d34c", + "folderId": null, + "folder": null, + "falsePositive": false, + "category": { + "id": "65a6d3d3-8586-4e6c-ab85-4a73243e459a", + "severity": "medium", + "descriptionTitle": "Content injection", + "descriptionText": "An attacker has the ability to set arbitrary text within the application, which can lead to phishing attacks: the user will be inclined to trust messages from the application.", + "descriptionRemediation": "The developer should set attacker-controlled text in fields such as EditText rather than TextView, or restrict the ability to add such content at all. The user should be sure that it is not an application message.", + "platform": "android" + }, + "code": [], + "stackTraces": [ + { + "thread_name": "main", + "frames": [ + { + "element": { + "className": "androidx.appcompat.app.AppCompatDelegateImpl", + "methodName": "setTitle", + "fileName": "AppCompatDelegateImpl.java", + "lineNumber": 1184 + }, + "variables": [ + { + "is_field": true, + "name": "this", + "class_name": "androidx.appcompat.app.AppCompatDelegateImpl", + "value_id": "E0BC23F7-51AF-4DF6-8B79-42362231CDF7" + }, + { + "is_field": false, + "name": "p0", + "class_name": "java.lang.CharSequence", + "value_id": "95E7F345-6E10-4F21-B9F2-628F69449C58" + } + ] + }, + { + "element": { + "className": "androidx.appcompat.app.AppCompatActivity", + "methodName": "onTitleChanged", + "fileName": "AppCompatActivity.java", + "lineNumber": 277 + }, + "variables": [ + { + "is_field": true, + "name": "this", + "class_name": "com.example.mobile.scanner.QrScannerActivity", + "value_id": "815884C3-ACD4-44F7-8299-F2E4314E61B5" + }, + { + "is_field": false, + "name": "p0", + "class_name": "java.lang.CharSequence", + "value_id": "95E7F345-6E10-4F21-B9F2-628F69449C58" + } + ] + } + ], + "values": [ + { + "id": "815884C3-ACD4-44F7-8299-F2E4314E61B5", + "class_name": "com.example.mobile.scanner.QrScannerActivity", + "value_type": "object", + "value_content": { + "identity_hash_code": 11978464, + "display_value": [ + { + "is_field": true, + "name": "a", + "class_name": "com.example.mobile.scanner.a", + "value_id": "1B119565-23F1-4E43-819F-8F02C4A53134" + }, + { + "is_field": true, + "name": "a", + "class_name": "boolean", + "value_id": "C12D1596-3B66-431D-82DB-551C545BD493" + } + ] + } + }, + { + "id": "95E7F345-6E10-4F21-B9F2-628F69449C58", + "class_name": "java.lang.String", + "value_type": "string", + "value_content": { + "identity_hash_code": 322027666, + "display_value": "test1337" + } + } + ], + "highlights": [ + { + "frame_index": 1, + "path": [ + { + "type": "field", + "name": "p0" + } + ] + } + ] + } + ], + "pocs": [ + { + "kind": "shell", + "code": [ + { + "source_type": "shell", + "lines": [ + { + "line_number": 1, + "is_matched": false, + "content": "adb shell am start -n com.oversecured.sample.nightly.staging/com.example.mobile.scanner.QrScannerActivity -d snitch://1979826049 -e android.intent.extra.REFERRER_NAME snitch931318559 -e com.android.vending.referral_url snitch770731296 -e google.message_id snitch1647502547 -e message_id snitch1486495031 --ez openFlight false -e title test1337", + "descriptions": [] + } + ] + } + ] + } + ], + "attachments": [], + "llmDescription": null + }, + { + "id": "CF41D6F6-B1D1-4364-B578-04B7038CB347", + "hash": "a6d8893a0c5d841c7437f5c464503dcd88af611ba8b8c55e63c85c803fa011dc", + "folderId": null, + "folder": null, + "falsePositive": false, + "category": { + "id": "8d309f07-3732-4238-8b37-43f38221029c", + "severity": "low", + "descriptionTitle": "File access is enabled for WebView", + "descriptionText": "Access to data using file:// links is not disabled or explicitly enabled for the WebView. The setAllowFileAccess() method allows JavaScript to access local files in the context of the running application. By performing a man-in-the-middle attack or by manipulating a server response, an attacker could gain access to the application's files, such as preferences, local databases, cache, and so on. This could lead to the disclosure of confidential data such as authentication tokens and passwords.", + "descriptionRemediation": "If the application does not use this functionality, the developer should disable access to file:// scheme URLs by calling webView.getSettings().setAllowFileAccess(false).", + "platform": "android" + }, + "code": [ + { + "file": "com/example/mobile/webview/webview/AppWebView.java", + "source_type": "java", + "lines": [ + { + "line_number": 266, + "is_matched": false, + "content": " android.webkit.CookieManager.getInstance().setAcceptThirdPartyCookies(this, true);", + "descriptions": [] + }, + { + "line_number": 267, + "is_matched": false, + "content": " com.example.mobile.webview.webview.WebViewRenderPolicy.disableAccessibility(this.context);", + "descriptions": [] + }, + { + "line_number": 268, + "is_matched": false, + "content": " android.webkit.WebSettings settings = getSettings();", + "descriptions": [] + }, + { + "line_number": 269, + "is_matched": true, + "content": " settings.setAllowFileAccess(true);", + "descriptions": [] + } + ] + } + ], + "stackTraces": [], + "pocs": [], + "attachments": [], + "llmDescription": null + } + ] +}