diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflows/setup-repository.yml b/.github/workflows/setup-repository.yml new file mode 100644 index 0000000..80324e7 --- /dev/null +++ b/.github/workflows/setup-repository.yml @@ -0,0 +1,230 @@ +name: Setup repository from template + +on: + workflow_dispatch: + inputs: + visibility: + description: 'Visibility' + required: true + default: 'private' + type: choice + options: + - private + - public + trufflehog: + description: 'Trufflehog' + required: true + default: true + type: boolean + semgrep: + description: 'Semgrep' + required: true + default: true + type: boolean + python: + description: 'Python/Jupyter Notebook' + required: true + default: false + type: boolean + javascript: + description: 'TypeScript/JavaScript' + required: true + default: false + type: boolean + terraform: + description: 'Terraform' + required: true + default: false + type: boolean + golang: + description: 'Go' + required: true + default: false + type: boolean +jobs: + common-setup: + name: Common Setup + outputs: + run_jobs: ${{ steps.check-template.outputs.run_jobs}} + runs-on: ubuntu-22.04 + env: + REPO_SETUP_TOKEN: ${{ secrets.REPO_SETUP_TOKEN }} + steps: + - name: Do not run setup on template repository + id: check-template + shell: bash {0} + # Using the GitHub rest API allows us to identify if the current repository + # is a template repository or not. + run: | + not_template=$(curl --silent -X GET -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github+json" https://api.github.com/repos/$GITHUB_REPOSITORY | jq --exit-status '.is_template == false'); + echo "run_jobs=$not_template" >> $GITHUB_OUTPUT + - uses: actions/checkout@v3 + with: + # Cannot use the built-in $GITHUB_TOKEN since we need webhook permission + token: ${{ env.REPO_SETUP_TOKEN }} + + ### RESTRICT RUNNABLE GITHUB ACTIONS + - name: Set runnable actions to 'selected' + shell: bash + run: | + curl -X PUT -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ env.REPO_SETUP_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/$GITHUB_REPOSITORY/actions/permissions -d '{"enabled":true,"allowed_actions":"selected"}' + - name: Restrict runnable actions + shell: bash + run: | + curl -X PUT -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ env.REPO_SETUP_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/$GITHUB_REPOSITORY/actions/permissions/selected-actions -d '{"github_owned_allowed":true,"verified_allowed":false,"patterns_allowed":["trufflesecurity/trufflehog@v3.26.0","returntocorp/semgrep", "tenable/terrascan-action@main"]}' + + ### PYTHON SETUP + - name: python-setup + if: ${{ inputs.python && steps.check-template.outputs.run_jobs == 'true' }} + shell: bash + # Copy the bandit action workflow file to the appropriate location + run: | + cp template-files/python/bandit-ci.yml .github/workflows/bandit-ci.yml + cat template-files/python/.gitignore >> .gitignore + + ### JS/TS SETUP + - name: javascript-setup + if: ${{ inputs.javascript && steps.check-template.outputs.run_jobs == 'true' }} + shell: bash + run: | + cat template-files/js/.gitignore >> .gitignore + + ### TERRAFORM SETUP + - name: terraform-setup + if: ${{ inputs.terraform && steps.check-template.outputs.run_jobs == 'true' }} + shell: bash + run: | + cat template-files/terraform/.gitignore >> .gitignore + cp template-files/terraform/atlantis.yaml atlantis.yaml + cp template-files/terraform/terrascan-ci.yml .github/workflows/terrascan-ci.yml + + ### TEMPLATE FILE + - name: move-template-file + if: ${{ github.event.inputs.terraform == 'true' || github.event.inputs.python == 'true' }} + id: move-output-template + shell: bash + # Copy the logging template file to the workflows folder + run: | + cp template-files/common/output-template.json .github/workflows/output-template.json + + ### GOLANG SETUP + - name: golang-setup + if: ${{ inputs.golang && steps.check-template.outputs.run_jobs == 'true' }} + shell: bash + run: | + cat template-files/go/.gitignore >> .gitignore + + - name: commit-job-changes + shell: bash {0} + # Commit the changes we've made for this job since artifacting all of them would be difficult + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" && \ + git config --global user.name "github-actions[bot]" && \ + git add --all + if ! git diff-index --quiet HEAD; then + git commit -m 'Repository Setup' + git push origin main -f + fi + + cleanup: + name: Cleanup + needs: [common-setup] + if: ${{ needs.common-setup.outputs.run_jobs == 'true' }} + env: + REPO_SETUP_TOKEN: ${{ secrets.REPO_SETUP_TOKEN }} + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + with: + # Cannot use the built-in $GITHUB_TOKEN since we need webhook permission + token: ${{ env.REPO_SETUP_TOKEN }} + # include the ref to the default branch so we get the changes from the previous jobs + ref: main + - name: Clean up template files + shell: bash + run: | + rm -rf template-files + rm -f .github/workflows/setup-repository.yml + - name: Reinitialize git repository + shell: bash + # We use `git checkout --orphan` to create a branch in a git init-like state and get a clean history + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" && \ + git config --global user.name "github-actions[bot]" && \ + git checkout --orphan temp-branch && \ + git add . && \ + git commit -m 'Repository Setup' && \ + git push origin temp-branch:main -f + - name: Protect main branch + shell: bash + run: | + curl -X PUT -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ env.REPO_SETUP_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" https://api.github.com/repos/$GITHUB_REPOSITORY/branches/main/protection -d '{"required_status_checks":null,"enforce_admins":null,"required_pull_request_reviews":{"dismissal_restrictions":{"users":[],"teams":[],"apps":[]},"dismiss_stale_reviews":false,"require_code_owner_reviews":false,"required_approving_review_count":1,"require_last_push_approval":false,"bypass_pull_request_allowances":{"users":[],"teams":["security-eng","platform-eng"]}},"restrictions":null,"required_linear_history":false,"allow_force_pushes":false,"allow_deletions":false,"block_creations":false,"required_conversation_resolution":true,"lock_branch":false,"allow_fork_syncing":false}' + ### This must be done at the end of the workflow after all changes have been committed due to org-wide restrictions + ### SEMGREP SETUP USING REPOSITORY RULESETS + - name: install-semgrep-action + if: ${{ github.event.inputs.semgrep == 'true' }} + id: install-semgrep-action + shell: bash + # id 279250 represents semgrep ruleset + # ids_cleaned is a necessary step, since bash naturally delimits by newline, which breaks single-line read + run: | + raw=$(curl -L -X GET \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ env.REPO_SETUP_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/orgs/scaleapi/rulesets/279250) + ids=$(echo "$raw" | jq '.conditions.repository_id.repository_ids[]?') + ids_cleaned=${ids//$'\n'/ } + ids_cleaned=${ids_cleaned//$'\r'/ } + refs=$(echo "$raw" | jq '.conditions.ref_name') + names=$(echo "$raw" | jq '.conditions.repository_name.include') + read -a id_array <<< $ids_cleaned + echo 'Beginning with '${#id_array[*]}' repositories.' + id_array+=(${{ github.repository_id }}) + echo 'Now there are '${#id_array[*]}' repositories.' + json_ids=$(jq --compact-output --null-input '$ARGS.positional' --args -- "${id_array[@]}" | tr -d "\"") + body=$(echo '{"conditions": { "ref_name": '$refs', "repository_id": {"repository_ids": '"${json_ids//\" /}"'}}}') + curl -L -X PUT \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ env.REPO_SETUP_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/orgs/scaleapi/rulesets/279250 \ + -d "$body" + ### TRUFFLEHOG SETUP USING REPOSITORY RULESETS + - name: install-trufflehog-action + if: ${{ github.event.inputs.trufflehog == 'true' }} + id: install-trufflehog-action + shell: bash + # id 279251 represents trufflehog ruleset + # ids_cleaned is a necessary step, since bash naturally delimits by newline, which breaks single-line read + run: | + raw=$(curl -L -X GET \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ env.REPO_SETUP_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/orgs/scaleapi/rulesets/279251) + ids=$(echo "$raw" | jq '.conditions.repository_id.repository_ids[]?') + ids_cleaned=${ids//$'\n'/ } + ids_cleaned=${ids_cleaned//$'\r'/ } + refs=$(echo "$raw" | jq '.conditions.ref_name') + names=$(echo "$raw" | jq '.conditions.repository_name.include') + read -a id_array <<< $ids_cleaned + id_array+=(${{ github.repository_id }}) + json_ids=$(jq --compact-output --null-input '$ARGS.positional' --args -- "${id_array[@]}" | tr -d "\"") + body=$(echo '{"conditions": { "ref_name": '$refs', "repository_id": {"repository_ids": '"${json_ids//\" /}"'}}}') + curl -L -X PUT \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ env.REPO_SETUP_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/orgs/scaleapi/rulesets/279251 \ + -d "$body" + - name: Remove secret REPO_SETUP_TOKEN + # After re-initializing the repository, we can remove the `REPO_SETUP_TOKEN` secret since it has permissions we don't want to sit around in the repository + shell: bash + if: ${{ env.REPO_SETUP_TOKEN }} + run: | + curl \ + -X DELETE --fail \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: Bearer ${{ env.REPO_SETUP_TOKEN }}" \ + https://api.github.com/repos/$GITHUB_REPOSITORY/actions/secrets/REPO_SETUP_TOKEN diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd1f763 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Logs +logs +*.log +npm-debug.log* +*.pth + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# IntelliJ +**/.idea +*.iml + +# VSCode +.vscode +*.code-workspace + +# filesystem files +.DS_Store + +# Local environment files +*.env +.env.* +*.envrc +frontend/.npmrc +local*.yaml + +# filesystem databases +dump.rdb +*.sqlite +*.db + +# Temp dirs +tmp diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..79378b6 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# repository-template +A repository template for repository creation at Scale AI. + +## Usage +### Automatic +Request a new repository from the slackbot `Onyx` using `/onyx` and input the appropriate information such as desired language(s) + +### Manual +Requires repository creation permissions and an appropriately-permissioned REPO_SETUP_TOKEN + +1. Create a new repository using this template +2. Add a secret `REPO_SETUP_TOKEN` to the new repository +3. Run the GitHub workflow `repository-setup`, inputting parameters as desired. +4. Allow the workflow to run and set up language-specific files and settings. diff --git a/template-files/common/output-template.json b/template-files/common/output-template.json new file mode 100644 index 0000000..e6303bc --- /dev/null +++ b/template-files/common/output-template.json @@ -0,0 +1,13 @@ +{ + "source": "github", + "organization": "\($organization)", + "timestamp": "\($time)", + "action": "\($action)", + "meta": { + "repository": "\($repository)", + "commit": "\($sha)", + "branch": "\($branch)", + "link": "\($link)" + }, + "results": [] +} diff --git a/template-files/go/.gitignore b/template-files/go/.gitignore new file mode 100644 index 0000000..30debfa --- /dev/null +++ b/template-files/go/.gitignore @@ -0,0 +1,18 @@ + +### GOLANG + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Go workspace file +go.work diff --git a/template-files/js/.gitignore b/template-files/js/.gitignore new file mode 100644 index 0000000..1fc4ae5 --- /dev/null +++ b/template-files/js/.gitignore @@ -0,0 +1,138 @@ + +### TYPESCRIPT/JAVASCRIPT + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +typings/ +lib/*.js +test/*.js +*.map diff --git a/template-files/python/.gitignore b/template-files/python/.gitignore new file mode 100644 index 0000000..38e7f52 --- /dev/null +++ b/template-files/python/.gitignore @@ -0,0 +1,136 @@ + +### PYTHON + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints +_temp_extension +junit.xml +[uU]ntitled* +notebook/static/* +!notebook/static/favicons +notebook/labextension +notebook/schemas +docs/source/changelog.md +docs/source/contributing.md + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# pdm +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/template-files/python/bandit-ci.yml b/template-files/python/bandit-ci.yml new file mode 100644 index 0000000..0b93e20 --- /dev/null +++ b/template-files/python/bandit-ci.yml @@ -0,0 +1,63 @@ +name: Bandit + +on: + # Scan changed files in PRs: + pull_request: {} + +jobs: + bandit-scan: + name: Bandit + runs-on: ubuntu-22.04 + if: (github.actor != 'dependabot[bot]') && (github.actor != 'github-actions[bot]') + steps: + - name: Install PyCQA/bandit + shell: bash + run: | + pip install bandit + - name: Checkout base branch + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 1 + submodules: false + - name: Run a baseline scan + shell: bash + run: | + bandit --recursive --aggregate file . -f json -o baseline.json || true + - name: Checkout feature branch + shell: bash + run: | + git fetch origin $GITHUB_HEAD_REF + git checkout $GITHUB_HEAD_REF + - name: Run Scan off of baseline + shell: bash + run: | + bandit --recursive --aggregate file . --baseline baseline.json -f json -o results.json || true + - name: Install logging prerequisites + shell: bash {0} + run: | + sudo apt-get -y install jq curl + - name: Generate logger template + shell: bash {0} # don't fail the job if the logging fails + run: | + jq -n --arg organization $GITHUB_REPOSITORY_OWNER \ + -n --arg time $( date +'%Y-%m-%dT%H:%M:%SZ' ) \ + -n --arg action $GITHUB_WORKFLOW \ + -n --arg repository $GITHUB_REPOSITORY \ + -n --arg sha $GITHUB_SHA \ + -n --arg branch $GITHUB_HEAD_REF \ + -n --arg link "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ + -f .github/workflows/output-template.json > tmp-output.json + - name: Format results appropriately from results.json + shell: bash {0} # don't fail the job if the logging fails + run: | + jq '.results | map({"path": .filename, "message": .issue_text, "line": .line_number})' results.json > tmp.json + jq --argjson scanResults "$( output.json + - name: Send unified results to logging cluster + shell: bash {0} # don't fail the job if the logging fails + run: | + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${{ secrets.N8N_PRODSEC_ACTIONS_TOKEN }}" \ + -d @./output.json \ + ${{ secrets.N8N_PRODSEC_ACTIONS_ENDPOINT }} diff --git a/template-files/terraform/.gitignore b/template-files/terraform/.gitignore new file mode 100644 index 0000000..9b8a46e --- /dev/null +++ b/template-files/terraform/.gitignore @@ -0,0 +1,34 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc diff --git a/template-files/terraform/atlantis.yaml b/template-files/terraform/atlantis.yaml new file mode 100644 index 0000000..e69de29 diff --git a/template-files/terraform/terrascan-ci.yml b/template-files/terraform/terrascan-ci.yml new file mode 100644 index 0000000..ee931dd --- /dev/null +++ b/template-files/terraform/terrascan-ci.yml @@ -0,0 +1,56 @@ +name: Terrascan + +on: + # Scan changed files in PRs: + pull_request: {} + # Scan pushes to mainline branches and report all findings: + push: + branches: ["master", "main"] + +jobs: + terrascan_job: + runs-on: ubuntu-22.04 + name: terrascan-action + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Terrascan + id: install-terrascan + # we are version-pinned to v1.18.0 + run: | + curl -L "$(curl -s https://api.github.com/repos/tenable/terrascan/releases/91462438 | grep -o -E "https://.+?_Linux_x86_64.tar.gz")" > terrascan.tar.gz + tar -xf terrascan.tar.gz terrascan && rm terrascan.tar.gz + install terrascan /usr/local/bin && rm terrascan + - name: Run Terrascan + id: run-terrascan + # Terrascan outputs a failure (code 4) on scan errors even when the scan was successful due to rules. For now, ignore this with || true + run: | + terrascan scan . --output json | tee results.json || true + - name: Install logging prerequisites into container + shell: bash {0} + run: | + sudo apt-get -y install jq curl + - name: Generate logger template + shell: bash {0} # don't fail the job if the logging fails + run: | + jq -n --arg organization $GITHUB_REPOSITORY_OWNER \ + -n --arg time $( date +'%Y-%m-%dT%H:%M:%SZ' ) \ + -n --arg action $GITHUB_WORKFLOW \ + -n --arg repository $GITHUB_REPOSITORY \ + -n --arg sha $GITHUB_SHA \ + -n --arg branch $GITHUB_HEAD_REF \ + -n --arg link "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ + -f .github/workflows/output-template.json > tmp-output.json + - name: Format results appropriately from results.json + shell: bash {0} # don't fail the job if the logging fails + run: | + jq 'if .results.violations and (.results.violations != null) then .results.violations | map({"path": .file, "message": .description, "line": .line}) else [] end' results.json > tmp.json + jq --argjson scanResults "$( output.json + - name: Send unified results to logging cluster + shell: bash {0} # don't fail the job if the logging fails + run: | + curl -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${{ secrets.N8N_PRODSEC_ACTIONS_TOKEN }}" \ + -d @./output.json \ + ${{ secrets.N8N_PRODSEC_ACTIONS_ENDPOINT }}