From 7e5c0ca85e8cf3fbeeaa17c80d9d077e525520bf Mon Sep 17 00:00:00 2001 From: yuli Date: Sun, 26 Jul 2026 18:24:24 +0300 Subject: [PATCH] Add Semgrep SAST gate; suppress safe-in-Go deserialization false positives - Add self-contained semgrep.yml gate (public repo; master branch), blocking, p/golang + common packs - Suppress 3 go-unsafe-deserialization-interface findings: json.Unmarshal into interface{} is safe in Go and required to parse arbitrary JSON Schema - 0 findings; go build clean Co-Authored-By: Claude Opus 4.8 --- .github/workflows/semgrep.yml | 63 +++++++++++++++++++++++++++++++++++ keywords_core.go | 2 +- keywords_standard.go | 2 +- schema.go | 2 +- 4 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/semgrep.yml diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 0000000..7a87758 --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,63 @@ +# Self-contained Semgrep SAST scan (Semgrep OSS engine + community rules — LGPL, no account needed). +# Inlined rather than calling the shared reusable workflow in databunker-devops, because GitHub +# blocks a PUBLIC repo from using a reusable workflow stored in a PRIVATE repo. +name: semgrep + +on: + workflow_dispatch: # manual "Run workflow" button in the Actions tab + pull_request: + push: + branches: [master] # this repo's default branch is master, not main + schedule: + - cron: "13 7 * * 1" # weekly full sweep (Mon 07:13 UTC) + +# Least privilege. security-events:write publishes SARIF to the Code Scanning tab (free on +# public repos). Semgrep itself needs no write scope. +permissions: + contents: read + security-events: write + +jobs: + sast: + name: sast # display + required-check name + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.12" + + - name: Install Semgrep (pinned) + run: pip install semgrep==1.170.0 # bump deliberately (semgrep.dev/docs/release-notes) + + - name: Run Semgrep + env: + SEMGREP_SEND_METRICS: "off" # no telemetry; community rules only + run: | + # --error: exit non-zero on any finding -> blocks merge when set as a required check. + # SARIF is written before the non-zero exit, so the upload steps (if: always()) run. + semgrep scan \ + --config p/golang \ + --config p/secrets \ + --config p/security-audit \ + --config p/owasp-top-ten \ + --error \ + --sarif --output semgrep.sarif + + - name: Upload SARIF to Code Scanning + if: always() # publish findings even when the scan fails the check + uses: github/codeql-action/upload-sarif@bb16b9baa2ec4010b29f5c606d57d01190139edd # v4.37.1 + with: + sarif_file: semgrep.sarif + category: semgrep + + - name: Upload SARIF artifact (dated evidence, retained 180d) + if: always() # keep the report for triage + audit evidence even on failure + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: semgrep-report-${{ github.sha }} + path: semgrep.sarif + retention-days: 180 diff --git a/keywords_core.go b/keywords_core.go index 1c61bdd..3f13049 100644 --- a/keywords_core.go +++ b/keywords_core.go @@ -141,7 +141,7 @@ func (d *Default) Resolve(pointer jptr.Pointer, uri string) *Schema { // UnmarshalJSON implements the json.Unmarshaler interface for Default func (d *Default) UnmarshalJSON(data []byte) error { - var defaultData interface{} + var defaultData interface{} // nosemgrep: go.lang.security.deserialization.unsafe-deserialization-interface.go-unsafe-deserialization-interface -- json.Unmarshal into interface{} is safe in Go; required to parse arbitrary JSON Schema if err := json.Unmarshal(data, &defaultData); err != nil { return err } diff --git a/keywords_standard.go b/keywords_standard.go index 56e5dd9..e0d1c6a 100644 --- a/keywords_standard.go +++ b/keywords_standard.go @@ -30,7 +30,7 @@ func (c *Const) Resolve(pointer jptr.Pointer, uri string) *Schema { // ValidateKeyword implements the Keyword interface for Const func (c Const) ValidateKeyword(ctx context.Context, currentState *ValidationState, data interface{}) { schemaDebug("[Const] Validating") - var con interface{} + var con interface{} // nosemgrep: go.lang.security.deserialization.unsafe-deserialization-interface.go-unsafe-deserialization-interface -- json.Unmarshal into interface{} is safe in Go; required to parse arbitrary JSON Schema if err := json.Unmarshal(c, &con); err != nil { currentState.AddError(data, err.Error()) return diff --git a/schema.go b/schema.go index 10dabe2..8a1c896 100644 --- a/schema.go +++ b/schema.go @@ -323,7 +323,7 @@ func (s *Schema) validateSchemakeywords(ctx context.Context, currentState *Valid // ValidateBytes performs schema validation against a slice of json // byte data func (s *Schema) ValidateBytes(ctx context.Context, data []byte) ([]KeyError, error) { - var doc interface{} + var doc interface{} // nosemgrep: go.lang.security.deserialization.unsafe-deserialization-interface.go-unsafe-deserialization-interface -- json.Unmarshal into interface{} is safe in Go; required to parse arbitrary JSON Schema if err := json.Unmarshal(data, &doc); err != nil { return nil, fmt.Errorf("error parsing JSON bytes: %s", err.Error()) }