├── .dockerignore ├── .gitignore ├── Dockerfile ├── Dockerfile.build ├── LICENSE.txt ├── README.md ├── action.yaml ├── images ├── 42Crunch_GitHub-Alert_Details.png ├── 42Crunch_GitHub-REST_API_Code_Scanning_Alerts.png ├── 42Crunch_platform_Security_Audit.png └── link_to_detailed_report.jpg ├── package-lock.json ├── package.json ├── src ├── index.mts ├── sarif.mts └── upload.mts ├── tsconfig.json └── workflow.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 42crunch/github-api-security-audit:v4.2.0 2 | -------------------------------------------------------------------------------- /Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM node:22-alpine3.21 AS builder 2 | COPY . /build 3 | WORKDIR /build 4 | RUN npm ci && npm run build && npm prune --production 5 | 6 | FROM node:22-alpine3.21 7 | ENV NODE_ENV=production 8 | RUN apk add --no-cache tini 9 | 10 | RUN mkdir /action 11 | COPY --from=builder /build/package.json /action 12 | COPY --from=builder /build/node_modules /action/node_modules 13 | COPY --from=builder /build/dist /action/dist 14 | 15 | ENTRYPOINT [ "/sbin/tini", "--", "node", "/action/dist/index.mjs" ] 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Action: 42Crunch REST API Static Security Testing 2 | 3 | The REST API Static Security Testing action locates REST API contracts that follow the OpenAPI Specification (OAS, formerly known as Swagger) and runs thorough security checks on them. Both OAS v2 and v3.0.x are supported, in both JSON and YAML format. 4 | 5 | You can use this action in the following scenarios: 6 | 7 | - Add automatic static API security testing (SAST) task to your CI/CD workflows. 8 | - Perform these checks on pull request reviews and/or code merges. 9 | - Flag the located issues in GitHub's Security / Code Scanning Alerts. 10 | 11 | The action is powered by 42Crunch [API Security Audit](https://docs.42crunch.com/latest/content/concepts/api_contract_security_audit.htm). Security Audit performs a static analysis of the API definition that includes more than 300 checks on best practices and potential vulnerabilities related to authentication, authorization as well as data constraints. 12 | 13 | ## Discover APIs in your repositories 14 | 15 | By default, this action will: 16 | 17 | 1. Look for any `.json` and `.yaml` files in the repository. 18 | 2. Pick the files that use OpenAPI schema. 19 | 3. Perform security audit on the OpenAPI definitions. 20 | 21 | This way, you can locate any new or changed API contracts in the repository. 22 | 23 | You can fine-tune how the action behaves by specifying specific parts of the repository or filename masks to be included or excluded in the discovery of APIs. You can even disable discovery completely and instead list only specific API files to be checked and map them to your existing APIs in 42Crunch API Security Platform. You configure all these settings in the configuration file `42c-conf.yaml`. For advanced examples, see [here](https://github.com/42Crunch/resources/tree/master/cicd/42c-conf-examples). 24 | 25 | All discovered APIs are uploaded to an API collection in 42Crunch Platform. By default, the action uses the environment variables `GITHUB_REPOSITORY` and `GITHUB_REF` to name the repository and the branch/tag/PR name from where the API collection originated from. You can override the name using the `default-collection-name` action parameter. During the subsequent runs, the APIs in the collection are kept in sync with the changes in your repository. 26 | 27 | ## Use this action to block deployment of vulnerable APIs 28 | 29 | Add this action to your CI/CD workflows in GitHub and have it fail on API definitions that contain security issues. 30 | 31 | Security Audit gives each API contract an audit score from 0 to 100 reflecting the security surface of your APIs. You can use the `min-score` parameter of the GitHub Action to set the threshold for the audit score where the action fails (the default is `75`, if no other value is specified). This helps to catch APIs definitions of bad quality and address the issues as early as design time. 32 | 33 | More advanced failure conditions can be set in the configuration file `42c-conf.yaml`, such as audit score by category (security or data validation), severity level of issues, or even specific issues, specified by their issue ID. For advanced examples, see [here](https://github.com/42Crunch/resources/tree/master/cicd/42c-conf-examples). 34 | 35 | Additionally, the plugin enforces [security quality gates](https://docs.42crunch.com/latest/content/concepts/security_quality_gates.htm) defined at the platform level (default or tag-driven ones). Security quality gates enforce the application security requirements defined within the enterprise. 36 | 37 | ## Reading detailed actionable reports 38 | 39 | Each time the action runs, it includes a link to the detailed prioritized actionable report for each of your OpenAPI files: 40 | 41 | 42 | 43 | Follow the links to read the detailed report in 42Crunch Platform: 44 | 45 | 46 | 47 | ## Uploading 42Crunch alerts to GitHub code scanning 48 | 49 | You can also track the issues that the 42Crunch audit found directly in GitHub, on the **Security** tab under **Code scanning alerts**. 50 | 51 | To enable that, simply include `upload-to-code-scanning:true` to the parameters of the action in your GitHub workflow. 52 | 53 | 54 | 55 | Click any of the alerts to see its exact location in your code and to get the details of the vulnerability and the recommended remediation steps. 56 | 57 | 58 | 59 | ## Getting started 60 | 61 | This action uses 42Crunch API Security Audit service. Before using the action, you will need to have an account on the 42Crunch platform. If you are not a 42Crunch customer, you can request a free account from this page: https://42crunch.com/get-started/. 62 | 63 | Then, follow the steps described in the [documentation](https://docs.42crunch.com/latest/content/tasks/integrate_github_actions.htm) to create an API token for the action to authenticate to 42Crunch Platform, and save it as a secret in GitHub. 64 | 65 | ## Action parameters 66 | 67 | ### `api-token` 68 | 69 | **Required** The API token that the GitHub action uses to authenticate to 42Crunch Platform. Do not put your API token directly in the workflow file! Instead, create a Github secret in your repository settings and refer to it as shown in the example below. 70 | 71 | ### `min-score` 72 | 73 | The minimum audit score that OpenAPI files must reach, otherwise the action fails. Default is `75`. 74 | 75 | ### `upload-to-code-scanning` 76 | 77 | Upload the audit results to [Github Code Scanning](https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/about-code-scanning). Default is `false`. Note that the workflow must have specific permissions for this step to be successful. 78 | 79 | ```YAML 80 | ... 81 | jobs: 82 | run_42c_audit: 83 | permissions: 84 | contents: read # for actions/checkout to fetch code 85 | security-events: write # for results upload to Github Code Scanning 86 | ... 87 | ``` 88 | 89 | ### `ignore-failures` 90 | 91 | If set to `true`, forces to complete execution successfully even if the failures conditions (like min-score or SQG criteria) you have set are met. Default is `false`. 92 | 93 | This parameter can be useful if you want to detect SQG failures scenarios without enforcing them (i.e. give a grace period to development teams before you start breaking builds). 94 | 95 | ### `ignore-network-errors` 96 | 97 | If set to `true`, forces to complete execution successfully even if a network error has occurred (such as a failure to connect to 42Crunch Platform, etc.). Default is `false`. 98 | 99 | ### `skip-local-checks` 100 | 101 | If set to `true`, disables all failure conditions (like minimum score) set in the 42c-conf.yaml file and fails execution only if the criteria defined in SQGs are not met. Default is `false`. 102 | 103 | ### `platform-url` 104 | 105 | The URL where you access 42Crunch Platform. Default is `https://us.42crunch.cloud`. 106 | 107 | If you are an enterprise customer, enter the URL you use to access your production platform. 108 | 109 | ### `root-directory` 110 | 111 | The root directory that contains the `42c-conf.yaml` configuration file. If not specified, the current working directory for the plugin is used instead, which normally corresponds to the root of the checked out repository. 112 | 113 | ### `default-collection-name` 114 | 115 | The default collection name used when creating collections for discovered apis. If no name is given, a default name is created from the repository and branch/PR information. 116 | 117 | ### `log-level` 118 | 119 | Level of details in the logs, one of: `FATAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`. Default is `INFO`. 120 | 121 | ### `share-everyone` 122 | 123 | Automatically shares API collections created by the CI/CD task with everyone in your organization on 42Crunch Platform. Accepted values are: `OFF`, `READ_ONLY`, `READ_WRITE`. Default is `OFF`. Note that the identity the action runs under (the owner of the API token) must have the `Share with Everyone` permission, otherwise the task will fail with a 403 error. 124 | 125 | ### `json-report` 126 | 127 | Writes an audit execution report in JSON format to the specified file. An [execution report](https://docs.42crunch.com/latest/content/tasks/integrate_github_actions.htm#scrollNav-4) details the list of APIs that were created, updated and deleted. This is useful if you want to automatically consume the results of the audit execution in a subsequent pipeline step. By default, no report is written. 128 | 129 | ### `api-tags` 130 | 131 | The CI/CD task can automatically assign tags to newly created APIs. Tags are specified in the following format: `category1:name1 category2:name2`. This flag is *optional*. 132 | 133 | ### `sarif-report` 134 | 135 | Converts the audit raw JSON format to SARIF and save the results into the specified file. By default, no report is written. 136 | 137 | ### `audit-timeout` 138 | 139 | Sets the maximum timeout (in seconds) for the audit report. The task will fail if the result isn't ready within that interval. Default: `600` 140 | 141 | ## Prerequisites 142 | 143 | Create an API token on the 42Crunch Platform and copy its value into a [repository secret](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets) named `API_TOKEN`. 144 | 145 | ## Examples 146 | 147 | ### Single Step Example 148 | A typical new step in an existing workflow would look like this: 149 | 150 | ```yaml 151 | - name: 42crunch-static-api-testing 152 | uses: 42Crunch/api-security-audit-action@v4 153 | with: 154 | api-token: ${{ secrets.API_TOKEN }} 155 | default-collection-name: GitHub-MyRepo-${{ github.ref_name }} 156 | log-level: info 157 | json-report: audit-action-report-${{ github.run_id }} 158 | sarif-report: 42Crunch_AuditReport_${{ github.run_id }}.SARIF 159 | ``` 160 | ### Full Workflow Example 161 | 162 | A typical workflow which checks the contents of the repository, runs Security Audit on each of the OpenAPI files found in the project and saves the execution file as artifact would look like this: 163 | 164 | ```yaml 165 | name: "42crunch-audit-workflow" 166 | 167 | # follow standard Code Scanning triggers 168 | on: 169 | push: 170 | branches: [ "main" ] 171 | pull_request: 172 | # The branches below must be a subset of the branches above 173 | branches: [ "main" ] 174 | schedule: 175 | - cron: '19 9 * * 6' 176 | 177 | env: 178 | PLATFORM_URL: https://us.42crunch.cloud 179 | 180 | jobs: 181 | run_42c_audit: 182 | environment: QA 183 | permissions: 184 | contents: read # for actions/checkout to fetch code 185 | security-events: write # for results upload to Github Code Scanning 186 | runs-on: ubuntu-latest 187 | steps: 188 | - name: checkout repo 189 | uses: actions/checkout@v3 190 | - name: 42crunch-static-api-testing 191 | uses: 42Crunch/api-security-audit-action@v4 192 | with: 193 | api-token: ${{ secrets.API_TOKEN }} 194 | platform-url: ${{ env.PLATFORM_URL}} 195 | default-collection-name: GitHub-MyRepo-${{ github.ref_name }} 196 | # Upload results to Github code scanning 197 | upload-to-code-scanning: false 198 | log-level: info 199 | json-report: audit-action-report-${{ github.run_id }} 200 | sarif-report: 42Crunch_AuditReport_${{ github.run_id }}.SARIF 201 | - name: save-audit-report 202 | if: always() 203 | uses: actions/upload-artifact@v3 204 | with: 205 | name: auditaction-report-${{ github.run_id }} 206 | path: audit-action-report-${{ github.run_id }}.json 207 | if-no-files-found: error 208 | ``` 209 | 210 | ## Support 211 | 212 | The action is maintained by the 42Crunch Ecosystems team. If you run into an issue, or have a question not answered here, you can create a support ticket at [support.42crunch.com](https://support.42crunch.com/). 213 | 214 | When reporting an issue, do include: 215 | - The version of the GitHub action 216 | - Relevant logs and error messages 217 | - Steps to reproduce the issue 218 | -------------------------------------------------------------------------------- /action.yaml: -------------------------------------------------------------------------------- 1 | # action.yml 2 | name: "42Crunch REST API Static Security Testing" 3 | description: "The REST API Static Security Testing action adds an automatic static application security testing (SAST) to your workflows" 4 | inputs: 5 | api-token: 6 | description: "The API token to access 42Crunch Platform. Follow hese steps to configure API_TOKEN https://docs.42crunch.com/latest/content/tasks/integrate_github_actions.htm" 7 | required: false 8 | min-score: 9 | description: "Minimum score for OpenAPI files." 10 | required: false 11 | default: "75" 12 | platform-url: 13 | description: "42Crunch platform URL" 14 | required: false 15 | default: "https://us.42crunch.cloud" 16 | log-level: 17 | description: "Log level, one of: FATAL, ERROR, WARN, INFO, DEBUG" 18 | required: false 19 | default: "INFO" 20 | share-everyone: 21 | description: "Share new API collections with everyone, one of: OFF, READ_ONLY, READ_WRITE" 22 | required: false 23 | default: "OFF" 24 | github-token: 25 | description: "GitHub token for uploading results to Github Code Scanning" 26 | required: false 27 | default: ${{ github.token }} 28 | upload-to-code-scanning: 29 | description: "Upload results to Github Code Scanning" 30 | required: false 31 | default: "false" 32 | ignore-failures: 33 | description: "If set to 'true', disables all local failure conditions (like minimum score) and fails execution only if the criteria defined in SQGs are not met." 34 | required: false 35 | default: "false" 36 | root-directory: 37 | description: "Set the root directory for the plugin" 38 | required: false 39 | default-collection-name: 40 | description: "Change the default collection name" 41 | required: false 42 | json-report: 43 | description: "Writes Audit report in JSON format to a specified file" 44 | required: false 45 | api-tags: 46 | description: "List of tags to be set on the newly created APIs. Format \"category1:name1 category2:name2\", optional." 47 | required: false 48 | ignore-network-errors: 49 | description: "If set to 'true', forces to complete execution successfully even if a network error has occurred (such as a failure to connect to 4unch Platform, etc)." 50 | required: false 51 | default: "false" 52 | skip-local-checks: 53 | description: "If set to 'true', forces to complete execution successfully even if the failures conditions (like min-score or SQG criteria) you have set are met." 54 | required: false 55 | default: "false" 56 | sarif-report: 57 | description: "Writes SARIF report to a specified file" 58 | required: false 59 | default: "" 60 | audit-timeout: 61 | description: "Set the maximum timeout (in seconds) for the audit report. Fail if the result isn't ready within that interval." 62 | required: false 63 | branding: 64 | icon: "eye" 65 | color: "purple" 66 | runs: 67 | using: "docker" 68 | image: "Dockerfile" 69 | -------------------------------------------------------------------------------- /images/42Crunch_GitHub-Alert_Details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42Crunch/api-security-audit-action/5bde5dd83bdd935e504a63f20d61962f1ff8d31a/images/42Crunch_GitHub-Alert_Details.png -------------------------------------------------------------------------------- /images/42Crunch_GitHub-REST_API_Code_Scanning_Alerts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42Crunch/api-security-audit-action/5bde5dd83bdd935e504a63f20d61962f1ff8d31a/images/42Crunch_GitHub-REST_API_Code_Scanning_Alerts.png -------------------------------------------------------------------------------- /images/42Crunch_platform_Security_Audit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42Crunch/api-security-audit-action/5bde5dd83bdd935e504a63f20d61962f1ff8d31a/images/42Crunch_platform_Security_Audit.png -------------------------------------------------------------------------------- /images/link_to_detailed_report.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/42Crunch/api-security-audit-action/5bde5dd83bdd935e504a63f20d61962f1ff8d31a/images/link_to_detailed_report.jpg -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-security-audit-action", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "api-security-audit-action", 9 | "version": "0.0.0", 10 | "license": "AGPL-3.0-only", 11 | "dependencies": { 12 | "@actions/core": "^1.11.1", 13 | "@octokit/core": "^6.1.5", 14 | "@types/turndown": "^5.0.1", 15 | "@xliic/cicd-core-node": "7.1.0", 16 | "got": "^14.4.1", 17 | "turndown": "^7.1.2" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "^20.14.11", 21 | "typescript": "^5.5.0" 22 | } 23 | }, 24 | "node_modules/@actions/core": { 25 | "version": "1.11.1", 26 | "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", 27 | "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", 28 | "license": "MIT", 29 | "dependencies": { 30 | "@actions/exec": "^1.1.1", 31 | "@actions/http-client": "^2.0.1" 32 | } 33 | }, 34 | "node_modules/@actions/exec": { 35 | "version": "1.1.1", 36 | "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", 37 | "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", 38 | "license": "MIT", 39 | "dependencies": { 40 | "@actions/io": "^1.0.1" 41 | } 42 | }, 43 | "node_modules/@actions/http-client": { 44 | "version": "2.2.3", 45 | "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", 46 | "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", 47 | "license": "MIT", 48 | "dependencies": { 49 | "tunnel": "^0.0.6", 50 | "undici": "^5.25.4" 51 | } 52 | }, 53 | "node_modules/@actions/io": { 54 | "version": "1.1.3", 55 | "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", 56 | "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", 57 | "license": "MIT" 58 | }, 59 | "node_modules/@fastify/busboy": { 60 | "version": "2.1.1", 61 | "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", 62 | "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", 63 | "license": "MIT", 64 | "engines": { 65 | "node": ">=14" 66 | } 67 | }, 68 | "node_modules/@jsdevtools/ono": { 69 | "version": "7.1.3", 70 | "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", 71 | "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", 72 | "license": "MIT" 73 | }, 74 | "node_modules/@mixmark-io/domino": { 75 | "version": "2.2.0", 76 | "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", 77 | "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", 78 | "license": "BSD-2-Clause" 79 | }, 80 | "node_modules/@nodelib/fs.scandir": { 81 | "version": "2.1.5", 82 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 83 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 84 | "license": "MIT", 85 | "dependencies": { 86 | "@nodelib/fs.stat": "2.0.5", 87 | "run-parallel": "^1.1.9" 88 | }, 89 | "engines": { 90 | "node": ">= 8" 91 | } 92 | }, 93 | "node_modules/@nodelib/fs.stat": { 94 | "version": "2.0.5", 95 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 96 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 97 | "license": "MIT", 98 | "engines": { 99 | "node": ">= 8" 100 | } 101 | }, 102 | "node_modules/@nodelib/fs.walk": { 103 | "version": "1.2.8", 104 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 105 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 106 | "license": "MIT", 107 | "dependencies": { 108 | "@nodelib/fs.scandir": "2.1.5", 109 | "fastq": "^1.6.0" 110 | }, 111 | "engines": { 112 | "node": ">= 8" 113 | } 114 | }, 115 | "node_modules/@octokit/auth-token": { 116 | "version": "5.1.2", 117 | "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", 118 | "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", 119 | "license": "MIT", 120 | "engines": { 121 | "node": ">= 18" 122 | } 123 | }, 124 | "node_modules/@octokit/core": { 125 | "version": "6.1.5", 126 | "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz", 127 | "integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==", 128 | "license": "MIT", 129 | "dependencies": { 130 | "@octokit/auth-token": "^5.0.0", 131 | "@octokit/graphql": "^8.2.2", 132 | "@octokit/request": "^9.2.3", 133 | "@octokit/request-error": "^6.1.8", 134 | "@octokit/types": "^14.0.0", 135 | "before-after-hook": "^3.0.2", 136 | "universal-user-agent": "^7.0.0" 137 | }, 138 | "engines": { 139 | "node": ">= 18" 140 | } 141 | }, 142 | "node_modules/@octokit/endpoint": { 143 | "version": "10.1.4", 144 | "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", 145 | "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", 146 | "license": "MIT", 147 | "dependencies": { 148 | "@octokit/types": "^14.0.0", 149 | "universal-user-agent": "^7.0.2" 150 | }, 151 | "engines": { 152 | "node": ">= 18" 153 | } 154 | }, 155 | "node_modules/@octokit/graphql": { 156 | "version": "8.2.2", 157 | "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", 158 | "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", 159 | "license": "MIT", 160 | "dependencies": { 161 | "@octokit/request": "^9.2.3", 162 | "@octokit/types": "^14.0.0", 163 | "universal-user-agent": "^7.0.0" 164 | }, 165 | "engines": { 166 | "node": ">= 18" 167 | } 168 | }, 169 | "node_modules/@octokit/openapi-types": { 170 | "version": "25.0.0", 171 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.0.0.tgz", 172 | "integrity": "sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw==", 173 | "license": "MIT" 174 | }, 175 | "node_modules/@octokit/request": { 176 | "version": "9.2.3", 177 | "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.3.tgz", 178 | "integrity": "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w==", 179 | "license": "MIT", 180 | "dependencies": { 181 | "@octokit/endpoint": "^10.1.4", 182 | "@octokit/request-error": "^6.1.8", 183 | "@octokit/types": "^14.0.0", 184 | "fast-content-type-parse": "^2.0.0", 185 | "universal-user-agent": "^7.0.2" 186 | }, 187 | "engines": { 188 | "node": ">= 18" 189 | } 190 | }, 191 | "node_modules/@octokit/request-error": { 192 | "version": "6.1.8", 193 | "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", 194 | "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", 195 | "license": "MIT", 196 | "dependencies": { 197 | "@octokit/types": "^14.0.0" 198 | }, 199 | "engines": { 200 | "node": ">= 18" 201 | } 202 | }, 203 | "node_modules/@octokit/types": { 204 | "version": "14.0.0", 205 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.0.0.tgz", 206 | "integrity": "sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA==", 207 | "license": "MIT", 208 | "dependencies": { 209 | "@octokit/openapi-types": "^25.0.0" 210 | } 211 | }, 212 | "node_modules/@sec-ant/readable-stream": { 213 | "version": "0.4.1", 214 | "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", 215 | "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", 216 | "license": "MIT" 217 | }, 218 | "node_modules/@sindresorhus/is": { 219 | "version": "7.0.1", 220 | "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.1.tgz", 221 | "integrity": "sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==", 222 | "license": "MIT", 223 | "engines": { 224 | "node": ">=18" 225 | }, 226 | "funding": { 227 | "url": "https://github.com/sindresorhus/is?sponsor=1" 228 | } 229 | }, 230 | "node_modules/@sindresorhus/merge-streams": { 231 | "version": "2.3.0", 232 | "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", 233 | "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", 234 | "license": "MIT", 235 | "engines": { 236 | "node": ">=18" 237 | }, 238 | "funding": { 239 | "url": "https://github.com/sponsors/sindresorhus" 240 | } 241 | }, 242 | "node_modules/@szmarczak/http-timer": { 243 | "version": "5.0.1", 244 | "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", 245 | "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", 246 | "license": "MIT", 247 | "dependencies": { 248 | "defer-to-connect": "^2.0.1" 249 | }, 250 | "engines": { 251 | "node": ">=14.16" 252 | } 253 | }, 254 | "node_modules/@types/http-cache-semantics": { 255 | "version": "4.0.4", 256 | "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", 257 | "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", 258 | "license": "MIT" 259 | }, 260 | "node_modules/@types/json-schema": { 261 | "version": "7.0.15", 262 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 263 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 264 | "license": "MIT" 265 | }, 266 | "node_modules/@types/node": { 267 | "version": "20.17.30", 268 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", 269 | "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", 270 | "dev": true, 271 | "license": "MIT", 272 | "dependencies": { 273 | "undici-types": "~6.19.2" 274 | } 275 | }, 276 | "node_modules/@types/turndown": { 277 | "version": "5.0.5", 278 | "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", 279 | "integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==", 280 | "license": "MIT" 281 | }, 282 | "node_modules/@xliic/cicd-core-node": { 283 | "version": "7.1.0", 284 | "resolved": "https://registry.npmjs.org/@xliic/cicd-core-node/-/cicd-core-node-7.1.0.tgz", 285 | "integrity": "sha512-PhYvXVqPUiBQ1q2Wavxn5J/9fRJUWyEpugx7IRiXG+BuHJREH5za0glxqm2watpXDZLkTffI4QwNfIU10vO75A==", 286 | "license": "AGPL-3.0-only", 287 | "dependencies": { 288 | "@xliic/json-schema-ref-parser": "^9.3.0", 289 | "@xliic/preserving-json-yaml-parser": "^1.9.1", 290 | "ajv": "^8.17.1", 291 | "formdata-node": "^6.0.3", 292 | "globby": "^14.1.0", 293 | "got": "^14.4.1", 294 | "line-chomper": "^0.5.0", 295 | "picomatch": "^2.2.3" 296 | } 297 | }, 298 | "node_modules/@xliic/json-schema-ref-parser": { 299 | "version": "9.3.0", 300 | "resolved": "https://registry.npmjs.org/@xliic/json-schema-ref-parser/-/json-schema-ref-parser-9.3.0.tgz", 301 | "integrity": "sha512-veoNqMyB1nJbKJkbHRZrxYOdz1gz9JuXo7x8v1xRPJIQ5Y6iM1kXonAarwyylqE7RmKIVZLAwqilC5pXdc8Ckg==", 302 | "license": "MIT", 303 | "dependencies": { 304 | "@jsdevtools/ono": "^7.1.3", 305 | "@types/json-schema": "^7.0.6", 306 | "call-me-maybe": "^1.0.1", 307 | "js-yaml": "^4.1.0" 308 | } 309 | }, 310 | "node_modules/@xliic/preserving-json-yaml-parser": { 311 | "version": "1.11.0", 312 | "resolved": "https://registry.npmjs.org/@xliic/preserving-json-yaml-parser/-/preserving-json-yaml-parser-1.11.0.tgz", 313 | "integrity": "sha512-k4TO2DuX0tSe7aMsKxlarAbYJr99Y04TQxWwxg3/2cCfmzT1VYASaoyzH0ExhnU4CWHDSM1ciFxMN1l62JKGbA==", 314 | "license": "AGPL-3.0-only", 315 | "dependencies": { 316 | "jsonc-parser": "^3.0.0", 317 | "yaml-language-server-parser": "^0.1.3" 318 | } 319 | }, 320 | "node_modules/ajv": { 321 | "version": "8.17.1", 322 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", 323 | "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", 324 | "license": "MIT", 325 | "dependencies": { 326 | "fast-deep-equal": "^3.1.3", 327 | "fast-uri": "^3.0.1", 328 | "json-schema-traverse": "^1.0.0", 329 | "require-from-string": "^2.0.2" 330 | }, 331 | "funding": { 332 | "type": "github", 333 | "url": "https://github.com/sponsors/epoberezkin" 334 | } 335 | }, 336 | "node_modules/argparse": { 337 | "version": "2.0.1", 338 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 339 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 340 | "license": "Python-2.0" 341 | }, 342 | "node_modules/before-after-hook": { 343 | "version": "3.0.2", 344 | "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", 345 | "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", 346 | "license": "Apache-2.0" 347 | }, 348 | "node_modules/braces": { 349 | "version": "3.0.3", 350 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 351 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 352 | "license": "MIT", 353 | "dependencies": { 354 | "fill-range": "^7.1.1" 355 | }, 356 | "engines": { 357 | "node": ">=8" 358 | } 359 | }, 360 | "node_modules/cacheable-lookup": { 361 | "version": "7.0.0", 362 | "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", 363 | "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", 364 | "license": "MIT", 365 | "engines": { 366 | "node": ">=14.16" 367 | } 368 | }, 369 | "node_modules/cacheable-request": { 370 | "version": "12.0.1", 371 | "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz", 372 | "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==", 373 | "license": "MIT", 374 | "dependencies": { 375 | "@types/http-cache-semantics": "^4.0.4", 376 | "get-stream": "^9.0.1", 377 | "http-cache-semantics": "^4.1.1", 378 | "keyv": "^4.5.4", 379 | "mimic-response": "^4.0.0", 380 | "normalize-url": "^8.0.1", 381 | "responselike": "^3.0.0" 382 | }, 383 | "engines": { 384 | "node": ">=18" 385 | } 386 | }, 387 | "node_modules/call-me-maybe": { 388 | "version": "1.0.2", 389 | "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", 390 | "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", 391 | "license": "MIT" 392 | }, 393 | "node_modules/decompress-response": { 394 | "version": "6.0.0", 395 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 396 | "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", 397 | "license": "MIT", 398 | "dependencies": { 399 | "mimic-response": "^3.1.0" 400 | }, 401 | "engines": { 402 | "node": ">=10" 403 | }, 404 | "funding": { 405 | "url": "https://github.com/sponsors/sindresorhus" 406 | } 407 | }, 408 | "node_modules/decompress-response/node_modules/mimic-response": { 409 | "version": "3.1.0", 410 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", 411 | "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", 412 | "license": "MIT", 413 | "engines": { 414 | "node": ">=10" 415 | }, 416 | "funding": { 417 | "url": "https://github.com/sponsors/sindresorhus" 418 | } 419 | }, 420 | "node_modules/defer-to-connect": { 421 | "version": "2.0.1", 422 | "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", 423 | "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", 424 | "license": "MIT", 425 | "engines": { 426 | "node": ">=10" 427 | } 428 | }, 429 | "node_modules/fast-content-type-parse": { 430 | "version": "2.0.1", 431 | "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", 432 | "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", 433 | "funding": [ 434 | { 435 | "type": "github", 436 | "url": "https://github.com/sponsors/fastify" 437 | }, 438 | { 439 | "type": "opencollective", 440 | "url": "https://opencollective.com/fastify" 441 | } 442 | ], 443 | "license": "MIT" 444 | }, 445 | "node_modules/fast-deep-equal": { 446 | "version": "3.1.3", 447 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 448 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 449 | "license": "MIT" 450 | }, 451 | "node_modules/fast-glob": { 452 | "version": "3.3.3", 453 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 454 | "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 455 | "license": "MIT", 456 | "dependencies": { 457 | "@nodelib/fs.stat": "^2.0.2", 458 | "@nodelib/fs.walk": "^1.2.3", 459 | "glob-parent": "^5.1.2", 460 | "merge2": "^1.3.0", 461 | "micromatch": "^4.0.8" 462 | }, 463 | "engines": { 464 | "node": ">=8.6.0" 465 | } 466 | }, 467 | "node_modules/fast-uri": { 468 | "version": "3.0.6", 469 | "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", 470 | "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", 471 | "funding": [ 472 | { 473 | "type": "github", 474 | "url": "https://github.com/sponsors/fastify" 475 | }, 476 | { 477 | "type": "opencollective", 478 | "url": "https://opencollective.com/fastify" 479 | } 480 | ], 481 | "license": "BSD-3-Clause" 482 | }, 483 | "node_modules/fastq": { 484 | "version": "1.19.1", 485 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 486 | "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 487 | "license": "ISC", 488 | "dependencies": { 489 | "reusify": "^1.0.4" 490 | } 491 | }, 492 | "node_modules/fill-range": { 493 | "version": "7.1.1", 494 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 495 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 496 | "license": "MIT", 497 | "dependencies": { 498 | "to-regex-range": "^5.0.1" 499 | }, 500 | "engines": { 501 | "node": ">=8" 502 | } 503 | }, 504 | "node_modules/form-data-encoder": { 505 | "version": "4.0.2", 506 | "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", 507 | "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", 508 | "license": "MIT", 509 | "engines": { 510 | "node": ">= 18" 511 | } 512 | }, 513 | "node_modules/formdata-node": { 514 | "version": "6.0.3", 515 | "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz", 516 | "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==", 517 | "license": "MIT", 518 | "engines": { 519 | "node": ">= 18" 520 | } 521 | }, 522 | "node_modules/get-stream": { 523 | "version": "9.0.1", 524 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", 525 | "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", 526 | "license": "MIT", 527 | "dependencies": { 528 | "@sec-ant/readable-stream": "^0.4.1", 529 | "is-stream": "^4.0.1" 530 | }, 531 | "engines": { 532 | "node": ">=18" 533 | }, 534 | "funding": { 535 | "url": "https://github.com/sponsors/sindresorhus" 536 | } 537 | }, 538 | "node_modules/glob-parent": { 539 | "version": "5.1.2", 540 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 541 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 542 | "license": "ISC", 543 | "dependencies": { 544 | "is-glob": "^4.0.1" 545 | }, 546 | "engines": { 547 | "node": ">= 6" 548 | } 549 | }, 550 | "node_modules/globby": { 551 | "version": "14.1.0", 552 | "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", 553 | "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", 554 | "license": "MIT", 555 | "dependencies": { 556 | "@sindresorhus/merge-streams": "^2.1.0", 557 | "fast-glob": "^3.3.3", 558 | "ignore": "^7.0.3", 559 | "path-type": "^6.0.0", 560 | "slash": "^5.1.0", 561 | "unicorn-magic": "^0.3.0" 562 | }, 563 | "engines": { 564 | "node": ">=18" 565 | }, 566 | "funding": { 567 | "url": "https://github.com/sponsors/sindresorhus" 568 | } 569 | }, 570 | "node_modules/got": { 571 | "version": "14.4.7", 572 | "resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz", 573 | "integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==", 574 | "license": "MIT", 575 | "dependencies": { 576 | "@sindresorhus/is": "^7.0.1", 577 | "@szmarczak/http-timer": "^5.0.1", 578 | "cacheable-lookup": "^7.0.0", 579 | "cacheable-request": "^12.0.1", 580 | "decompress-response": "^6.0.0", 581 | "form-data-encoder": "^4.0.2", 582 | "http2-wrapper": "^2.2.1", 583 | "lowercase-keys": "^3.0.0", 584 | "p-cancelable": "^4.0.1", 585 | "responselike": "^3.0.0", 586 | "type-fest": "^4.26.1" 587 | }, 588 | "engines": { 589 | "node": ">=20" 590 | }, 591 | "funding": { 592 | "url": "https://github.com/sindresorhus/got?sponsor=1" 593 | } 594 | }, 595 | "node_modules/http-cache-semantics": { 596 | "version": "4.1.1", 597 | "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", 598 | "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", 599 | "license": "BSD-2-Clause" 600 | }, 601 | "node_modules/http2-wrapper": { 602 | "version": "2.2.1", 603 | "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", 604 | "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", 605 | "license": "MIT", 606 | "dependencies": { 607 | "quick-lru": "^5.1.1", 608 | "resolve-alpn": "^1.2.0" 609 | }, 610 | "engines": { 611 | "node": ">=10.19.0" 612 | } 613 | }, 614 | "node_modules/ignore": { 615 | "version": "7.0.3", 616 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", 617 | "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", 618 | "license": "MIT", 619 | "engines": { 620 | "node": ">= 4" 621 | } 622 | }, 623 | "node_modules/is-extglob": { 624 | "version": "2.1.1", 625 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 626 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 627 | "license": "MIT", 628 | "engines": { 629 | "node": ">=0.10.0" 630 | } 631 | }, 632 | "node_modules/is-glob": { 633 | "version": "4.0.3", 634 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 635 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 636 | "license": "MIT", 637 | "dependencies": { 638 | "is-extglob": "^2.1.1" 639 | }, 640 | "engines": { 641 | "node": ">=0.10.0" 642 | } 643 | }, 644 | "node_modules/is-number": { 645 | "version": "7.0.0", 646 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 647 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 648 | "license": "MIT", 649 | "engines": { 650 | "node": ">=0.12.0" 651 | } 652 | }, 653 | "node_modules/is-stream": { 654 | "version": "4.0.1", 655 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", 656 | "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", 657 | "license": "MIT", 658 | "engines": { 659 | "node": ">=18" 660 | }, 661 | "funding": { 662 | "url": "https://github.com/sponsors/sindresorhus" 663 | } 664 | }, 665 | "node_modules/js-yaml": { 666 | "version": "4.1.0", 667 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 668 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 669 | "license": "MIT", 670 | "dependencies": { 671 | "argparse": "^2.0.1" 672 | }, 673 | "bin": { 674 | "js-yaml": "bin/js-yaml.js" 675 | } 676 | }, 677 | "node_modules/json-buffer": { 678 | "version": "3.0.1", 679 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 680 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 681 | "license": "MIT" 682 | }, 683 | "node_modules/json-schema-traverse": { 684 | "version": "1.0.0", 685 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", 686 | "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", 687 | "license": "MIT" 688 | }, 689 | "node_modules/jsonc-parser": { 690 | "version": "3.3.1", 691 | "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", 692 | "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", 693 | "license": "MIT" 694 | }, 695 | "node_modules/keyv": { 696 | "version": "4.5.4", 697 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 698 | "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 699 | "license": "MIT", 700 | "dependencies": { 701 | "json-buffer": "3.0.1" 702 | } 703 | }, 704 | "node_modules/line-chomper": { 705 | "version": "0.5.0", 706 | "resolved": "https://registry.npmjs.org/line-chomper/-/line-chomper-0.5.0.tgz", 707 | "integrity": "sha512-BVnlxdQhHEh7kj2B8sVziWzB0erzTR/H3rW9tdox3l36xTgpfFV2DtQgT5H6DRDcxrui46UN+WDyT0V1mVLOqg==", 708 | "license": "Apache 2" 709 | }, 710 | "node_modules/lowercase-keys": { 711 | "version": "3.0.0", 712 | "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", 713 | "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", 714 | "license": "MIT", 715 | "engines": { 716 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 717 | }, 718 | "funding": { 719 | "url": "https://github.com/sponsors/sindresorhus" 720 | } 721 | }, 722 | "node_modules/merge2": { 723 | "version": "1.4.1", 724 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 725 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 726 | "license": "MIT", 727 | "engines": { 728 | "node": ">= 8" 729 | } 730 | }, 731 | "node_modules/micromatch": { 732 | "version": "4.0.8", 733 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 734 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 735 | "license": "MIT", 736 | "dependencies": { 737 | "braces": "^3.0.3", 738 | "picomatch": "^2.3.1" 739 | }, 740 | "engines": { 741 | "node": ">=8.6" 742 | } 743 | }, 744 | "node_modules/mimic-response": { 745 | "version": "4.0.0", 746 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", 747 | "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", 748 | "license": "MIT", 749 | "engines": { 750 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 751 | }, 752 | "funding": { 753 | "url": "https://github.com/sponsors/sindresorhus" 754 | } 755 | }, 756 | "node_modules/normalize-url": { 757 | "version": "8.0.1", 758 | "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", 759 | "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", 760 | "license": "MIT", 761 | "engines": { 762 | "node": ">=14.16" 763 | }, 764 | "funding": { 765 | "url": "https://github.com/sponsors/sindresorhus" 766 | } 767 | }, 768 | "node_modules/p-cancelable": { 769 | "version": "4.0.1", 770 | "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", 771 | "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", 772 | "license": "MIT", 773 | "engines": { 774 | "node": ">=14.16" 775 | } 776 | }, 777 | "node_modules/path-type": { 778 | "version": "6.0.0", 779 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", 780 | "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", 781 | "license": "MIT", 782 | "engines": { 783 | "node": ">=18" 784 | }, 785 | "funding": { 786 | "url": "https://github.com/sponsors/sindresorhus" 787 | } 788 | }, 789 | "node_modules/picomatch": { 790 | "version": "2.3.1", 791 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 792 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 793 | "license": "MIT", 794 | "engines": { 795 | "node": ">=8.6" 796 | }, 797 | "funding": { 798 | "url": "https://github.com/sponsors/jonschlinkert" 799 | } 800 | }, 801 | "node_modules/queue-microtask": { 802 | "version": "1.2.3", 803 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 804 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 805 | "funding": [ 806 | { 807 | "type": "github", 808 | "url": "https://github.com/sponsors/feross" 809 | }, 810 | { 811 | "type": "patreon", 812 | "url": "https://www.patreon.com/feross" 813 | }, 814 | { 815 | "type": "consulting", 816 | "url": "https://feross.org/support" 817 | } 818 | ], 819 | "license": "MIT" 820 | }, 821 | "node_modules/quick-lru": { 822 | "version": "5.1.1", 823 | "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", 824 | "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", 825 | "license": "MIT", 826 | "engines": { 827 | "node": ">=10" 828 | }, 829 | "funding": { 830 | "url": "https://github.com/sponsors/sindresorhus" 831 | } 832 | }, 833 | "node_modules/require-from-string": { 834 | "version": "2.0.2", 835 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", 836 | "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", 837 | "license": "MIT", 838 | "engines": { 839 | "node": ">=0.10.0" 840 | } 841 | }, 842 | "node_modules/resolve-alpn": { 843 | "version": "1.2.1", 844 | "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", 845 | "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", 846 | "license": "MIT" 847 | }, 848 | "node_modules/responselike": { 849 | "version": "3.0.0", 850 | "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", 851 | "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", 852 | "license": "MIT", 853 | "dependencies": { 854 | "lowercase-keys": "^3.0.0" 855 | }, 856 | "engines": { 857 | "node": ">=14.16" 858 | }, 859 | "funding": { 860 | "url": "https://github.com/sponsors/sindresorhus" 861 | } 862 | }, 863 | "node_modules/reusify": { 864 | "version": "1.1.0", 865 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 866 | "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 867 | "license": "MIT", 868 | "engines": { 869 | "iojs": ">=1.0.0", 870 | "node": ">=0.10.0" 871 | } 872 | }, 873 | "node_modules/run-parallel": { 874 | "version": "1.2.0", 875 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 876 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 877 | "funding": [ 878 | { 879 | "type": "github", 880 | "url": "https://github.com/sponsors/feross" 881 | }, 882 | { 883 | "type": "patreon", 884 | "url": "https://www.patreon.com/feross" 885 | }, 886 | { 887 | "type": "consulting", 888 | "url": "https://feross.org/support" 889 | } 890 | ], 891 | "license": "MIT", 892 | "dependencies": { 893 | "queue-microtask": "^1.2.2" 894 | } 895 | }, 896 | "node_modules/slash": { 897 | "version": "5.1.0", 898 | "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", 899 | "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", 900 | "license": "MIT", 901 | "engines": { 902 | "node": ">=14.16" 903 | }, 904 | "funding": { 905 | "url": "https://github.com/sponsors/sindresorhus" 906 | } 907 | }, 908 | "node_modules/to-regex-range": { 909 | "version": "5.0.1", 910 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 911 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 912 | "license": "MIT", 913 | "dependencies": { 914 | "is-number": "^7.0.0" 915 | }, 916 | "engines": { 917 | "node": ">=8.0" 918 | } 919 | }, 920 | "node_modules/tunnel": { 921 | "version": "0.0.6", 922 | "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", 923 | "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", 924 | "license": "MIT", 925 | "engines": { 926 | "node": ">=0.6.11 <=0.7.0 || >=0.7.3" 927 | } 928 | }, 929 | "node_modules/turndown": { 930 | "version": "7.2.0", 931 | "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", 932 | "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==", 933 | "license": "MIT", 934 | "dependencies": { 935 | "@mixmark-io/domino": "^2.2.0" 936 | } 937 | }, 938 | "node_modules/type-fest": { 939 | "version": "4.39.0", 940 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.0.tgz", 941 | "integrity": "sha512-w2IGJU1tIgcrepg9ZJ82d8UmItNQtOFJG0HCUE3SzMokKkTsruVDALl2fAdiEzJlfduoU+VyXJWIIUZ+6jV+nw==", 942 | "license": "(MIT OR CC0-1.0)", 943 | "engines": { 944 | "node": ">=16" 945 | }, 946 | "funding": { 947 | "url": "https://github.com/sponsors/sindresorhus" 948 | } 949 | }, 950 | "node_modules/typescript": { 951 | "version": "5.8.2", 952 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 953 | "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 954 | "dev": true, 955 | "license": "Apache-2.0", 956 | "bin": { 957 | "tsc": "bin/tsc", 958 | "tsserver": "bin/tsserver" 959 | }, 960 | "engines": { 961 | "node": ">=14.17" 962 | } 963 | }, 964 | "node_modules/undici": { 965 | "version": "5.29.0", 966 | "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", 967 | "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", 968 | "license": "MIT", 969 | "dependencies": { 970 | "@fastify/busboy": "^2.0.0" 971 | }, 972 | "engines": { 973 | "node": ">=14.0" 974 | } 975 | }, 976 | "node_modules/undici-types": { 977 | "version": "6.19.8", 978 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 979 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 980 | "dev": true, 981 | "license": "MIT" 982 | }, 983 | "node_modules/unicorn-magic": { 984 | "version": "0.3.0", 985 | "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", 986 | "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", 987 | "license": "MIT", 988 | "engines": { 989 | "node": ">=18" 990 | }, 991 | "funding": { 992 | "url": "https://github.com/sponsors/sindresorhus" 993 | } 994 | }, 995 | "node_modules/universal-user-agent": { 996 | "version": "7.0.2", 997 | "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", 998 | "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", 999 | "license": "ISC" 1000 | }, 1001 | "node_modules/yaml-language-server-parser": { 1002 | "version": "0.1.3", 1003 | "resolved": "https://registry.npmjs.org/yaml-language-server-parser/-/yaml-language-server-parser-0.1.3.tgz", 1004 | "integrity": "sha512-xD2I+6M/vqQvcy4ded8JpXUaDHXmZMdhIO3OpuiFxstutwnW4whrfDzNcrsfXVdgMWqOUpdv3747Q081PFN1+g==", 1005 | "license": "Apache-2.0" 1006 | } 1007 | } 1008 | } 1009 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-security-audit-action", 3 | "version": "0.0.0", 4 | "description": "Performs API contract security audit to get a detailed analysis of the possible vulnerabilities and other issues in the API contract.", 5 | "main": "index.mjs", 6 | "type": "module", 7 | "scripts": { 8 | "build": "tsc" 9 | }, 10 | "author": "", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/42Crunch/api-security-audit-action.git" 14 | }, 15 | "license": "AGPL-3.0-only", 16 | "dependencies": { 17 | "@actions/core": "^1.11.1", 18 | "@octokit/core": "^6.1.5", 19 | "@types/turndown": "^5.0.1", 20 | "@xliic/cicd-core-node": "7.1.0", 21 | "got": "^14.4.1", 22 | "turndown": "^7.1.2" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^20.14.11", 26 | "typescript": "^5.5.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/index.mts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 42Crunch Ltd. All rights reserved. 3 | Licensed under the GNU Affero General Public License version 3. See LICENSE.txt in the project root for license information. 4 | */ 5 | 6 | import { extname, resolve } from "path"; 7 | import * as core from "@actions/core"; 8 | import { audit } from "@xliic/cicd-core-node"; 9 | import { produceSarif, Sarif } from "./sarif.mjs"; 10 | import { uploadSarif } from "./upload.mjs"; 11 | import { Logger, Reference, SharingType } from "@xliic/cicd-core-node"; 12 | import { existsSync, writeFileSync } from "fs"; 13 | 14 | function logger(levelName: string): Logger { 15 | const levels = { 16 | FATAL: 5, 17 | ERROR: 4, 18 | WARN: 3, 19 | INFO: 2, 20 | DEBUG: 1, 21 | }; 22 | 23 | const level = levels[levelName.toUpperCase()] ?? levels.INFO; 24 | 25 | return { 26 | debug: (message: string) => { 27 | if (levels.DEBUG >= level) { 28 | console.log(message); 29 | } 30 | }, 31 | info: (message: string) => { 32 | if (levels.INFO >= level) { 33 | console.log(message); 34 | } 35 | }, 36 | warning: (message: string) => { 37 | if (levels.WARN >= level) { 38 | console.log(message); 39 | } 40 | }, 41 | error: (message: string) => { 42 | if (levels.ERROR >= level) { 43 | console.log(message); 44 | } 45 | }, 46 | fatal: (message: string) => { 47 | if (levels.FATAL >= level) { 48 | console.log(message); 49 | } 50 | }, 51 | }; 52 | } 53 | 54 | function getInputValue(input: string, options: any, defaultValue: any): any { 55 | const value = core.getInput(input, { required: false }); 56 | if (typeof value === "undefined") { 57 | return defaultValue; 58 | } 59 | 60 | if (options.hasOwnProperty(value)) { 61 | return options[value]; 62 | } 63 | 64 | console.log( 65 | `Unexpected value for input "${input}" using default value instead` 66 | ); 67 | 68 | return defaultValue; 69 | } 70 | 71 | function getInputNumber(input: string): number { 72 | const inputValue = core.getInput(input, { required: false }); 73 | if (inputValue === undefined || inputValue === ""){ 74 | return undefined; 75 | } 76 | const value = Number.parseInt(inputValue); 77 | if(isNaN(value)) { 78 | throw new Error(`Failed to parse integer value for input "${input}", original input value is "${inputValue}"`); 79 | } 80 | return value; 81 | } 82 | 83 | function env(name: string): string { 84 | if (typeof process.env[name] === "undefined") { 85 | throw new Error(`Environment variable ${name} is not set`); 86 | } 87 | return process.env[name]!; 88 | } 89 | 90 | function getReference(): Reference | undefined { 91 | const BRANCH_PREFIX = "refs/heads/"; 92 | const TAG_PREFIX = "refs/tags/"; 93 | const PR_PREFIX = "refs/pull/"; 94 | const ref = env("GITHUB_REF"); 95 | 96 | if (ref.startsWith(BRANCH_PREFIX)) { 97 | const branch = ref.substring(BRANCH_PREFIX.length); 98 | return { branch }; 99 | } 100 | 101 | if (ref.startsWith(TAG_PREFIX)) { 102 | const tag = ref.substring(TAG_PREFIX.length); 103 | return { tag }; 104 | } 105 | 106 | if (ref.startsWith(PR_PREFIX)) { 107 | const pr = ref.substring(PR_PREFIX.length, ref.length - "/merge".length); 108 | const targetBranch = env("GITHUB_BASE_REF"); 109 | return { pr: { id: pr, target: targetBranch } }; 110 | } 111 | } 112 | 113 | function checkPath(rootDir: string, path: string, extention: string = ""){ 114 | let absolutePath = resolve(rootDir, path); 115 | 116 | const ext = extname(absolutePath); 117 | if (ext === "") { 118 | absolutePath += `.${extention}`; 119 | } 120 | 121 | if (existsSync(absolutePath)) { 122 | throw new Error( 123 | `File ${absolutePath} alredy exist` 124 | ); 125 | } 126 | 127 | return absolutePath; 128 | } 129 | 130 | (async () => { 131 | const ignoreNetworkErrors = core.getInput("ignore-network-errors") === "true"; 132 | try { 133 | const githubServerUrl = env("GITHUB_SERVER_URL"); 134 | const githubRepository = env("GITHUB_REPOSITORY"); 135 | const apiToken = core.getInput("api-token", { required: false }); 136 | const minScore = core.getInput("min-score", { required: true }); 137 | const uploadToCodeScanning = core.getInput("upload-to-code-scanning", { 138 | required: true, 139 | }); 140 | const ignoreFailures = core.getInput("ignore-failures") === "true"; 141 | const userAgent = `GithubAction-CICD/2.0`; 142 | const platformUrl = core.getInput("platform-url", { required: true }); 143 | const logLevel = core.getInput("log-level", { required: true }); 144 | const rootDirectory = core.getInput("root-directory", { required: false }); 145 | const defaultCollectionName = core.getInput("default-collection-name", { 146 | required: false, 147 | }); 148 | const writeJsonReportTo = core.getInput("json-report", { required: false }); 149 | const api_tags = core.getInput("api-tags", { required: false }); 150 | const skipLocalChecks = core.getInput("skip-local-checks") === "true"; 151 | const repositoryUrl = `${githubServerUrl}/${githubRepository}`; 152 | const sarifReport = core.getInput("sarif-report", { required: false }); 153 | const auditTimeout = getInputNumber("audit-timeout"); 154 | const needSarif = uploadToCodeScanning !== "false" || !!sarifReport 155 | 156 | const reference = getReference(); 157 | if (!reference) { 158 | core.setFailed("Unable to get the branch/tag/PR name"); 159 | return; 160 | } 161 | 162 | const shareEveryone = getInputValue( 163 | "share-everyone", 164 | { 165 | OFF: undefined, 166 | READ_ONLY: SharingType.ReadOnly, 167 | READ_WRITE: SharingType.ReadWrite, 168 | }, 169 | undefined 170 | ); 171 | 172 | if (ignoreNetworkErrors) { 173 | core.info("Ignoring network errors"); 174 | } 175 | if (ignoreFailures) { 176 | core.info("Ignoring security audit failures"); 177 | } 178 | 179 | const rootDir = resolve(process.cwd(), rootDirectory || "."); 180 | 181 | const result = await audit({ 182 | rootDir, 183 | referer: repositoryUrl, 184 | userAgent, 185 | apiToken, 186 | onboardingUrl: 187 | "https://docs.42crunch.com/latest/content/tasks/integrate_github_actions.htm", 188 | platformUrl, 189 | logger: logger(logLevel), 190 | lineNumbers: needSarif, 191 | reference, 192 | repoName: repositoryUrl, 193 | cicdName: "github", 194 | minScore, 195 | shareEveryone, 196 | defaultCollectionName, 197 | writeJsonReportTo, 198 | api_tags, 199 | skipLocalChecks, 200 | assessmentMaxWaitTime : auditTimeout ? auditTimeout*1000 : undefined 201 | }); 202 | 203 | if(needSarif){ 204 | const sarif: Sarif = await produceSarif(result.files); 205 | 206 | if(sarifReport){ 207 | try { 208 | const sarifPath = checkPath(rootDir, sarifReport, "sarif"); 209 | writeFileSync(sarifPath, JSON.stringify(sarif, null, 4)); 210 | core.info(`SARIF report was written to: "${sarifPath}"`); 211 | } catch (e) { 212 | core.setFailed( 213 | `Can't write SARIF report\n ${e}` 214 | ); 215 | } 216 | } 217 | 218 | if (uploadToCodeScanning !== "false") { 219 | core.info("Uploading results to Code Scanning"); 220 | await uploadSarif(sarif); 221 | } 222 | } 223 | 224 | if (!ignoreFailures) { 225 | if (result!.failures > 0) { 226 | throw new Error(`Completed with ${result!.failures} failure(s)`); 227 | } else if (result.files.size === 0) { 228 | throw new Error("No OpenAPI files found"); 229 | } 230 | } 231 | } catch (ex) { 232 | if (ignoreNetworkErrors && ex?.isNetworkError === true) { 233 | core.info(ex.message); 234 | } else { 235 | core.setFailed( 236 | `${ex.message} ${(ex?.code || "", ex?.response?.body || "")}` 237 | ); 238 | } 239 | } 240 | })(); 241 | -------------------------------------------------------------------------------- /src/sarif.mts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 42Crunch Ltd. All rights reserved. 3 | Licensed under the GNU Affero General Public License version 3. See LICENSE.txt in the project root for license information. 4 | */ 5 | 6 | import * as url from "url"; 7 | import { resolve } from "path"; 8 | import { FileAuditMap, Issue } from "@xliic/cicd-core-node"; 9 | import TurndownService from "turndown"; 10 | import got from "got"; 11 | 12 | export interface Sarif { 13 | $schema?: string; 14 | version: "2.1.0"; 15 | runs: Run[]; 16 | } 17 | 18 | export interface Run { 19 | tool: { 20 | driver: { 21 | name: string; 22 | informationUri: string; 23 | rules: Rule[]; 24 | }; 25 | }; 26 | artifacts: Artifact[]; 27 | results: Result[]; 28 | } 29 | 30 | export interface Artifact { 31 | location: { 32 | uri: url.URL; 33 | }; 34 | } 35 | 36 | export interface Result { 37 | ruleId?: string; 38 | ruleIndex?: number; 39 | level?: "notApplicable" | "pass" | "note" | "warning" | "error" | "open"; 40 | message?: { 41 | text?: string; 42 | }; 43 | locations?: Location[]; 44 | } 45 | 46 | export interface Location { 47 | physicalLocation?: { 48 | id?: number; 49 | artifactLocation: { 50 | uri: url.URL; 51 | index: number; 52 | }; 53 | region?: Region; 54 | }; 55 | } 56 | 57 | export interface Region { 58 | startLine?: number; 59 | startColumn?: number; 60 | } 61 | 62 | export interface Rule { 63 | id: string; 64 | shortDescription?: { 65 | text?: string; 66 | }; 67 | helpUri?: string; 68 | help?: { 69 | text?: string; 70 | }; 71 | properties?: { 72 | tags?: string[]; 73 | [k: string]: any; 74 | }; 75 | } 76 | 77 | const ARTICLES_URL = "https://platform.42crunch.com/kdb/audit-with-yaml.json"; 78 | 79 | export async function getArticles(): Promise { 80 | try { 81 | const response = await got(ARTICLES_URL); 82 | const articles = JSON.parse(response.body); 83 | return articles; 84 | } catch (error) { 85 | throw new Error(`Failed to read articles.json: ${error}`); 86 | } 87 | } 88 | 89 | function getResultSeverity( 90 | issue: Issue 91 | ): string { 92 | const criticalityToSeverity = { 93 | 1: "2", 94 | 2: "5", 95 | 3: "6", 96 | 4: "8", 97 | 5: "9.5", 98 | }; 99 | 100 | return criticalityToSeverity[issue.criticality]; 101 | } 102 | 103 | function getResultLevel( 104 | issue: Issue 105 | ): "notApplicable" | "pass" | "note" | "warning" | "error" | "open" { 106 | const criticalityToSeverity = { 107 | 1: "note", 108 | 2: "note", 109 | 3: "warning", 110 | 4: "error", 111 | 5: "error", 112 | }; 113 | 114 | return criticalityToSeverity[issue.criticality]; 115 | } 116 | 117 | 118 | 119 | const fallbackArticle = { 120 | description: { 121 | text: `

Whoops! Looks like there has been an oversight and we are missing a page for this issue.

122 |

Let us know the title of the issue, and we make sure to add it to the encyclopedia.

`, 123 | }, 124 | }; 125 | 126 | function articleById(articles: any, id: string) { 127 | function partToText(part) { 128 | if (!part || !part.sections) { 129 | return ""; 130 | } 131 | return part.sections 132 | .map((section) => `${section.text || ""}${formatCode(section?.code?.json)}`) 133 | .join(""); 134 | } 135 | 136 | const article = articles[id] || fallbackArticle; 137 | 138 | return [ 139 | article ? article.description.text : "", 140 | partToText(article.example), 141 | partToText(article.exploit), 142 | partToText(article.remediation), 143 | ].join(""); 144 | } 145 | 146 | function formatCode(code: string | undefined): string { 147 | if(code === undefined) { 148 | return ""; 149 | } 150 | return `
${code}
`; 151 | } 152 | 153 | export async function produceSarif(summary: FileAuditMap): Promise { 154 | const sarifResults: Result[] = []; 155 | const sarifFiles = {}; 156 | 157 | const sarifRules = {}; 158 | const sarifRuleIndices = {}; 159 | let nextRuleIndex = 0; 160 | const sarifArtifactIndices = {}; 161 | let nextArtifactIndex = 0; 162 | 163 | // @ts-ignore 164 | const turndownService = new TurndownService({preformattedCode: true}); 165 | 166 | const articles = await getArticles(); 167 | 168 | const sarifLog: Sarif = { 169 | version: "2.1.0", 170 | $schema: "http://json.schemastore.org/sarif-2.1.0-rtm.4", 171 | runs: [ 172 | { 173 | tool: { 174 | driver: { 175 | name: "42Crunch REST API Static Security Testing", 176 | informationUri: "https://42crunch.com/", 177 | rules: [], 178 | }, 179 | }, 180 | results: sarifResults, 181 | artifacts: [], 182 | }, 183 | ], 184 | }; 185 | 186 | for (const filename of summary.keys()) { 187 | const absoluteFile = resolve(filename); 188 | const result = summary.get(filename)!; 189 | if ("errors" in result) { 190 | continue; 191 | } 192 | 193 | if (result.issues) { 194 | for (const issue of result.issues) { 195 | if (typeof sarifFiles[issue.file] === "undefined") { 196 | sarifArtifactIndices[issue.file] = nextArtifactIndex++; 197 | sarifFiles[issue.file] = { 198 | location: { 199 | uri: url.pathToFileURL(issue.file), 200 | }, 201 | }; 202 | } 203 | 204 | const sarifRepresentation: Result = { 205 | level: getResultLevel(issue), 206 | ruleId: issue.id, 207 | message: { 208 | text: issue.description, 209 | }, 210 | locations: [ 211 | { 212 | physicalLocation: { 213 | artifactLocation: { 214 | uri: url.pathToFileURL(issue.file), 215 | index: sarifArtifactIndices[issue.file], 216 | }, 217 | region: { 218 | startLine: issue.line, 219 | startColumn: 1, 220 | }, 221 | }, 222 | }, 223 | ], 224 | }; 225 | 226 | sarifResults.push(sarifRepresentation); 227 | 228 | if (typeof sarifRules[issue.id] === "undefined") { 229 | sarifRuleIndices[issue.id] = nextRuleIndex++; 230 | 231 | let helpUrl = "https://support.42crunch.com"; 232 | const article = articles[issue.id]; 233 | if (article) { 234 | const version = issue.id.startsWith("v3-") ? "oasv3" : "oasv2"; 235 | const group = article.group; 236 | const subgroup = article.subgroup; 237 | helpUrl = `https://docs.42crunch.com/latest/content/${version}/${group}/${subgroup}/${issue.id}.htm`; 238 | } 239 | 240 | const helpText = turndownService.turndown( 241 | articleById(articles, issue.id) 242 | ); 243 | 244 | // Create a new entry in the rules dictionary. 245 | sarifRules[issue.id] = { 246 | id: issue.id, 247 | shortDescription: { 248 | text: issue.description, 249 | }, 250 | helpUri: helpUrl, //meta.docs.url, 251 | help: { 252 | text: helpText, 253 | }, 254 | properties: { 255 | category: "Other", //meta.docs.category, 256 | "security-severity": getResultSeverity(issue) 257 | }, 258 | }; 259 | } 260 | 261 | sarifRepresentation.ruleIndex = sarifRuleIndices[issue.id]; 262 | } 263 | } 264 | 265 | /* Do not report failures for the time being 266 | if (result.failures && result.failures.length > 0) { 267 | for (let i = 0; i < result.failures.length; i++) { 268 | const failure = result.failures[i]; 269 | 270 | // Only add it if not already there. 271 | if (typeof sarifFiles[absoluteFile] === 'undefined') { 272 | sarifArtifactIndices[absoluteFile] = nextArtifactIndex++; 273 | sarifFiles[absoluteFile] = { 274 | location: { 275 | uri: url.pathToFileURL(absoluteFile), 276 | }, 277 | }; 278 | } 279 | 280 | const failureRuleId = `failure-${i}`; 281 | 282 | const sarifRepresentation: Result = { 283 | level: 'error', 284 | ruleId: failureRuleId, 285 | message: { 286 | text: failure, 287 | }, 288 | locations: [ 289 | { 290 | physicalLocation: { 291 | artifactLocation: { 292 | uri: url.pathToFileURL(absoluteFile), 293 | index: sarifArtifactIndices[absoluteFile], 294 | }, 295 | }, 296 | }, 297 | ], 298 | }; 299 | 300 | sarifResults.push(sarifRepresentation); 301 | 302 | // each failure gets uinique rule 303 | sarifRuleIndices[failureRuleId] = nextRuleIndex++; 304 | sarifRules[failureRuleId] = { 305 | id: failureRuleId, 306 | shortDescription: { 307 | text: failure, 308 | }, 309 | helpUri: 'http://support.42crunch.com', //meta.docs.url, 310 | help: { 311 | text: failure, 312 | }, 313 | properties: { 314 | category: 'Other', //meta.docs.category, 315 | }, 316 | }; 317 | } 318 | } 319 | */ 320 | } 321 | 322 | if (Object.keys(sarifFiles).length > 0) { 323 | sarifLog.runs[0].artifacts = []; 324 | 325 | Object.keys(sarifFiles).forEach(function (path) { 326 | sarifLog.runs[0].artifacts.push(sarifFiles[path]); 327 | }); 328 | } 329 | 330 | if (Object.keys(sarifRules).length > 0) { 331 | Object.keys(sarifRules).forEach(function (ruleId) { 332 | let rule = sarifRules[ruleId]; 333 | sarifLog.runs[0].tool.driver.rules.push(rule); 334 | }); 335 | } 336 | 337 | return sarifLog; 338 | } 339 | -------------------------------------------------------------------------------- /src/upload.mts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 42Crunch Ltd. All rights reserved. 3 | Licensed under the GNU Affero General Public License version 3. See LICENSE.txt in the project root for license information. 4 | */ 5 | 6 | import { Octokit } from "@octokit/core"; 7 | import * as core from "@actions/core"; 8 | import * as url from "url"; 9 | import zlib from "zlib"; 10 | 11 | import { Sarif } from "./sarif.mjs"; 12 | 13 | export function getRequiredInput(name: string): string { 14 | return core.getInput(name, { required: true }); 15 | } 16 | 17 | export function getRequiredEnvParam(paramName: string): string { 18 | const value = process.env[paramName]; 19 | if (value === undefined || value.length === 0) { 20 | throw new Error(`${paramName} environment variable must be set`); 21 | } 22 | core.debug(`${paramName}=${value}`); 23 | return value; 24 | } 25 | 26 | export function getRef(): string { 27 | // Will be in the form "refs/heads/master" on a push event 28 | // or in the form "refs/pull/N/merge" on a pull_request event 29 | const ref = getRequiredEnvParam("GITHUB_REF"); 30 | 31 | // For pull request refs we want to convert from the 'merge' ref 32 | // to the 'head' ref, as that is what we want to analyse. 33 | // There should have been some code earlier in the workflow to do 34 | // the checkout, but we have no way of verifying that here. 35 | const pull_ref_regex = /refs\/pull\/(\d+)\/merge/; 36 | if (pull_ref_regex.test(ref)) { 37 | return ref.replace(pull_ref_regex, "refs/pull/$1/head"); 38 | } else { 39 | return ref; 40 | } 41 | } 42 | 43 | export async function uploadSarif(sarif: Sarif) { 44 | const octokit = new Octokit({ auth: getRequiredInput("github-token") }); 45 | 46 | const [owner, repo] = getRequiredEnvParam("GITHUB_REPOSITORY").split("/"); 47 | const ref = getRef(); 48 | const commit_sha = getRequiredEnvParam("GITHUB_SHA"); 49 | const zipped_sarif = zlib.gzipSync(JSON.stringify(sarif)).toString("base64"); 50 | 51 | await octokit.request("POST /repos/:owner/:repo/code-scanning/sarifs", { 52 | owner, 53 | repo, 54 | commit_sha, 55 | ref, 56 | sarif: zipped_sarif, 57 | tool_name: "42Crunch REST API Static Security Testing", 58 | checkout_uri: url.pathToFileURL(process.cwd()).toString(), 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "NodeNext", 5 | "strict": false, 6 | "esModuleInterop": true, 7 | "moduleResolution": "NodeNext", 8 | "sourceMap": true, 9 | "outDir": "dist", 10 | "forceConsistentCasingInFileNames": true 11 | }, 12 | "include": ["src/**/*"], 13 | "exclude": ["node_modules", ".vscode-test"] 14 | } 15 | -------------------------------------------------------------------------------- /workflow.yaml: -------------------------------------------------------------------------------- 1 | # The REST API Static Security Testing action locates REST API contracts that follow the 2 | # OpenAPI Specification (OAS, formerly known as Swagger) and runs thorough security checks 3 | # on them. Both OAS v2 and v3.0.x are supported, in both JSON and YAML format. 4 | # 5 | # Documentation is located here: https://docs.42crunch.com/latest/content/tasks/integrate_github_actions.htm 6 | # 7 | # To use this workflow, you will need a customer/evaluation tenant on a 42Crunch SaaS platform. 8 | # 9 | # 1. Follow steps at https://docs.42crunch.com/latest/content/tasks/integrate_github_actions.htm 10 | # to create an API Token on the 42Crunch platform 11 | # 12 | # 2. Add a secret in GitHub as explained in https://docs.42crunch.com/latest/content/tasks/integrate_github_actions.htm, 13 | # store the 42Crunch API Token in that secret, and supply the secret's name as api-token parameter in this workflow 14 | # 15 | # If you have any questions or need help, contact: https://support.42crunch.com 16 | 17 | name: "42Crunch REST API Static Security Testing" 18 | 19 | # follow standard Code Scanning triggers 20 | on: 21 | push: 22 | branches: [ "main" ] 23 | pull_request: 24 | # The branches below must be a subset of the branches above 25 | branches: [ "main" ] 26 | schedule: 27 | - cron: '19 9 * * 6' 28 | 29 | jobs: 30 | rest-api-static-security-testing: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v3 34 | 35 | - name: 42Crunch REST API Static Security Testing 36 | uses: 42Crunch/api-security-audit-action@v4 37 | with: 38 | # Follow these steps to configure API_TOKEN https://docs.42crunch.com/latest/content/tasks/integrate_github_actions.htm 39 | api-token: ${{ secrets.API_TOKEN }} 40 | # Fail if any OpenAPI file scores lower than 75 41 | min-score: 75 42 | # Upload results to Github code scanning 43 | upload-to-code-scanning: false 44 | # Github token for uploading the results 45 | github-token: ${{ github.token }} 46 | --------------------------------------------------------------------------------