-
Notifications
You must be signed in to change notification settings - Fork 0
155 lines (144 loc) · 6.12 KB
/
Copy pathsecurity.yml
File metadata and controls
155 lines (144 loc) · 6.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
name: Security Scan
# Duas camadas de SAST:
# 1. Rulesets genéricos do Semgrep (PHP, OWASP, secrets, JS).
# 2. Regras específicas de Flarum v2 (.github/semgrep/flarum-v2.yaml) —
# derivadas do playbook de segurança CLAUDE.md (§2–§37).
# CodeQL (JS/TS) fica no codeql.yml.
on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: '0 7 * * 1' # toda segunda, 07:00 UTC
workflow_dispatch:
# §35.13 C1 — default-deny.
permissions: {}
concurrency:
group: security-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
semgrep:
name: Semgrep (SAST genérico + regras Flarum v2)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # upload SARIF
actions: read
container:
# 1.96.0 saía com exit ≠ 0 na camada 2 (ruleset custom) sem escrever o
# SARIF — a etapa de upload então falhava com "Path does not exist".
# 1.163.0 roda as 22 regras e gera o arquivo (verificado localmente:
# `semgrep --validate` => 0 erros; `semgrep scan` => SARIF de 19 achados).
image: semgrep/semgrep:1.163.0
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# Histórico completo: o scan diff-aware da camada 2 precisa do commit
# base disponível para comparar (--baseline-commit).
fetch-depth: 0
# ---- Camada 1: rulesets curados pelo time Semgrep ------------------
- name: Semgrep — rulesets genéricos
run: |
set +e
semgrep scan \
--config p/php \
--config p/security-audit \
--config p/owasp-top-ten \
--config p/secrets \
--config p/javascript \
--exclude='js/dist' \
--exclude='*.min.js' \
--sarif --sarif-output=semgrep-generic.sarif \
--metrics=off
echo "Semgrep (genérico) exit code: $?"
# Garante que o arquivo existe mesmo se o semgrep crashar antes de escrevê-lo.
[ -f semgrep-generic.sarif ] || echo '{"version":"2.1.0","runs":[]}' > semgrep-generic.sarif
ls -l semgrep-generic.sarif
exit 0
# Achado com `nosemgrep` na linha já não entra no exit code do
# semgrep, mas ainda vai para o SARIF marcado como `inSource`. O bot
# de review do GitHub anota mesmo assim, enchendo o PR de alerta em
# linha que carrega a justificativa ao lado. Filtramos só a cópia
# destinada ao code scanning; o SARIF íntegro segue no artefato.
- name: Filtrar achados suprimidos do upload — genérico
if: always()
run: |
IN=semgrep-generic.sarif
OUT=semgrep-generic-clean.sarif
python3 .github/semgrep/strip-suppressed.py "$IN" "$OUT" || cp "$IN" "$OUT"
- name: Upload SARIF — genérico
if: always()
# Code scanning pode não estar habilitado (repo privado sem GitHub
# Advanced Security) — nesse caso o upload emite warning. O artefato
# ao final do job é o canal de resultados que sempre funciona.
continue-on-error: true
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: semgrep-generic-clean.sarif
category: semgrep
# ---- Camada 2: regras específicas de Flarum v2 --------------------
# BLOQUEANTE em diff-aware: num PR, falha se houver achado NOVO vs. o
# commit base (--baseline-commit ... --error). Em push/schedule, varre
# tudo mas NÃO reprova — os achados legados ficam informativos no SARIF.
# Assim o gate aperta o código novo (inclui o gerado por IA) sem travar
# no legado, no mesmo espírito do baseline do PHPStan.
- name: Semgrep — regras Flarum v2 (CLAUDE.md)
env:
EVENT: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
set +e
BASELINE_ARGS=""
BLOCK=0
if [ "$EVENT" = "pull_request" ] && [ -n "$BASE_SHA" ]; then
BASELINE_ARGS="--baseline-commit $BASE_SHA --error"
BLOCK=1
fi
semgrep scan \
--config .github/semgrep/flarum-v2.yaml \
--exclude='js/dist' \
--exclude='*.min.js' \
$BASELINE_ARGS \
--sarif --sarif-output=semgrep-flarum.sarif \
--metrics=off
EXIT=$?
echo "Semgrep (Flarum v2) exit code: $EXIT (block=$BLOCK)"
# Garante que o arquivo existe mesmo se o semgrep crashar antes de escrevê-lo.
[ -f semgrep-flarum.sarif ] || echo '{"version":"2.1.0","runs":[]}' > semgrep-flarum.sarif
ls -l semgrep-flarum.sarif
# Em PR (BLOCK=1): propaga o exit do semgrep (≠0 = achado novo).
# Em push/schedule: informativo, nunca reprova.
[ "$BLOCK" = "1" ] && exit $EXIT
exit 0
# Mesmo motivo da camada 1 — ver o comentário lá.
- name: Filtrar achados suprimidos do upload — Flarum v2
if: always()
run: |
IN=semgrep-flarum.sarif
OUT=semgrep-flarum-clean.sarif
python3 .github/semgrep/strip-suppressed.py "$IN" "$OUT" || cp "$IN" "$OUT"
- name: Upload SARIF — Flarum v2
if: always()
continue-on-error: true
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: semgrep-flarum-clean.sarif
category: flarum-v2-security
# Canal de resultados independente do code scanning estar ligado:
# baixável em Actions → run → Artifacts. Inclui as duas camadas.
- name: Upload SARIF como artefato
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: semgrep-sarif
path: |
semgrep-generic.sarif
semgrep-flarum.sarif
semgrep-generic-clean.sarif
semgrep-flarum-clean.sarif
retention-days: 30
if-no-files-found: warn