From 08622df7ed0b14c6bde79569ebb238da4caee0b4 Mon Sep 17 00:00:00 2001 From: Timo Behrendt Date: Tue, 10 Feb 2026 19:18:45 +0100 Subject: [PATCH 1/4] mvp --- README.md | 28 ++++++++ openapi.json | 131 ++++++++++++++++++++++++++++++++++++ tas-upload-sarif/README.md | 48 +++++++++++++ tas-upload-sarif/action.yml | 66 ++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 openapi.json create mode 100644 tas-upload-sarif/README.md create mode 100644 tas-upload-sarif/action.yml diff --git a/README.md b/README.md index 754d807..b482ac2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ # tas-actions +Reusable GitHub Actions for [TAS (Tea Advanced Security)](https://github.com/go-gitea/gitea): upload SARIF reports and gate CI on the API response. + +## Actions + +### [tas-upload-sarif](tas-upload-sarif/) + +Uploads a SARIF report from a file to TAS and **fails the job** if the API returns `allowed: false`. + +**Example workflow** (e.g. after a security scan that produces SARIF): + +```yaml +jobs: + scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Run your scanner and produce SARIF (e.g. to results.sarif) + # - run: ./run-scanner --output results.sarif + + - name: Upload SARIF to TAS and gate + uses: your-org/tas-actions/tas-upload-sarif@v1 + with: + tas-base-url: 'https://tas.example.com' + sarif-file: 'results.sarif' +``` + +See [tas-upload-sarif/README.md](tas-upload-sarif/README.md) for all inputs and options. \ No newline at end of file diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..cbcf7ec --- /dev/null +++ b/openapi.json @@ -0,0 +1,131 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "TAS — Tea Advanced Security", + "description": "Security reporting and gating API for Gitea. Upload SARIF reports per repo/branch; get allowed/new_findings for CI gating.", + "version": "1.0.0" + }, + "servers": [{ "url": "http://localhost:3000", "description": "Local" }], + "paths": { + "/repos/{owner}/{repo}/branches/{branch}/reports": { + "post": { + "operationId": "postReposByOwnerByRepoBranchesByBranchReports", + "description": "Upload a SARIF report for a repository and branch. Replaces any existing report. On non-default branches, compares to baseline and returns allowed/new_findings for CI gating.", + "request": { + "params": { + "owner": { + "description": "Repository owner (e.g. t.behrendt)", + "required": true + }, + "repo": { + "description": "Repository name (e.g. k_gitea)", + "required": true + }, + "branch": { "description": "Branch name", "required": true } + }, + "body": { + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "SARIF 2.1 document with runs[]" + } + } + } + } + }, + "responses": { + "200": { + "description": "Report stored and (for non-default branch) gating result", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "allowed": { "type": "boolean" }, + "reason": { "type": "string" }, + "new_critical": { "type": "number" }, + "new_high": { "type": "number" }, + "new_medium": { "type": "number" }, + "new_low": { "type": "number" }, + "new_findings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "rule_id": { "type": "string" }, + "level": { "type": "string" }, + "message": { "type": "string" }, + "severity": { + "type": "string", + "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"] + }, + "artifact_uri": { "type": "string" }, + "region": { + "type": "object", + "properties": { + "start_line": { + "anyOf": [ + { "type": "number" }, + { "type": "null" } + ] + }, + "start_column": { + "anyOf": [ + { "type": "number" }, + { "type": "null" } + ] + } + }, + "required": ["start_line", "start_column"] + } + }, + "required": [ + "rule_id", + "level", + "message", + "severity", + "artifact_uri", + "region" + ] + } + } + }, + "required": [ + "allowed", + "new_critical", + "new_high", + "new_medium", + "new_low", + "new_findings" + ] + } + } + } + } + }, + "parameters": [ + { + "schema": { "type": "string" }, + "in": "path", + "name": "owner", + "required": true + }, + { + "schema": { "type": "string" }, + "in": "path", + "name": "repo", + "required": true + }, + { + "schema": { "type": "string" }, + "in": "path", + "name": "branch", + "required": true + } + ] + } + } + }, + "components": {} +} diff --git a/tas-upload-sarif/README.md b/tas-upload-sarif/README.md new file mode 100644 index 0000000..51e627a --- /dev/null +++ b/tas-upload-sarif/README.md @@ -0,0 +1,48 @@ +# TAS Upload SARIF + +Reusable GitHub Action that uploads a SARIF report to [TAS (Tea Advanced Security)](https://github.com/go-gitea/gitea) and **fails the job** if the API responds with `allowed: false` (e.g. new findings above your policy). + +## Inputs + +| Input | Required | Description | +|-------|----------|-------------| +| `tas-base-url` | Yes | Base URL of the TAS API (e.g. `https://tas.example.com`) | +| `sarif-file` | Yes | Path to the SARIF report file (JSON) | +| `owner` | No | Repository owner (default: `github.repository_owner`) | +| `repo` | No | Repository name (default: `github.event.repository.name`) | +| `branch` | No | Branch name (default: `github.ref_name`) | + +## Usage + +```yaml +- name: Upload SARIF to TAS and gate + uses: your-org/tas-actions/tas-upload-sarif@v1 + with: + tas-base-url: 'https://tas.example.com' + sarif-file: 'results.sarif' +``` + +With explicit owner/repo/branch (e.g. for monorepos or custom refs): + +```yaml +- uses: your-org/tas-actions/tas-upload-sarif@v1 + with: + tas-base-url: ${{ vars.TAS_BASE_URL }} + sarif-file: 'scan-output.sarif' + owner: my-org + repo: my-repo + branch: ${{ github.head_ref }} +``` + +## Behavior + +1. **POST**s the SARIF file to `{tas-base-url}/repos/{owner}/{repo}/branches/{branch}/reports`. +2. On HTTP non-200, the step fails and prints the response body. +3. On success, reads the JSON response: + - If `allowed` is `true`, the step succeeds. + - If `allowed` is `false`, the step **fails** and prints `reason` and the full response (e.g. `new_critical`, `new_high`, `new_findings`). + +## Requirements + +- **GitHub-hosted runners**: `curl` and `jq` are available by default. +- **Self-hosted runners**: ensure `curl` and `jq` are installed. diff --git a/tas-upload-sarif/action.yml b/tas-upload-sarif/action.yml new file mode 100644 index 0000000..53e7362 --- /dev/null +++ b/tas-upload-sarif/action.yml @@ -0,0 +1,66 @@ +name: 'TAS Upload SARIF' +description: 'Upload a SARIF report to TAS (Tea Advanced Security) and fail the job if gating returns allowed: false' +inputs: + tas-base-url: + description: 'Base URL of the TAS API (e.g. https://tas.example.com)' + required: true + sarif-file: + description: 'Path to the SARIF report file (JSON)' + required: true + owner: + description: 'Repository owner (default: GitHub repository owner)' + required: false + repo: + description: 'Repository name (default: GitHub repository name)' + required: false + branch: + description: 'Branch name (default: current ref name, e.g. main)' + required: false +runs: + using: 'composite' + steps: + - name: Upload SARIF to TAS and gate + shell: bash + env: + OWNER: ${{ inputs.owner || github.repository_owner }} + REPO: ${{ inputs.repo || github.event.repository.name }} + BRANCH: ${{ inputs.branch || github.ref_name }} + BASE_URL: ${{ inputs.tas-base-url }} + SARIF_FILE: ${{ inputs.sarif-file }} + run: | + BASE_URL="${BASE_URL%/}" + URL="${BASE_URL}/repos/${OWNER}/${REPO}/branches/${BRANCH}/reports" + echo "Uploading SARIF to TAS: $URL" + + if [[ ! -f "$SARIF_FILE" ]]; then + echo "::error::SARIF file not found: $SARIF_FILE" + exit 1 + fi + + RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$URL" \ + -H "Content-Type: application/json" \ + -d @"$SARIF_FILE") + + HTTP_BODY=$(echo "$RESPONSE" | head -n -1) + HTTP_CODE=$(echo "$RESPONSE" | tail -n 1) + + if [[ "$HTTP_CODE" != "200" ]]; then + echo "::error::TAS API returned HTTP $HTTP_CODE" + echo "$HTTP_BODY" | head -20 + exit 1 + fi + + ALLOWED=$(echo "$HTTP_BODY" | jq -r '.allowed') + REASON=$(echo "$HTTP_BODY" | jq -r '.reason // empty') + + if [[ "$ALLOWED" != "true" ]]; then + echo "::error::TAS gating failed (allowed: false). $REASON" + echo "::error::new_critical/new_high/new_medium/new_low are in the API response." + echo "$HTTP_BODY" | jq '.' + exit 1 + fi + + echo "TAS gating passed (allowed: true)." + if [[ -n "$REASON" ]]; then + echo "$REASON" + fi -- 2.49.1 From 97a422dacf2c7aedb7c62160aacd2f76b8de9f45 Mon Sep 17 00:00:00 2001 From: Timo Behrendt Date: Wed, 11 Feb 2026 19:51:15 +0100 Subject: [PATCH 2/4] finalize --- openapi.json | 131 ------------------------------------- tas-upload-sarif/README.md | 26 ++++---- 2 files changed, 13 insertions(+), 144 deletions(-) delete mode 100644 openapi.json diff --git a/openapi.json b/openapi.json deleted file mode 100644 index cbcf7ec..0000000 --- a/openapi.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "TAS — Tea Advanced Security", - "description": "Security reporting and gating API for Gitea. Upload SARIF reports per repo/branch; get allowed/new_findings for CI gating.", - "version": "1.0.0" - }, - "servers": [{ "url": "http://localhost:3000", "description": "Local" }], - "paths": { - "/repos/{owner}/{repo}/branches/{branch}/reports": { - "post": { - "operationId": "postReposByOwnerByRepoBranchesByBranchReports", - "description": "Upload a SARIF report for a repository and branch. Replaces any existing report. On non-default branches, compares to baseline and returns allowed/new_findings for CI gating.", - "request": { - "params": { - "owner": { - "description": "Repository owner (e.g. t.behrendt)", - "required": true - }, - "repo": { - "description": "Repository name (e.g. k_gitea)", - "required": true - }, - "branch": { "description": "Branch name", "required": true } - }, - "body": { - "content": { - "application/json": { - "schema": { - "type": "object", - "description": "SARIF 2.1 document with runs[]" - } - } - } - } - }, - "responses": { - "200": { - "description": "Report stored and (for non-default branch) gating result", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "allowed": { "type": "boolean" }, - "reason": { "type": "string" }, - "new_critical": { "type": "number" }, - "new_high": { "type": "number" }, - "new_medium": { "type": "number" }, - "new_low": { "type": "number" }, - "new_findings": { - "type": "array", - "items": { - "type": "object", - "properties": { - "rule_id": { "type": "string" }, - "level": { "type": "string" }, - "message": { "type": "string" }, - "severity": { - "type": "string", - "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"] - }, - "artifact_uri": { "type": "string" }, - "region": { - "type": "object", - "properties": { - "start_line": { - "anyOf": [ - { "type": "number" }, - { "type": "null" } - ] - }, - "start_column": { - "anyOf": [ - { "type": "number" }, - { "type": "null" } - ] - } - }, - "required": ["start_line", "start_column"] - } - }, - "required": [ - "rule_id", - "level", - "message", - "severity", - "artifact_uri", - "region" - ] - } - } - }, - "required": [ - "allowed", - "new_critical", - "new_high", - "new_medium", - "new_low", - "new_findings" - ] - } - } - } - } - }, - "parameters": [ - { - "schema": { "type": "string" }, - "in": "path", - "name": "owner", - "required": true - }, - { - "schema": { "type": "string" }, - "in": "path", - "name": "repo", - "required": true - }, - { - "schema": { "type": "string" }, - "in": "path", - "name": "branch", - "required": true - } - ] - } - } - }, - "components": {} -} diff --git a/tas-upload-sarif/README.md b/tas-upload-sarif/README.md index 51e627a..b64e2bd 100644 --- a/tas-upload-sarif/README.md +++ b/tas-upload-sarif/README.md @@ -4,33 +4,33 @@ Reusable GitHub Action that uploads a SARIF report to [TAS (Tea Advanced Securit ## Inputs -| Input | Required | Description | -|-------|----------|-------------| -| `tas-base-url` | Yes | Base URL of the TAS API (e.g. `https://tas.example.com`) | -| `sarif-file` | Yes | Path to the SARIF report file (JSON) | -| `owner` | No | Repository owner (default: `github.repository_owner`) | -| `repo` | No | Repository name (default: `github.event.repository.name`) | -| `branch` | No | Branch name (default: `github.ref_name`) | +| Input | Required | Description | +| -------------- | -------- | --------------------------------------------------------- | +| `tas-base-url` | Yes | Base URL of the TAS API (e.g. `https://tas.example.com`) | +| `sarif-file` | Yes | Path to the SARIF report file (JSON) | +| `owner` | No | Repository owner (default: `github.repository_owner`) | +| `repo` | No | Repository name (default: `github.event.repository.name`) | +| `branch` | No | Branch name (default: `github.ref_name`) | ## Usage ```yaml - name: Upload SARIF to TAS and gate - uses: your-org/tas-actions/tas-upload-sarif@v1 + uses: https://gitea.t000-n.de/t.behrendt/tas-actions/tas-upload-sarif@v1 with: - tas-base-url: 'https://tas.example.com' - sarif-file: 'results.sarif' + tas-base-url: "https://tas.example.com" + sarif-file: "results.sarif" ``` With explicit owner/repo/branch (e.g. for monorepos or custom refs): ```yaml -- uses: your-org/tas-actions/tas-upload-sarif@v1 +- uses: [your-org/tas-actions/tas-upload-sarif@v1](https://gitea.t000-n.de/t.behrendt/tas-actions/tas-upload-sarif@v1) with: tas-base-url: ${{ vars.TAS_BASE_URL }} sarif-file: 'scan-output.sarif' - owner: my-org - repo: my-repo + owner: ${{ github.repository_owner}} + repo: ${{ github.event.repository.name }} branch: ${{ github.head_ref }} ``` -- 2.49.1 From 5241e79eaa4742a20072439ad3291127a79a2b28 Mon Sep 17 00:00:00 2001 From: Timo Behrendt Date: Wed, 11 Feb 2026 19:52:26 +0100 Subject: [PATCH 3/4] add cd --- .gitea/workflows/cd.yaml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .gitea/workflows/cd.yaml diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml new file mode 100644 index 0000000..2c8d5fd --- /dev/null +++ b/.gitea/workflows/cd.yaml @@ -0,0 +1,25 @@ +name: CD + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.2 + with: + fetch-depth: 0 + - name: Increment tag + id: tag + uses: https://gitea.t000-n.de/t.behrendt/conventional-semantic-git-tag-increment@af46017d0af5fd6af4425f8e6961f14280a1acd1 # 0.1.26 + with: + token: ${{ secrets.GITEA_TOKEN }} + - name: Push tag + uses: ./release-git-tag + with: + tag: ${{ steps.tag.outputs.new-tag }} -- 2.49.1 From fa85efd82c66ddd6e0ecae29ff249c2a8eb60abc Mon Sep 17 00:00:00 2001 From: Timo Behrendt Date: Wed, 11 Feb 2026 19:53:11 +0100 Subject: [PATCH 4/4] add renovate --- renovate.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..f275018 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["local>t.behrendt/renovate-configs:action"] +} -- 2.49.1