From cf6220c7dbedc2b169fa59de013da0072c8cf566 Mon Sep 17 00:00:00 2001 From: Matthew McQuaid Date: Wed, 4 Dec 2024 15:56:06 -0800 Subject: [PATCH] update ci_scan_complete_response --- semgrep_output_v1.atd | 19 +++ semgrep_output_v1.jsonschema | 14 ++- semgrep_output_v1.proto | 8 +- semgrep_output_v1.py | 34 ++++++ semgrep_output_v1.ts | 22 ++++ semgrep_output_v1_j.ml | 220 ++++++++++++++++++++++++++++++++++- semgrep_output_v1_j.mli | 28 ++++- 7 files changed, 341 insertions(+), 4 deletions(-) diff --git a/semgrep_output_v1.atd b/semgrep_output_v1.atd index 6d1d40df..4377e036 100644 --- a/semgrep_output_v1.atd +++ b/semgrep_output_v1.atd @@ -1617,12 +1617,31 @@ type parsing_stats = { num_bytes: int; } +(* +Policy/automation related data the app wants the CLI to know about +certain findings. Right now this is only used for the app to cause +a CI scan to block, so 'kind' can only ever be "block". The list +of match_based_ids are the MIDs of the findings that the action +relates to. + +This a record with a 'kind' field instead +instead of a sum type because ATD doesn't have convenient support for +adding new constructors to sum types without breaking backwards compatibility. +Right now we only have one kind of action ("block"), but in the future we may add more. +e.g. we could inform the CLI that a certain list of findings triggered a PR comment +*) +type app_finding_action = { + kind : string; + match_based_ids : string list; +} + (* Response by the backend to the CLI to the POST /complete *) type ci_scan_complete_response = { success: bool; ~app_block_override: bool; (* only when app_block_override is true *) ~app_block_reason: string; + ~app_finding_actions : app_finding_action list; } (* ----------------------------- *) diff --git a/semgrep_output_v1.jsonschema b/semgrep_output_v1.jsonschema index 08169527..4597ef80 100644 --- a/semgrep_output_v1.jsonschema +++ b/semgrep_output_v1.jsonschema @@ -1272,13 +1272,25 @@ "num_bytes": { "type": "integer" } } }, + "app_finding_action": { + "type": "object", + "required": [ "kind", "match_based_ids" ], + "properties": { + "kind": { "type": "string" }, + "match_based_ids": { "type": "array", "items": { "type": "string" } } + } + }, "ci_scan_complete_response": { "type": "object", "required": [ "success" ], "properties": { "success": { "type": "boolean" }, "app_block_override": { "type": "boolean" }, - "app_block_reason": { "type": "string" } + "app_block_reason": { "type": "string" }, + "app_finding_actions": { + "type": "array", + "items": { "$ref": "#/definitions/app_finding_action" } + } } }, "ci_scan_dependencies": { diff --git a/semgrep_output_v1.proto b/semgrep_output_v1.proto index e41c61fc..ddc4aafb 100644 --- a/semgrep_output_v1.proto +++ b/semgrep_output_v1.proto @@ -1,6 +1,6 @@ // Generated by jsonschema2protobuf. DO NOT EDIT! // Source file: semgrep_output_v1.jsonschema -// Source file sha256 digest: d9baa6fa076fda6a6d6524f2169a1649bb55d75db50fe2ed88b98a517f2acfa2 +// Source file sha256 digest: 008531f03f4e1c824b6135bba8fe2285dc3c359d81113f1658e775aa664fb51f syntax = "proto3"; @@ -515,10 +515,16 @@ message ParsingStats { int64 num_bytes = 49709845; } +message AppFindingAction { + string kind = 3088172; + repeated string match_based_ids = 533667648; +} + message CiScanCompleteResponse { bool success = 224900935; bool app_block_override = 45266970; string app_block_reason = 276965897; + repeated AppFindingAction app_finding_actions = 91104740; } message DependencyParserError { diff --git a/semgrep_output_v1.py b/semgrep_output_v1.py index 87c7834f..8acebad1 100644 --- a/semgrep_output_v1.py +++ b/semgrep_output_v1.py @@ -9083,6 +9083,37 @@ def to_json_string(self, **kw: Any) -> str: return json.dumps(self.to_json(), **kw) +@dataclass +class AppFindingAction: + """Original type: app_finding_action = { ... }""" + + kind: str + match_based_ids: List[str] + + @classmethod + def from_json(cls, x: Any) -> 'AppFindingAction': + if isinstance(x, dict): + return cls( + kind=_atd_read_string(x['kind']) if 'kind' in x else _atd_missing_json_field('AppFindingAction', 'kind'), + match_based_ids=_atd_read_list(_atd_read_string)(x['match_based_ids']) if 'match_based_ids' in x else _atd_missing_json_field('AppFindingAction', 'match_based_ids'), + ) + else: + _atd_bad_json('AppFindingAction', x) + + def to_json(self) -> Any: + res: Dict[str, Any] = {} + res['kind'] = _atd_write_string(self.kind) + res['match_based_ids'] = _atd_write_list(_atd_write_string)(self.match_based_ids) + return res + + @classmethod + def from_json_string(cls, x: str) -> 'AppFindingAction': + return cls.from_json(json.loads(x)) + + def to_json_string(self, **kw: Any) -> str: + return json.dumps(self.to_json(), **kw) + + @dataclass class CiScanCompleteResponse: """Original type: ci_scan_complete_response = { ... }""" @@ -9090,6 +9121,7 @@ class CiScanCompleteResponse: success: bool app_block_override: bool = field(default_factory=lambda: False) app_block_reason: str = field(default_factory=lambda: "") + app_finding_actions: List[AppFindingAction] = field(default_factory=lambda: []) @classmethod def from_json(cls, x: Any) -> 'CiScanCompleteResponse': @@ -9098,6 +9130,7 @@ def from_json(cls, x: Any) -> 'CiScanCompleteResponse': success=_atd_read_bool(x['success']) if 'success' in x else _atd_missing_json_field('CiScanCompleteResponse', 'success'), app_block_override=_atd_read_bool(x['app_block_override']) if 'app_block_override' in x else False, app_block_reason=_atd_read_string(x['app_block_reason']) if 'app_block_reason' in x else "", + app_finding_actions=_atd_read_list(AppFindingAction.from_json)(x['app_finding_actions']) if 'app_finding_actions' in x else [], ) else: _atd_bad_json('CiScanCompleteResponse', x) @@ -9107,6 +9140,7 @@ def to_json(self) -> Any: res['success'] = _atd_write_bool(self.success) res['app_block_override'] = _atd_write_bool(self.app_block_override) res['app_block_reason'] = _atd_write_string(self.app_block_reason) + res['app_finding_actions'] = _atd_write_list((lambda x: x.to_json()))(self.app_finding_actions) return res @classmethod diff --git a/semgrep_output_v1.ts b/semgrep_output_v1.ts index 770d37e6..3a2de107 100644 --- a/semgrep_output_v1.ts +++ b/semgrep_output_v1.ts @@ -696,10 +696,16 @@ export type ParsingStats = { num_bytes: number /*int*/; } +export type AppFindingAction = { + kind: string; + match_based_ids: string[]; +} + export type CiScanCompleteResponse = { success: boolean; app_block_override: boolean; app_block_reason: string; + app_finding_actions: AppFindingAction[]; } export type CiScanDependencies = Map @@ -3108,11 +3114,26 @@ export function readParsingStats(x: any, context: any = x): ParsingStats { }; } +export function writeAppFindingAction(x: AppFindingAction, context: any = x): any { + return { + 'kind': _atd_write_required_field('AppFindingAction', 'kind', _atd_write_string, x.kind, x), + 'match_based_ids': _atd_write_required_field('AppFindingAction', 'match_based_ids', _atd_write_array(_atd_write_string), x.match_based_ids, x), + }; +} + +export function readAppFindingAction(x: any, context: any = x): AppFindingAction { + return { + kind: _atd_read_required_field('AppFindingAction', 'kind', _atd_read_string, x['kind'], x), + match_based_ids: _atd_read_required_field('AppFindingAction', 'match_based_ids', _atd_read_array(_atd_read_string), x['match_based_ids'], x), + }; +} + export function writeCiScanCompleteResponse(x: CiScanCompleteResponse, context: any = x): any { return { 'success': _atd_write_required_field('CiScanCompleteResponse', 'success', _atd_write_bool, x.success, x), 'app_block_override': _atd_write_field_with_default(_atd_write_bool, false, x.app_block_override, x), 'app_block_reason': _atd_write_field_with_default(_atd_write_string, "", x.app_block_reason, x), + 'app_finding_actions': _atd_write_field_with_default(_atd_write_array(writeAppFindingAction), [], x.app_finding_actions, x), }; } @@ -3121,6 +3142,7 @@ export function readCiScanCompleteResponse(x: any, context: any = x): CiScanComp success: _atd_read_required_field('CiScanCompleteResponse', 'success', _atd_read_bool, x['success'], x), app_block_override: _atd_read_field_with_default(_atd_read_bool, false, x['app_block_override'], x), app_block_reason: _atd_read_field_with_default(_atd_read_string, "", x['app_block_reason'], x), + app_finding_actions: _atd_read_field_with_default(_atd_read_array(readAppFindingAction), [], x['app_finding_actions'], x), }; } diff --git a/semgrep_output_v1_j.ml b/semgrep_output_v1_j.ml index b407995f..3937f7a5 100644 --- a/semgrep_output_v1_j.ml +++ b/semgrep_output_v1_j.ml @@ -877,11 +877,17 @@ type ci_scan_results_response = } [@@deriving show] +type app_finding_action = Semgrep_output_v1_t.app_finding_action = { + kind: string; + match_based_ids: string list +} + type ci_scan_complete_response = Semgrep_output_v1_t.ci_scan_complete_response = { success: bool; app_block_override: bool; - app_block_reason: string + app_block_reason: string; + app_finding_actions: app_finding_action list } [@@deriving show] @@ -34571,6 +34577,175 @@ let read_ci_scan_results_response = ( ) let ci_scan_results_response_of_string s = read_ci_scan_results_response (Yojson.Safe.init_lexer ()) (Lexing.from_string s) +let write_app_finding_action : _ -> app_finding_action -> _ = ( + fun ob (x : app_finding_action) -> + Buffer.add_char ob '{'; + let is_first = ref true in + if !is_first then + is_first := false + else + Buffer.add_char ob ','; + Buffer.add_string ob "\"kind\":"; + ( + Yojson.Safe.write_string + ) + ob x.kind; + if !is_first then + is_first := false + else + Buffer.add_char ob ','; + Buffer.add_string ob "\"match_based_ids\":"; + ( + write__string_list + ) + ob x.match_based_ids; + Buffer.add_char ob '}'; +) +let string_of_app_finding_action ?(len = 1024) x = + let ob = Buffer.create len in + write_app_finding_action ob x; + Buffer.contents ob +let read_app_finding_action = ( + fun p lb -> + Yojson.Safe.read_space p lb; + Yojson.Safe.read_lcurl p lb; + let field_kind = ref (None) in + let field_match_based_ids = ref (None) in + try + Yojson.Safe.read_space p lb; + Yojson.Safe.read_object_end lb; + Yojson.Safe.read_space p lb; + let f = + fun s pos len -> + if pos < 0 || len < 0 || pos + len > String.length s then + invalid_arg (Printf.sprintf "out-of-bounds substring position or length: string = %S, requested position = %i, requested length = %i" s pos len); + match len with + | 4 -> ( + if String.unsafe_get s pos = 'k' && String.unsafe_get s (pos+1) = 'i' && String.unsafe_get s (pos+2) = 'n' && String.unsafe_get s (pos+3) = 'd' then ( + 0 + ) + else ( + -1 + ) + ) + | 15 -> ( + if String.unsafe_get s pos = 'm' && String.unsafe_get s (pos+1) = 'a' && String.unsafe_get s (pos+2) = 't' && String.unsafe_get s (pos+3) = 'c' && String.unsafe_get s (pos+4) = 'h' && String.unsafe_get s (pos+5) = '_' && String.unsafe_get s (pos+6) = 'b' && String.unsafe_get s (pos+7) = 'a' && String.unsafe_get s (pos+8) = 's' && String.unsafe_get s (pos+9) = 'e' && String.unsafe_get s (pos+10) = 'd' && String.unsafe_get s (pos+11) = '_' && String.unsafe_get s (pos+12) = 'i' && String.unsafe_get s (pos+13) = 'd' && String.unsafe_get s (pos+14) = 's' then ( + 1 + ) + else ( + -1 + ) + ) + | _ -> ( + -1 + ) + in + let i = Yojson.Safe.map_ident p f lb in + Atdgen_runtime.Oj_run.read_until_field_value p lb; + ( + match i with + | 0 -> + field_kind := ( + Some ( + ( + Atdgen_runtime.Oj_run.read_string + ) p lb + ) + ); + | 1 -> + field_match_based_ids := ( + Some ( + ( + read__string_list + ) p lb + ) + ); + | _ -> ( + Yojson.Safe.skip_json p lb + ) + ); + while true do + Yojson.Safe.read_space p lb; + Yojson.Safe.read_object_sep p lb; + Yojson.Safe.read_space p lb; + let f = + fun s pos len -> + if pos < 0 || len < 0 || pos + len > String.length s then + invalid_arg (Printf.sprintf "out-of-bounds substring position or length: string = %S, requested position = %i, requested length = %i" s pos len); + match len with + | 4 -> ( + if String.unsafe_get s pos = 'k' && String.unsafe_get s (pos+1) = 'i' && String.unsafe_get s (pos+2) = 'n' && String.unsafe_get s (pos+3) = 'd' then ( + 0 + ) + else ( + -1 + ) + ) + | 15 -> ( + if String.unsafe_get s pos = 'm' && String.unsafe_get s (pos+1) = 'a' && String.unsafe_get s (pos+2) = 't' && String.unsafe_get s (pos+3) = 'c' && String.unsafe_get s (pos+4) = 'h' && String.unsafe_get s (pos+5) = '_' && String.unsafe_get s (pos+6) = 'b' && String.unsafe_get s (pos+7) = 'a' && String.unsafe_get s (pos+8) = 's' && String.unsafe_get s (pos+9) = 'e' && String.unsafe_get s (pos+10) = 'd' && String.unsafe_get s (pos+11) = '_' && String.unsafe_get s (pos+12) = 'i' && String.unsafe_get s (pos+13) = 'd' && String.unsafe_get s (pos+14) = 's' then ( + 1 + ) + else ( + -1 + ) + ) + | _ -> ( + -1 + ) + in + let i = Yojson.Safe.map_ident p f lb in + Atdgen_runtime.Oj_run.read_until_field_value p lb; + ( + match i with + | 0 -> + field_kind := ( + Some ( + ( + Atdgen_runtime.Oj_run.read_string + ) p lb + ) + ); + | 1 -> + field_match_based_ids := ( + Some ( + ( + read__string_list + ) p lb + ) + ); + | _ -> ( + Yojson.Safe.skip_json p lb + ) + ); + done; + assert false; + with Yojson.End_of_object -> ( + ( + { + kind = (match !field_kind with Some x -> x | None -> Atdgen_runtime.Oj_run.missing_field p "kind"); + match_based_ids = (match !field_match_based_ids with Some x -> x | None -> Atdgen_runtime.Oj_run.missing_field p "match_based_ids"); + } + : app_finding_action) + ) +) +let app_finding_action_of_string s = + read_app_finding_action (Yojson.Safe.init_lexer ()) (Lexing.from_string s) +let write__app_finding_action_list = ( + Atdgen_runtime.Oj_run.write_list ( + write_app_finding_action + ) +) +let string_of__app_finding_action_list ?(len = 1024) x = + let ob = Buffer.create len in + write__app_finding_action_list ob x; + Buffer.contents ob +let read__app_finding_action_list = ( + Atdgen_runtime.Oj_run.read_list ( + read_app_finding_action + ) +) +let _app_finding_action_list_of_string s = + read__app_finding_action_list (Yojson.Safe.init_lexer ()) (Lexing.from_string s) let write_ci_scan_complete_response : _ -> ci_scan_complete_response -> _ = ( fun ob (x : ci_scan_complete_response) -> Buffer.add_char ob '{'; @@ -34602,6 +34777,15 @@ let write_ci_scan_complete_response : _ -> ci_scan_complete_response -> _ = ( Yojson.Safe.write_string ) ob x.app_block_reason; + if !is_first then + is_first := false + else + Buffer.add_char ob ','; + Buffer.add_string ob "\"app_finding_actions\":"; + ( + write__app_finding_action_list + ) + ob x.app_finding_actions; Buffer.add_char ob '}'; ) let string_of_ci_scan_complete_response ?(len = 1024) x = @@ -34615,6 +34799,7 @@ let read_ci_scan_complete_response = ( let field_success = ref (None) in let field_app_block_override = ref (false) in let field_app_block_reason = ref ("") in + let field_app_finding_actions = ref ([]) in try Yojson.Safe.read_space p lb; Yojson.Safe.read_object_end lb; @@ -34648,6 +34833,14 @@ let read_ci_scan_complete_response = ( -1 ) ) + | 19 -> ( + if String.unsafe_get s pos = 'a' && String.unsafe_get s (pos+1) = 'p' && String.unsafe_get s (pos+2) = 'p' && String.unsafe_get s (pos+3) = '_' && String.unsafe_get s (pos+4) = 'f' && String.unsafe_get s (pos+5) = 'i' && String.unsafe_get s (pos+6) = 'n' && String.unsafe_get s (pos+7) = 'd' && String.unsafe_get s (pos+8) = 'i' && String.unsafe_get s (pos+9) = 'n' && String.unsafe_get s (pos+10) = 'g' && String.unsafe_get s (pos+11) = '_' && String.unsafe_get s (pos+12) = 'a' && String.unsafe_get s (pos+13) = 'c' && String.unsafe_get s (pos+14) = 't' && String.unsafe_get s (pos+15) = 'i' && String.unsafe_get s (pos+16) = 'o' && String.unsafe_get s (pos+17) = 'n' && String.unsafe_get s (pos+18) = 's' then ( + 3 + ) + else ( + -1 + ) + ) | _ -> ( -1 ) @@ -34680,6 +34873,14 @@ let read_ci_scan_complete_response = ( ) p lb ); ) + | 3 -> + if not (Yojson.Safe.read_null_if_possible p lb) then ( + field_app_finding_actions := ( + ( + read__app_finding_action_list + ) p lb + ); + ) | _ -> ( Yojson.Safe.skip_json p lb ) @@ -34717,6 +34918,14 @@ let read_ci_scan_complete_response = ( -1 ) ) + | 19 -> ( + if String.unsafe_get s pos = 'a' && String.unsafe_get s (pos+1) = 'p' && String.unsafe_get s (pos+2) = 'p' && String.unsafe_get s (pos+3) = '_' && String.unsafe_get s (pos+4) = 'f' && String.unsafe_get s (pos+5) = 'i' && String.unsafe_get s (pos+6) = 'n' && String.unsafe_get s (pos+7) = 'd' && String.unsafe_get s (pos+8) = 'i' && String.unsafe_get s (pos+9) = 'n' && String.unsafe_get s (pos+10) = 'g' && String.unsafe_get s (pos+11) = '_' && String.unsafe_get s (pos+12) = 'a' && String.unsafe_get s (pos+13) = 'c' && String.unsafe_get s (pos+14) = 't' && String.unsafe_get s (pos+15) = 'i' && String.unsafe_get s (pos+16) = 'o' && String.unsafe_get s (pos+17) = 'n' && String.unsafe_get s (pos+18) = 's' then ( + 3 + ) + else ( + -1 + ) + ) | _ -> ( -1 ) @@ -34749,6 +34958,14 @@ let read_ci_scan_complete_response = ( ) p lb ); ) + | 3 -> + if not (Yojson.Safe.read_null_if_possible p lb) then ( + field_app_finding_actions := ( + ( + read__app_finding_action_list + ) p lb + ); + ) | _ -> ( Yojson.Safe.skip_json p lb ) @@ -34761,6 +34978,7 @@ let read_ci_scan_complete_response = ( success = (match !field_success with Some x -> x | None -> Atdgen_runtime.Oj_run.missing_field p "success"); app_block_override = !field_app_block_override; app_block_reason = !field_app_block_reason; + app_finding_actions = !field_app_finding_actions; } : ci_scan_complete_response) ) diff --git a/semgrep_output_v1_j.mli b/semgrep_output_v1_j.mli index 05d3c553..ec940f5f 100644 --- a/semgrep_output_v1_j.mli +++ b/semgrep_output_v1_j.mli @@ -877,11 +877,17 @@ type ci_scan_results_response = } [@@deriving show] +type app_finding_action = Semgrep_output_v1_t.app_finding_action = { + kind: string; + match_based_ids: string list +} + type ci_scan_complete_response = Semgrep_output_v1_t.ci_scan_complete_response = { success: bool; app_block_override: bool; - app_block_reason: string + app_block_reason: string; + app_finding_actions: app_finding_action list } [@@deriving show] @@ -3645,6 +3651,26 @@ val ci_scan_results_response_of_string : string -> ci_scan_results_response (** Deserialize JSON data of type {!type:ci_scan_results_response}. *) +val write_app_finding_action : + Buffer.t -> app_finding_action -> unit + (** Output a JSON value of type {!type:app_finding_action}. *) + +val string_of_app_finding_action : + ?len:int -> app_finding_action -> string + (** Serialize a value of type {!type:app_finding_action} + into a JSON string. + @param len specifies the initial length + of the buffer used internally. + Default: 1024. *) + +val read_app_finding_action : + Yojson.Safe.lexer_state -> Lexing.lexbuf -> app_finding_action + (** Input JSON data of type {!type:app_finding_action}. *) + +val app_finding_action_of_string : + string -> app_finding_action + (** Deserialize JSON data of type {!type:app_finding_action}. *) + val write_ci_scan_complete_response : Buffer.t -> ci_scan_complete_response -> unit (** Output a JSON value of type {!type:ci_scan_complete_response}. *)