5 Proven CI Gates for API Security: OPA Rules You Can Ship
Engineering leaders don’t need more theory—you need merge-blocking, evidence-producing gates you can roll out this sprint. Below is a practical, code-heavy guide to implement API security CI/CD gates with Open Policy Agent (OPA/Rego) and GitHub Actions, including mappings to SOC 2 & PCI DSS, and automated remediation tickets.

Looking for help implementing this? Explore Cyber Rely’s API Penetration Testing Services and secure rollout playbooks on our site.
What you’ll build
- 5 OPA/Rego gates that fail CI when:
- endpoints lack authorization,
- rate-limits are missing,
- code logs PII,
- JWTs miss critical claims,
- evidence isn’t captured.
- A single GitHub Actions workflow that runs Conftest, uploads JUnit/SARIF evidence, and auto-opens remediation issues.
- Evidence mapping your CI output to SOC 2 CC6/CC7 and PCI DSS Req. 6 & 10.
Inputs your gates will read
openapi.(yaml|json)– your OpenAPI specgateway/– API gateway config (e.g., Kong declarative)scan/semgrep.json– static findings (optional)ci/context.json– build metadata (commit SHA, runner, policy bundle version)
Free Tool screenshot (Website Vulnerability Scanner):

Gate #1 — AuthZ required per endpoint (OpenAPI)
Policy intent: Every operation must declare a security requirement that maps to an allowed scheme/role.
policy/authz.rego
package api.authz
# deny if an operation has no security requirement
deny[{"msg": sprintf("Missing authZ on %s %s", [method, path])}] {
some path, method
op := input.paths[path][method]
not op.security
}
# deny if security references an unknown scheme
deny[{"msg": sprintf("Unknown security scheme on %s %s", [method, path])}] {
some path, method, i
op := input.paths[path][method]
scheme := op.security[i]
not valid_scheme(keys(scheme)[0])
}
valid_scheme(s) {
s == "oauth2" # adapt to your org's allowed schemes
} else {
s == "jwt"
}
Run with Conftest
conftest test openapi.yaml --policy policy --namespace api.authz
Failure effect: CI fails → merge is blocked until missing security is declared.
Evidence: JUnit test report + policy version recorded.
Gate #2 — Rate-limits present (OpenAPI or Gateway config)
Enforce a vendor extension in OpenAPI or a gateway plugin in config.
OpenAPI mode (policy/ratelimit.rego)
package api.ratelimit
deny[{"msg": sprintf("Missing rate-limit on %s %s", [m, p])}] {
some p, m
op := input.paths[p][m]
not op["x-rate-limit"]
}
Kong declarative mode (policy/ratelimit_kong.rego)
package kong.ratelimit
# require global or per-route rate-limiting plugin
deny[msg] {
not input.plugins[_].name == "rate-limiting"
not some_route_limit
msg := "No Kong rate-limiting plugin configured"
}
some_route_limit {
some i
input.routes[i].plugins[_].name == "rate-limiting"
}
Gate #3 — No PII in application logs
Feed lightweight static results (e.g., grep/Semgrep) into OPA and fail on risky log patterns.
Example scan/semgrep.json (snippet)
{
"results": [
{"check_id":"pii.log.email","path":"svc/user.js","extra":{"message":"logger.info('email=', user.email)"}}
]
}
policy/pii_logging.rego
package app.logging
deny[{"msg": sprintf("PII logged in %s: %s", [r.path, r.extra.message])}] {
r := input.results[_]
startswith(r.check_id, "pii.log.")
}
Gate #4 — JWT/session claims must be present
Ensure exp, aud, and least-privilege scopes are enforced at the edge.
policy/jwt_claims.rego
package edge.jwt
default required_claims := {"exp", "iat", "sub", "aud"}
deny[{"msg": sprintf("JWT missing required claim: %s", [c])}] {
some c
c := required_claims[_]
not input.jwt[c]
}
deny[{"msg": "scope allows wildcard"}] {
input.jwt.scope == "*"
}
How to feed input: Have your gateway CI job decode a sample JWT (or fixture) to input.jwt before policy evaluation.
Gate #5 — Evidence & ticket automation
- Store evidence: upload
conftestJUnit + SARIF output, plusci/context.json(commit, policy bundle hash). - Auto-open remediation issues when any gate fails.
scripts/ci_context.sh
jq -n --arg sha "$GITHUB_SHA" --arg run "$GITHUB_RUN_ID" \
--arg bundle "$(sha256sum policy/*.rego | sha256sum | cut -d' ' -f1)" \
'{commit:$sha, run_id:$run, policy_bundle:$bundle, timestamp: now|toiso8601}'
> ci/context.json
GitHub Actions: one-file workflow you can paste
name: api-security-gates
on:
pull_request:
push:
branches: [ main ]
jobs:
gates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up OPA/Conftest
uses: instrumenta/conftest-action@v1
- name: Build CI context
run: bash scripts/ci_context.sh
- name: Validate AuthZ
run: conftest test openapi.yaml --policy policy --namespace api.authz --output junit > junit-authz.xml
- name: Validate Rate Limits (OpenAPI)
run: conftest test openapi.yaml --policy policy --namespace api.ratelimit --output junit > junit-rl.xml
- name: Validate Rate Limits (Kong) # optional
if: ${{ hashFiles('gateway/kong.yaml') != '' }}
run: conftest test gateway/kong.yaml --policy policy --namespace kong.ratelimit --output junit > junit-rl-kong.xml
- name: Scan for PII log patterns (Semgrep)
run: |
pipx install semgrep
semgrep --config p/ci --config r/owasp-top-ten --json -o scan/semgrep.json || true
- name: Gate on PII logging via OPA
run: conftest test scan/semgrep.json --policy policy --namespace app.logging --output junit > junit-pii.xml
- name: Validate JWT claims
run: conftest test fixtures/jwt.json --policy policy --namespace edge.jwt --output junit > junit-jwt.xml
- name: Upload Evidence (JUnit)
uses: actions/upload-artifact@v4
with:
name: api-security-evidence
path: |
junit-*.xml
ci/context.json
- name: Convert to SARIF (optional)
run: |
opa eval --format=json -i openapi.yaml -d policy 'data' > sarif.json || true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: sarif.json }
- name: Open remediation issue if failed
if: failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "One or more API Security CI gates failed on $GITHUB_SHA" > body.txt
echo "Artifacts: api-security-evidence" >> body.txt
gh issue create --title "Fix: API Security CI gate failures" --body-file body.txt || true
Sample Report screenshot (from the tool) to check Website Vulnerability:

Evidence mapping (SOC 2 & PCI DSS)
- SOC 2 CC6 (Logical Access), CC7 (Change Mgmt, Monitoring)
- CI logs + JUnit results prove preventive controls at merge time and detective controls in build telemetry.
- PCI DSS Requirements 6 & 10 (Secure SDLC & Logging)
- AuthZ, rate-limit, and JWT gates demonstrably enforce secure coding & config; SARIF/JUnit + pipeline logs show audit evidence captured for assessments.
Need formal mapping & auditor-ready packaging? See our Risk Assessment Services and Remediation Services offerings to turn CI outputs into an assessor-friendly evidence pack.
- Risk Assessment Services: Pentest Testing Corp — Risk Assessment Services
- Remediation Services: Pentest Testing Corp — Remediation Services
Rollout plan on CI Gates for API Security (2 sprints)
Sprint 1:
- Add Gate #1 and #2 on a non-blocking job (warn only), fix noisy endpoints, document allowed schemes/exts.
- Wire evidence upload.
Sprint 2:
- Flip to blocking on PRs.
- Add Gate #3 (PII logging) + Gate #4 (JWT claims).
- Enable auto-ticket on failure.
Further reading on Cyber Rely
- OPA vs Cedar: 7 Proven Steps to Ship Policy-as-Code – Compare engines and see CI gate patterns.
- 12 Battle-Tested GraphQL Authorization Patterns + CI Gates – Resolver-level authZ patterns with CI checks.
- 7 Powerful Steps: Add an ASVS 5.0 Gate to CI/CD – Broader appsec gates and evidence capture.
- Browse all posts on the Cyber Rely Blog for up-to-date implementation guides.
Final Note
- Want us to stand up these gates and validate them with real attacks? Start with API Penetration Testing Services at Cyber Rely.
- Prefer an independent partner audit and remediation program?
- Risk Assessment Services → map CI controls to SOC 2/PCI DSS
- Remediation Services → backlog-ready fixes with side-by-side pairing
And if you need a quick baseline while CI is being wired, run a scan with our free tool at free.pentesttesting.com.
🔐 Frequently Asked Questions (FAQs)
Find answers to commonly asked questions about CI Gates for API Security.