-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path.coderabbit.yaml
More file actions
1228 lines (1051 loc) · 59.6 KB
/
Copy path.coderabbit.yaml
File metadata and controls
1228 lines (1051 loc) · 59.6 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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
#
# CodeRabbit organization config for the `corazawaf` GitHub org.
#
# Covers five repositories that are five different kinds of codebase, not one:
# - coraza — the WAF engine itself, Go: the SecLang directive parser,
# the phase/transaction pipeline, operators, actions,
# transformations, body processors, audit logging. It
# implements OWASP CRS v4 but does not ship or maintain
# rule content itself.
# - coraza-caddy — a Go Caddy server module wrapping coraza.
# - libinjection-go — a Go port of the C libinjection library (SQLi/XSS
# detection), used by coraza as a library dependency.
# - libcoraza — a C library (CGO-based) exposing coraza's engine as a
# C API for non-Go consumers (nginx, SWIG Python/Java
# bindings).
# - coraza.io — the bilingual (English/Spanish) Hugo documentation
# site, partly generated from the coraza Go source.
# Path-scoped guidance keeps each from being reviewed as if it were one of the
# others — that's the main design constraint on this file, same as before,
# just around a different shape of org.
#
# Full reference: https://docs.coderabbit.ai/configure-coderabbit/
#
# NOTE: List fields (custom_checks, path_filters, etc.) are REPLACED, not
# merged, when a repo defines the same key locally. A repo that wants to
# layer its own settings on top of this org config instead of replacing it
# must set `inheritance: true` in its own .coderabbit.yaml.
inheritance: true
# Free tier gives PR authors without a paid seat a degraded (but non-zero)
# automated review instead of none at all. Coraza takes drive-by community
# contributions (CONTRIBUTING.md invites bug reports and enhancements from
# anyone), so this matters more here than in a closed org.
enable_free_tier: true
# Language used for all review comments.
language: "en-US"
# Tone guidance appended to every review prompt. Max 250 chars (silently
# truncated beyond that).
tone_instructions: >-
Be direct, with respect. Be concise. Assume positive intent. Flag bugs, security issues, breaking API/compatibility changes and regressions, with a fix. Cite affected files, tests and ADRs. Skip style nits. No praise.
# Opt in to early-access / beta features org-wide.
early_access: true
reviews:
# Generate a semantic PR title on every review. coraza-caddy, libinjection-go
# and libcoraza all release via release-please, which derives the version
# bump and changelog entry directly from the conventional-commit type in the
# PR/commit title — a wrong type silently mis-versions the release or drops
# the entry, with no CI failure to catch it. coraza itself doesn't run
# release-please (CHANGELOG.md is hand-curated), but its own history and
# AGENTS.md still expect the same convention.
auto_title_instructions: >-
Semantic PR title using a conventional-commit type (feat, fix, perf, docs,
test, refactor, chore, ci). Include the affected package/area as the scope
when there is an obvious one, matching the project's own history, e.g.
`feat(seclang): ...`, `fix(bodyprocessors): ...`, `perf(rx): ...`. Use
`chore:` when nothing else fits. A breaking change needs `!` after the
type/scope (`fix(plugins)!: ...`) since release-please reads that marker
for a major-version bump.
# "chill" (fewer, higher-signal comments) vs "assertive" (comprehensive).
profile: "chill"
# Use GitHub's "request changes" workflow to hard-block merging.
request_changes_workflow: false
high_level_summary: true
review_status: true
# Fun poem at the end of each review. Keep reviews professional.
poem: false
collapse_walkthrough: true
changed_files_summary: true
estimate_code_review_effort: true
# Sequence diagrams add latency and visual noise; not useful for most PRs.
sequence_diagrams: false
# Fortune-cookie messages during review. Keep reviews professional.
in_progress_fortune: false
# Abort in-progress review when the PR closes; avoids wasted compute.
abort_on_close: true
# Labeling (suggested_labels, auto_apply_labels, labeling_instructions,
# mutually_exclusive_groups) is NOT configured here. corazawaf has no
# cross-repo changelog-label taxonomy: release notes and version bumps come
# from release-please parsing conventional-commit titles (see
# auto_title_instructions above), not from labels. Each repo keeps its own
# ad hoc label set (confirmed via `gh label list --repo corazawaf/coraza`:
# plain names like `bug`, `enhancement`, `good first issue`, `help wanted`,
# version labels like `v3`/`v4`) with no cross-repo meaning for CodeRabbit
# to apply consistently.
# Reviewer routing: CODEOWNERS remains the primary, deterministic mechanism
# (`* @corazawaf/core-developers` in coraza and coraza.io). This is an LLM-
# judgment safety net and over-fires easily, so it stays off until the
# instructions below have been validated against real PRs.
suggested_reviewers: false
auto_assign_reviewers: false
suggested_reviewers_instructions:
- reviewers:
- handle: "corazawaf/core-developers"
type: group
instructions: >
Assign for any change under `internal/seclang/` (the SecLang parser),
`experimental/plugins/` (the public plugin registration API), `types/`
or `collection/` (the public API surface), a CGO boundary change in
`libcoraza/*.go` or any `*.c`/`*.h`/`*.i` file, or any change that
should carry an ADR under `docs/adr/`. Engine semantics, plugin API
stability and ModSecurity/CRS compatibility are core-developer
decisions.
# The "ai-slop" label does not yet exist in any corazawaf repo (confirmed
# via `gh label list --repo corazawaf/coraza`). Detection and the review
# comment still work without it; create the label in a repo before this
# has any visible tagging effect there.
slop_detection:
enabled: true
label: "ai-slop"
auto_review:
enabled: true
auto_incremental_review: true
# Draft PRs are skipped; authors request review when ready.
drafts: false
# No LTS/backport branch pattern here — corazawaf repos are versioned by
# release-please or a hand-curated CHANGELOG.md against a single `main`,
# confirmed across all five repos.
base_branches:
- main
ignore_title_keywords:
- "[WIP]"
- "DO NOT REVIEW"
- "DO NOT MERGE"
- "[skip ci]"
- "[skip-review]"
- "[no-review]"
- "chore(release)"
# NOTE: CodeRabbit does NOT auto-import a repo's .gitignore into
# path_filters — patterns must be listed explicitly here (or locally
# with inheritance: true).
path_filters:
# Generated / vendored / build output.
- "!**/node_modules/**"
- "!**/vendor/**"
- "!**/build/**"
- "!**/dist/**"
- "!**/output/**"
- "!**/*.generated.*"
- "!**/*.pb.go"
- "!**/.idea/**"
- "!**/*.svg"
- "!**/*.png"
- "!**/*.jpg"
- "!**/*.ico"
- "!**/CHANGELOG.md"
# coraza.io: Hugo build output and Node/Hugo-module vendor caches.
- "!**/public/**"
- "!**/resources/_gen/**"
- "!**/.vendor/**"
# libcoraza: everything below is autotools/libtool/SWIG build output.
# Confirmed against libcoraza's own .gitignore — none of these are
# actually committed, but list them explicitly per the note above in
# case a contributor force-adds one by mistake.
- "!**/autom4te.cache/**"
- "!**/_obj/**"
- "!**/.libs/**"
- "!**/.deps/**"
- "!**/configure"
- "!**/Makefile.in"
- "!**/aclocal.m4"
- "!**/config.h"
- "!**/config.h.in~"
- "!**/config.log"
- "!**/config.status"
- "!**/config.guess"
- "!**/config.sub"
- "!**/ar-lib"
- "!**/compile"
- "!**/depcomp"
- "!**/install-sh"
- "!**/ltmain.sh"
- "!**/missing"
- "!**/coraza_wrap*.c"
- "!**/examples/python/coraza.py"
- "!**/examples/python/_coraza_wrap.c"
- "!**/examples/java/gen/**"
# AI-generated finishing touches (docstrings, unit tests, code
# simplification). Schema default is enabled, meaning CodeRabbit would push
# AI-authored suggestions directly into every PR without a human drafting
# them first. Disabled org-wide: coraza's AGENTS.md Testing section is
# explicit that new test *functions* are usually the wrong output ("default
# to zero new test functions"; add a row or a profile case instead), which
# is exactly what an auto-pushed unit-test commit would generate.
finishing_touches:
docstrings:
enabled: false
unit_tests:
enabled: false
simplify:
enabled: false
# Per-file review guidance. These carry the domain knowledge a generic
# reviewer does not have. Anything needing line-level localization lives
# here; whole-diff policy lives in pre_merge_checks below. Each instructions
# string is capped at 20,000 chars by the schema.
path_instructions:
- path: "**/internal/seclang/**"
# coraza's SecLang directive/rule parser — the only repo that has this
# directory. Compiles SecLang text into WAF rules and configuration.
instructions: |
This is coraza's SecLang parser (compiles directives into rules and
config; see `parser.go`, `rule_parser.go`, `directives.go`). Flag
⚠️ WARNING: <description> — <fix> on the affected line for:
Silent misparse instead of an error — a directive or rule that fails
to parse cleanly but does not surface an error to the caller. A rule
that silently becomes a no-op is worse than a `Parser.FromFile` that
returns an error, because the operator believes the rule is active.
Phase or evaluation-order assumptions baked into the parser rather
than left to the engine — the parser's job is to produce a correct
rule/config graph, not to encode when in the request lifecycle
something runs.
ModSecurity compatibility drift — a new directive, operator, action,
or transformation whose accepted syntax or semantics diverges from
documented ModSecurity v2/v3 behaviour without that divergence being
called out in the PR description. Coraza's compatibility with
ModSecurity SecLang and CRS is a stated project goal; an undocumented
deviation breaks configs that work elsewhere.
Directive/variable additions not wired into the generated maps —
`internal/seclang/directivesmap.gen.go` and
`internal/variables/variablesmap.gen.go` are generated by
`go generate` (`//go:generate go run generator/main.go` in
`directives.go` / `variables.go`) from the hand-written registration
code. A new directive or variable that isn't reflected in the
generated map after regeneration means either the source registration
or the regeneration step was missed.
`Include` recursion — any change to include-handling must keep
`maxIncludeRecursion` protection intact; removing or loosening it
reopens a DoS via a self-including config.
Missing test coverage in the wrong layer — see the "Test Layer
Discipline & Duplication" pre-merge check; parser changes belong in
`internal/seclang` table tests, not a hand-rolled `NewWAF` + `tx.Process*`
test or a new `testing/engine` profile for something that isn't
rule-evaluation behaviour.
- path: "**/experimental/plugins/**"
# coraza's public plugin registration surface: operators,
# transformations, actions, body processors, audit log writers/formatters.
instructions: |
This is coraza's plugin registration API — the contract every
integrator (coraza-caddy, coraza-proxy-wasm, third-party plugins)
builds against. Flag ⚠️ WARNING: <description> — <fix>, or
❌ blocker for a breaking signature change, for:
Import-cycle risk — `experimental/plugins` forwards to the internal
registries (`internal/operators`, `internal/transformations`,
`internal/actions`). An `internal/` package must never import
`experimental/plugins`; that is a cycle in the wrong direction.
Breaking an interface without a major-version signal — a changed
method signature on `Operator`, `Transformation`, `Action`,
`BodyProcessor`, `TransactionState`, or `RuleMetadata` in
`plugintypes/` breaks every external plugin implementing it at
compile time. This needs `!` in the commit type (see
auto_title_instructions) and almost always an ADR — see "ADR Required
for Structural Changes".
A new extension point registered inconsistently — a new
`Register*` function that doesn't forward through the same
`experimental/plugins` -> internal-registry pattern the existing six
follow (`RegisterOperator`, `RegisterTransformation`,
`RegisterAction`, `RegisterBodyProcessor`, `RegisterAuditLogWriter`,
`RegisterAuditLogFormatter`).
Missing `ActionType` classification — a new `Action` must declare the
correct `ActionType` (`Metadata`, `Disruptive`, `Data`,
`Nondisruptive`, `Flow`); a disruptive action misclassified as
non-disruptive would run even when `SecRuleEngine` is
`DetectionOnly`.
- path: "**/libcoraza/*.go"
# libcoraza's CGO boundary: the entire C API lives in one Go file
# (coraza.go) plus log.go, per its own CLAUDE.md.
instructions: |
This is libcoraza's CGO boundary — the code that exposes coraza's Go
engine as a C API via `runtime/cgo.Handle`. Every bug here is a memory
bug in a language with no garbage collector on the C side. Flag
❌ blocker for a memory-safety violation, ⚠️ WARNING otherwise:
Unpaired C string lifetime — every `C.CString()` must have a matching
`C.free()` on every return path, including error paths. A leaked
C string is invisible to Go's race detector and its leak detector.
`calloc`'d interventions not freed by the documented path —
interventions are allocated with `C.calloc()` and must be freed by the
caller via `coraza_free_intervention()`. A new intervention field or
allocation must follow the same contract, and the API docs/header
comment must say so.
`cgo.Handle` not deleted — every handle created for a
`coraza_waf_config_t`, `coraza_waf_t`, `coraza_transaction_t`, or
`coraza_matched_rule_t` must be deleted via `deleteRaw()`
(`cgo.Handle.Delete()`) on the corresponding C-side free/destroy call.
A missing delete leaks the underlying Go object for the life of the
process.
Type-unsafe handle conversion — a raw `uintptr_t` handle cast without
going through the generic `fromRaw[T]()` helper, which is what
actually enforces the C-handle-to-Go-object mapping is type safe.
A Go panic reaching the CGO boundary — a panic that unwinds into C
code is undefined behaviour, not a Go-level recoverable error; any new
code path callable from C must not be able to panic.
`coraza/coraza.h` edited directly — this header is generated by the
build (`make coraza/coraza.h`) and is not committed to the repository
(confirmed via `.gitignore` and `git ls-files`); a PR that adds or
edits it by hand is adding a generated build artifact to source
control, not documenting the API. The same applies to
`coraza_wrap*.c`, the SWIG-generated wrapper C files.
- path: "**/*.c"
instructions: |
C code in libcoraza (the CGO integration test drivers under `tests/`,
and any future non-generated C source). Flag ⚠️ WARNING:
<description> — <fix> on the affected line for:
Unchecked malloc/calloc return, or a missing corresponding free —
every allocation needs a checked return and a traceable free, matching
the Go-side handle-lifetime contract in `libcoraza/*.go`.
Buffer handling without an explicit bound — `strcpy`, `sprintf`,
`strcat`, or a fixed-size buffer filled from network/request data
without a length check. Prefer the `n`-suffixed variants with an
explicit size.
Use-after-free or double-free across the CGO boundary — freeing a
`cgo.Handle`-backed pointer from C and then dereferencing it, or
freeing the same intervention or C string twice.
Missing NULL checks on values crossing the C API boundary — every
`coraza_*` function pointer or handle received from a caller should be
validated before use; a WAF library must not crash an integrator's
process on a malformed call.
- path: "**/content/**/*.md"
# coraza.io Hugo content — the only repo with this directory shape.
instructions: |
This is coraza.io's Hugo content. `content/en/` is the source of
truth; `content/es/` (Peninsular Spanish) must track it in structure,
per AGENTS.md. Flag ⚠️ WARNING: <description> — <fix> for:
English changed without the Spanish counterpart — a new or restructured
page, section, or front-matter key under `content/en/` with no
matching change under `content/es/`. The CI parity check
(`go test ./tools/i18ncheck/...`) only verifies file existence, not
that structure or prose was actually kept in sync — do not treat a
passing CI run as proof this was done.
A generated file hand-edited — `content/en/docs/seclang/{directives,
actions,operators}.md` are generated by `go run mage.go generate`
(the tools in `tools/{directivesgen,actionsgen,operatorsgen}`) from
the coraza dependency pinned in `go.mod`, not authored by hand. An
edit to these files that isn't a regeneration (bumping the coraza
version and re-running the generator) will be silently overwritten
next time someone does regenerate. The Spanish counterparts under
`content/es/docs/seclang/` are generated once and then hand-translated
— flag a diff that mechanically overwrites the Spanish file instead of
diffing structure and translating only the changed prose (AGENTS.md
calls this out explicitly as the wrong way to update it).
Voice violations — per AGENTS.md: no AI-voice filler ("It's important
to note that...", "Let's dive into..."), no unearned enthusiasm
("seamlessly", "robust", "leverage"), British spelling in English
content ("behaviour", "colour", "initialise"), and code identifiers,
code blocks and Hugo shortcodes left untranslated in Spanish content.
Front matter — only the string values of `title`, `description`,
`lead` should differ between languages; keys themselves stay in
English, and structural keys (`weight`, `draft`) must match exactly
between `content/en/` and `content/es/` for the same page.
- path: "**/.github/workflows/**"
instructions: |
GitHub Actions workflows. Flag ⚠️ WARNING: <description> — <fix> on the
affected line for:
Unpinned action — every `uses:` must reference a full 40-character
commit SHA with the version in a trailing comment
(`actions/checkout@<sha> # v7.0.1`). A tag or branch ref is mutable.
Credential persistence — `actions/checkout` without
`persist-credentials: false` where the job does not need to push.
Injection through `${{ github.event.* }}` — PR title, body, branch
name, or comment text interpolated directly into a `run:` block.
Pass it through `env:` and reference `"$VAR"` instead.
Over-broad `permissions` — a workflow without an explicit
`permissions:` block, or one granting `write` where `read` suffices.
`libcoraza`'s and `coraza`'s workflows set `permissions: {}` at the
top level and grant per-job — match that pattern rather than a single
broad top-level grant.
`pull_request_target` with a checkout of the PR head — this runs
untrusted code with write-scoped secrets.
Base-branch filter mistake — `pull_request.branches` filters on the
PR's *base* branch, not its head. A workflow scoped to
`branches: [main]` never runs on a PR stacked on a feature branch.
A release-please, fuzz, or nightly-compatibility workflow (
`nightly-caddy.yml`, `nightly-coraza.yml`, `fuzz.yml`) whose failure
path doesn't actually surface — e.g. a `gh issue create` step with no
`if: failure()` guard, or one that only fires on the first attempt but
the workflow allows retries silently swallowing it.
- path: "**/*.go"
instructions: |
Go code across the org (coraza, coraza-caddy, libinjection-go, the
Go side of libcoraza, coraza.io's `tools/`). Flag ⚠️ WARNING:
<description> — <fix> on the affected line for:
Panic in library code — `panic`, `log.Fatal`, or `os.Exit` outside
`main`/`init` or a test file. These are libraries and a Caddy module
embedded in someone else's process; per coraza's own AGENTS.md, "an
integrator's process must not die because of a malformed request."
Unchecked or swallowed errors — an ignored return value, `_ = err`, or
a bare `return err` where wrapping would give the caller context. Use
`fmt.Errorf("doing X: %w", err)` and inspect with `errors.Is`/
`errors.As`, not string comparison.
Regex compiled outside the memoizer, in a hot path — `regexp.Compile`/
`MustCompile` called per-request or per-evaluation instead of at
package scope or through coraza's `internal/memoize` cache
(compiled-regex and Aho-Corasick table cache, on by default). This is
a real, measured cost in a WAF's request path, not a style nit.
Untrusted input treated as attacker-controlled — coraza's own
guidance: "treat every request byte, rule argument and configuration
value as attacker-controlled input." Flag an unbounded read of a
request/response body, a regex or string operation on raw payload
data with no size guard, or a comparison of a secret/token that isn't
constant-time.
Missing context propagation — a function that does I/O, blocks, or
can be cancelled without `ctx context.Context` as its first parameter,
or a loop that never checks `ctx.Done()`.
Goroutine without a lifetime — a `go func()` with no cancellation
path, no `WaitGroup`, or no way for the caller to know it finished.
Data races — shared state mutated from multiple goroutines without a
mutex or channel handoff. Coraza's own `collection` types are
documented as NOT concurrent-safe by design (isolated per
transaction) — flag any change that shares one across goroutines.
`interface{}`/`any` outside a plugin API boundary, and bare primitive
types where a named type would carry meaning.
A new test function where a table row or an engine/declarative-profile
case would do — see the "Test Layer Discipline & Duplication"
pre-merge check for the full decision procedure.
# Whole-PR gates. These evaluate the diff as a unit — cross-file invariants
# that no per-file instruction can express.
#
# All checks are mode: warning. Escalate an individual check to mode: error
# (paired with request_changes_workflow: true) only after confirming a low
# false-positive rate on real PRs. Check names are capped at 50 chars and
# instructions at 10,000 chars by the schema.
pre_merge_checks:
description:
mode: warning
issue_assessment:
mode: warning
custom_checks:
- name: "ADR Required for Structural Changes"
mode: warning
instructions: |
Trigger condition — evaluate by path, not by repository name (only
`coraza` has a `docs/adr/` directory, but don't rely on repo-name
resolution being available to this check). Only evaluate if the
diff touches `types/`, `collection/`, `experimental/plugins/`,
or `internal/` AND adds a new directive, operator, action,
transformation, variable, or collection; adds a new subsystem,
plugin, or experimental surface; makes a breaking change to a
public API (`types/`, `collection/`,
`experimental/plugins/plugintypes/`); changes an algorithm or
allocation pattern in a way that shifts performance characteristics;
or restructures how an existing subsystem is organized. Otherwise
report Passed with "not applicable" as the reason text. Bug fixes,
docs, CI, chores, and dependency bumps never need one — do not flag
those even if they touch a lot of files. If the repository has no
`docs/adr/` directory at all, report Passed with "not applicable"
rather than flagging a missing ADR it has nowhere to put.
Coraza's own AGENTS.md requires an ADR (`docs/adr/NNNN-short-slug.md`,
from the `docs/adr/0000-template.md` template) in the *same PR* as
any of the above. Flag ⚠️ WARNING: <description> — <fix> when a
qualifying change has no corresponding new file under `docs/adr/` in
the diff.
When an ADR IS present in the diff, flag ⚠️ WARNING if:
`Category` is not exactly one of Feature, Parity, Perf, or Refactor
(optionally with a parenthetical qualifier).
`## Technical Discussion` contains anything that looks like an
invented quote, decider name, or discussion that cannot be traced to
a real comment via a permalink into the repository — the standard
explicitly forbids inventing these; only "No substantive technical
discussion recorded" or a real, linked quote is acceptable.
The PR modifies the content of an ADR whose `Status` is already
`accepted` — an accepted ADR must not be rewritten; a new one
superseding it is required instead.
No corresponding row was added to the index table in
`docs/adr/README.md`.
Acceptable: an ADR whose `Status` is `proposed` while the PR is
under review (it should read `accepted` by merge); a
`Version: unreleased (post-vX.Y.Z)` placeholder when the release
isn't cut yet; `Considered Options` naming a single option candidly
rather than padding out alternatives that were never seriously
considered.
- name: "Test Layer Discipline & Duplication"
mes109fln.default-releaseode: warning
instructions: |
coraza's AGENTS.md Testing section is explicit that the test suite
grew from 64 to 148 files while the engine "barely changed shape,"
and that most of that growth was duplicated coverage, not new
coverage. This check enforces the opposite bias from what a generic
reviewer defaults to: the expected outcome of most changes is a new
*row* in an existing table or a new *case* in an existing engine
profile, not a new test function or file.
Trigger condition — only evaluate for PRs touching `coraza`'s test
code (`_test.go` files, `testing/engine/`, `testing/profile/`) or
for any PR in any repo introducing a new test file. Otherwise
report Passed with "not applicable".
Flag ⚠️ WARNING: <description> — <fix> when:
A new test FUNCTION is added for rule/directive/phase/chain/action
behaviour that could instead be a case in an existing
`testing/engine/*.go` profile (registered via
`profile.RegisterProfile`). Check whether an existing profile file
for that behaviour category (`setvar.go`, `chains.go`,
`disruptive_actions.go`, etc.) already exists before accepting a new
function.
A new test function is added for a pure function (one operator, one
transformation, one body processor) where a **row** in the existing
table test would do. A table without `t.Run` per case is also a
finding — it reports one failure for the whole table.
The same scenario is tested at more than one layer — e.g. a chained-
rule case duplicated between `internal/seclang` and a
`testing/engine` profile. Name the layer from AGENTS.md's table
that should own it and flag the others as redundant.
A test exists only to raise coverage or execute a line with no
stated failure mode — coverage-driven tests, constructor/accessor
tests (`New()` returns non-nil, a getter returns what a setter just
set), or a test that recomputes the expected value with the same
expression the implementation uses.
A test is added for `testing/`, `testing/profile`, or a test helper
itself, rather than for engine behaviour — these are already
exercised by every profile that runs through them.
A benchmark is added with no before/after number in the PR
description arguing a specific performance change.
A bug fix adds more than one regression case across multiple
layers/functions for what is a single behaviour — one case, in the
profile or table that owns it, is the expectation.
Acceptable: a genuinely new pure function with no existing table to
extend (a first test function for it is correct, not a violation);
a deletion of a redundant test with a one-line note of which
profile/table now covers the same ground; a hand-rolled test
function with an explicit one-line comment stating why it could not
be a profile case or a table row.
- name: "Generated Content Not Hand-Edited"
mode: warning
instructions: |
Trigger condition — only evaluate if the diff touches
`internal/seclang/directivesmap.gen.go`,
`internal/variables/variablesmap.gen.go` (coraza), or
`content/en/docs/seclang/{directives,actions,operators}.md` /
`content/es/docs/seclang/{directives,actions,operators}.md`
(coraza.io). Otherwise report Passed with "not applicable".
These files are generated, not authored: the `.gen.go` files by
`go generate` (`go run generator/main.go`, declared in
`directives.go` / `variables.go`) from the hand-written registration
code in the same package; the `content/en/docs/seclang/*.md` files
by `go run mage.go generate` (the tools in
`tools/{directivesgen,actionsgen,operatorsgen}`) reading the coraza
dependency version pinned in `go.mod`; the `content/es/` versions by
a one-time generation followed by hand translation.
Flag ⚠️ WARNING: <description> — <fix> when:
A `.gen.go` file changed but neither `directives.go` nor
`variables.go` (nor the pinned Go toolchain/dependency version) has
a corresponding change in the same diff — the edit was likely made
directly to the generated file and will be clobbered by the next
`go generate`.
`content/en/docs/seclang/*.md` changed but `go.mod`'s coraza
dependency version did not change and no generator under `tools/`
changed — same failure mode, English side.
`content/es/docs/seclang/*.md` was replaced wholesale (a diff that
looks like the English file copied over the Spanish one) rather than
having only the structurally-changed sections updated and newly
added prose translated — AGENTS.md calls this out explicitly as the
wrong way to update it, since it destroys existing translations.
Acceptable: a `.gen.go` diff that is the direct, mechanical result
of a `directives.go`/`variables.go` change also present in the PR;
a `content/en/docs/seclang/*.md` diff paired with a `go.mod` coraza
version bump; a `content/es/` diff that is clearly a structural
mirror plus translated prose, not a full overwrite.
- name: "Documentation Translation Parity"
mode: warning
instructions: |
Trigger condition — only evaluate for PRs in the coraza.io
repository that touch any file under `content/en/`. Otherwise
report Passed with "not applicable".
Flag ⚠️ WARNING: <description> — <fix> when a page is added,
deleted, or structurally changed (new/removed sections, renamed
files, changed front-matter keys like `weight`/`draft`) under
`content/en/` with no corresponding change under `content/es/` in
the same PR, per AGENTS.md's Translation Workflow. The CI parity
test (`go test ./tools/i18ncheck/...`) only confirms the Spanish
file exists at the same path — it does not confirm the structural
change was mirrored or the new prose translated, so do not treat a
green CI run as proof of parity.
Also flag: a new page added only in `content/en/` with a PR
description that doesn't note the Spanish translation as a planned
follow-up — silent, permanent parity gaps are the failure mode
AGENTS.md is trying to prevent.
Acceptable: a PR that only touches code examples or fixes typos in
prose without changing meaning (a mechanical/no-op update to
`content/es/` is enough, per AGENTS.md); a PR that explicitly states
in its description that the Spanish translation will land in a
follow-up PR, with that PR linked once it exists.
- name: "CGO Memory Safety"
mode: warning
instructions: |
Trigger condition — only evaluate for PRs in the libcoraza
repository that touch a `.go` file under `libcoraza/`, or any
`*.c`/`*.h`/`*.i` file. Otherwise report Passed with "not
applicable".
This duplicates the path_instructions for `**/libcoraza/*.go` and
`**/*.c` as a whole-diff check because memory-safety bugs at a CGO
boundary are often only visible across multiple functions in the
same PR (an allocation in one function, a free that should pair
with it in another).
Flag ❌ blocker for: a `C.CString()` with no matching `C.free()` on
every return path in the diff; a `C.calloc()`'d intervention with no
path to `coraza_free_intervention()`; a `cgo.Handle` created without
a corresponding `deleteRaw()`/`cgo.Handle.Delete()` on the matching
C-side destroy call; any handle dereferenced after its delete path
could have already run (use-after-free shape).
Flag ⚠️ WARNING for: a new exported C function added to
`libcoraza/coraza.go` without updating `coraza.i` (the SWIG
interface definition) to match, which would desync the Python/Java
bindings from the C API; a new field on a struct crossing the CGO
boundary with no corresponding update to the (generated, not
committed) C header's expected shape described in code comments.
Acceptable: an allocation and its free both present in the diff on
every path including error returns; a handle lifecycle where
creation and deletion are in the same PR with a test in
`coraza_test.go` exercising both; a `coraza.i` update accompanying a
new exported function.
- name: "Breaking Changes to Public API & Compatibility"
mode: warning
instructions: |
Flag ⚠️ WARNING: <description> — <fix> on any change that breaks
something a downstream consumer depends on, without it being
documented in the PR description:
Public Go API changes — removing or renaming an exported symbol,
or changing a function signature, under `types/`, `collection/`,
`experimental/plugins/` and `experimental/plugins/plugintypes/`
(coraza); any exported symbol in coraza-caddy or libinjection-go.
These break every downstream module at compile time, not just at
runtime.
libcoraza C API/ABI changes — a changed function signature, struct
layout, or removed function in `libcoraza/coraza.go`'s exported C
functions, or in `coraza.i`. This breaks every C, Python, and Java
consumer without a Go-level compiler error to catch it.
ModSecurity/SecLang compatibility changes — a directive, operator,
action, or transformation whose accepted syntax or behaviour changed
in a way that could break an existing config that worked on a prior
version.
Default configuration changes — a changed default in
`coraza.conf-recommended`, or a changed default in coraza-caddy's
plugin configuration surface. These change behaviour for everyone
running defaults.
Caddyfile directive changes — a renamed or removed `coraza_waf`
sub-directive, or a changed default for one, breaks every existing
Caddyfile using it.
Required documentation for any of the above: a description of what
changed and what consumers must update; for coraza specifically,
confirmation that `README.md`, `coraza.conf-recommended`, and the
relevant example were updated (per AGENTS.md's PR checklist); for a
user-visible change, a note that the coraza.io docs PR is linked or
planned.
Acceptable patterns: a deprecation kept for a release with a note; a
new default introduced alongside the old one with the switch
documented; a breaking change marked with `!` in the commit type so
release-please cuts a major version for it (see
auto_title_instructions).
- name: "Security Fix Process Compliance"
mode: warning
instructions: |
Trigger condition — only evaluate if the PR title, description, or
linked issue describes what reads like an exploitable
vulnerability (words like "vulnerability", "CVE", "GHSA", "security
issue", "exploit", or a fix framed as closing an attack rather than
a correctness bug). Otherwise report Passed with "not applicable".
coraza's AGENTS.md is explicit: "Never open a public issue or PR
describing an exploitable bug." Vulnerabilities go through the
private GitHub security advisory flow
(https://github.com/corazawaf/coraza/security/advisories/new) and a
fix is developed on a private fork/advisory branch until the
advisory is published — not as a normal public PR describing the
bug it fixes.
Flag ⚠️ WARNING: <description> — <fix> when a public PR's own
description lays out how to reproduce or trigger a security
vulnerability (a working payload, an attack sequence, or a clear
statement of impact) rather than referencing a private advisory.
Ask the author to move the technical detail to a GHSA advisory and
keep the public PR description limited to what's needed for
reviewers who don't need the exploit detail, or confirm an advisory
already covers it and link the advisory instead of restating the
exploit.
Also flag, per the same AGENTS.md section: a PR or advisory draft
where AI tooling materially contributed to finding the
vulnerability (the hypothesis, the PoC, or the impact write-up) with
no disclosure of which tools were used, what they generated, and
what independent verification was performed. Omitting this is
treated the same as a missing proof-of-concept: closed as invalid.
Acceptable: a PR whose description says "see GHSA-xxxx-xxxx-xxxx for
detail" and keeps public detail to the fix itself; a PR fixing a
bug that happens to be security-adjacent (e.g. a stricter input
validation) without exploit detail, where the fix is self-evidently
not disclosing a new exploitable path.
# Generic application-security review, trimmed to what actually applies
# to a WAF engine, a Go/Caddy plugin, a CGO library, and a docs site —
# none of these are API servers or LLM-integrated applications, so the
# OWASP API Top 10 and LLM Top 10 categories from a generic template
# would be pure noise here and are deliberately not included.
- name: "OWASP Security (Web & Supply Chain)"
mode: warning
instructions: |
Flag ⚠️ WARNING: <description> — <fix> on any affected line or block for:
Cryptographic Failures — hardcoded secrets or API keys, weak
algorithms (MD5, SHA1, DES), non-constant-time comparison of
secrets/tokens, secrets committed to version control.
Injection — command, path, or format-string injection in Go or C
code that builds a shell command, file path, or SQL-like query from
request-derived input. (SQLi/XSS *detection* logic in
libinjection-go is the library's purpose, not a finding here —
this is about injection risk in the tooling's own code.)
Security Misconfiguration — a default that's insecure out of the
box, verbose error messages leaking stack traces or internal paths
into a response or log, unnecessary features enabled by default.
Vulnerable and Outdated Components — direct use of a library or
version with a published CVE, or a dependency significantly behind
on known security fixes.
Security Logging and Monitoring Failures — see "Secrets, Payloads &
PII in Logs" below for the specifics; this category is the general
umbrella (missing audit trail for security-relevant events, no
rate/volume signal on repeated failures).
SSRF — coraza's `internal/auditlog` HTTPS writer, or any future code
that makes an outbound HTTP request to a configured URL: validate
or document that the URL is operator-configured, not derived from
request content, before treating it as safe.
- name: "Unpinned Dependencies & Actions"
mode: warning
instructions: |
Trigger condition, only evaluate ecosystems whose manifest, lockfile, Dockerfile,
or workflow file actually changed in this diff. If the diff touches none of
the file types listed below, report the standalone status Passed, not Inconclusive,
and state "not applicable" as the reason text rather than folding it into the status
itself.
Flag ⚠️ WARNING: <description> — <fix> whenever a dependency is not pinned to an exact
version or is missing a hash verifier:
GitHub Actions / .github/workflows/*.yml, any "uses:" referencing a tag, branch, or
short SHA. Require a full 40-character commit SHA with the human-readable version in
a trailing comment (actions/checkout@<sha> # v7.0.1). A tag is a mutable ref and a
compromised upstream Action runs with access to this workflow's secrets.
Go / go.mod, any replace directive pointing to a local path without a version;
go.sum must not be deleted or excluded from version control. Flag "GOFLAGS=-mod=mod"
in CI, which lets the build mutate go.mod instead of failing on drift.
npm (coraza.io only), any version using ^, ~, *, >, >=, or "latest" in
package.json; warn if package-lock.json is absent or not committed alongside
the manifest change.
Docker (coraza-caddy's ftw/, example/, e2e/ Dockerfiles), any image using the
:latest tag, no tag, or a named tag without a digest. Prefer
image@sha256:<digest>. Also flag OS package installs inside the Dockerfile
(apt-get install, apk add) with no version pinned per package.
Any other dependency manifest or lockfile format not explicitly listed above: accept
an exact version, a commit SHA, a content digest/hash, or a local/path source as
pinned. Flag a comparison operator, wildcard, range, branch reference, or
"latest"-style meta-version as unpinned.
- name: "New Dependency Scrutiny"
mode: warning
instructions: |
Trigger condition, the PR diff adds a new dependency entry (a line
or key that did not exist on the base branch) in go.mod,
package.json (coraza.io), or a new "uses:" step referencing a
third-party GitHub Action that was not present in
.github/workflows/*.yml on the base branch. A malicious or
compromised Action runs with direct access to CI secrets, so a
newly-added one deserves the same scrutiny as a newly-added
package — this check is about provenance of the addition, not
pinning syntax (already covered by "Unpinned Dependencies &
Actions").
For each newly-added dependency or Action, flag ⚠️ WARNING:
<description>, <fix> when the PR description does not explain why
it was added. Ask for:
Justification, a one-line reason the dependency is needed and why
an existing dependency already in the repo or org cannot cover it.
Typosquat check, compare the new package or Action name against
popular packages/Actions in the same ecosystem for suspicious
similarity.
Publisher/age signal, call out packages or Actions that are very
recently published, have very few downloads/stars, or have a
single maintainer with no other published packages/Actions.
Acceptable patterns: PR description already states why the
dependency or Action was added and links to its source repo or
docs; the dependency is a well-known, widely-used package; the
Action is published by "actions/" or another verified/first-party
GitHub org; the change is a Renovate automated PR bumping an
existing dependency or Action (not a new one).
- name: "Install & Build-Time Code Execution"
mode: warning
instructions: |
Flag ⚠️ WARNING: <description>, <fix> for any of the following:
Pipe-to-shell installers, "curl ... | bash", "curl ... | sh",
"wget -O- ... | sh", or equivalent in a Dockerfile, CI workflow step,
or shell script (including libcoraza's ./build.sh or the SWIG
workflow's setup steps). Prefer a pinned, checksum-verified download
followed by a separate execute step.
Go checksum database bypass, "GOSUMDB=off" or "GOFLAGS=-insecure"
set in a Dockerfile, CI workflow, or Go env file, or an overly broad
"GOPRIVATE"/"GONOSUMDB" glob.
npm lifecycle scripts (coraza.io), a new or changed "preinstall",
"install", or "postinstall" entry in package.json's "scripts" field.
Ask why the script is needed and whether "--ignore-scripts" is safe
for this dependency in CI.
Docker build-time fetches, a RUN step downloading a binary or
archive over HTTP, or over HTTPS with no checksum or signature
verification.
Autotools/build-script fetches — libcoraza's build.sh/configure/make
pipeline installing a tool or dependency without a pinned version
(e.g. swig, autoconf/automake/libtool versions in CI setup steps).
Acceptable patterns: pipe-to-shell replaced with a checksum-verified
download; a release binary installed from a pinned tag with its
published checksum verified; "go install tool@<exact version>" with
the module checksum database left enabled.
- name: "Secrets, Payloads & PII in Logs"
mode: warning
instructions: |
Flag ⚠️ WARNING: <description> — <fix> on any line that emits a log,
stack trace, error message, or telemetry record that may include the
categories below. Fix by masking, redacting, or allowlisting fields
explicitly; never log a full request or response object.
This org's engine and connectors handle live HTTP traffic, so "the
payload" is exactly the thing that must not leak into a shared log,
a CI job output, or a PR comment.
Full-object logging via coraza's `debuglog` interfaces, or a bare
`log.Printf("%v", req)` / `slog.Info("req", "body", body)` /
`json.Marshal` of a whole request/response struct into a log line.
Allowlist the fields instead.
Audit log over-capture — `internal/auditlog` writers (serial,
concurrent, syslog, HTTPS) emitting more of the request/response
than the configured audit log parts call for, or a new
formatter/writer that ignores `SecAuditLogParts`-style scoping.
Credentials & tokens forwarded without redaction — `Authorization`
header contents, session cookies, API keys logged verbatim by the
Caddy interceptor or the HTTP middleware in `http/`.
CGO log bridge over-capture — libcoraza's `log.go` debug callback
bridge passing a full C string containing request data across the
boundary into a log sink with no size or field limit.
Test fixtures and logs committed to the repo — a captured audit log,
a corpus file, or e2e test recording containing real traffic. Real
IPs, cookies, and credentials do not belong in synthetic test data.
Base64-encoded blobs written to a log or error message — decode and
you commonly find JWTs, tokens, bodies, or key material. Redact,
hash, or log a short fingerprint (first 6 chars + length) instead.
Acceptable patterns: structured logging with an explicit field
allowlist; redaction applied at the logger level; debug-only verbose
output behind an explicit flag that is off by default.
- name: "Renovate: config present and valid"
mode: warning
instructions: |
Scan every PR that adds, modifies, or deletes files in the repo root
or the `.github/` directory.
Trigger condition — only proceed if any of the following files are
touched: `renovate.json`, `renovate.json5`, `.github/renovate.json`,
or `.github/renovate.json5`. Also trigger if *none* of those files
exist anywhere in the repository at the time of the PR.
Check 1 — File presence:
The repository must contain at least one of:
- `renovate.json` (repo root)
- `renovate.json5` (repo root)
- `.github/renovate.json`
- `.github/renovate.json5`
If none exist, emit:
⚠️ WARNING: No Renovate config found. Add a `renovate.json` in
the repo root that extends `corazawaf/renovate-config` so