ADR-006: CI Security Gate Pipeline
Status: Accepted Date: 2026-02-18 Deciders: CODITECT Architecture Team Source Research: ClawGuard AI Agent Security Ecosystem Evaluation (2026-02-18)
Context
With the YAML pattern library (ADR-002) and risk scoring model (ADR-004) implemented, the security layer needs a mechanism to enforce these patterns in CI/CD pipelines. Without automated enforcement, patterns exist only as documentation — developers must remember to run scans manually, which defeats the purpose of a security gate.
Three enforcement points were considered:
| Enforcement Point | When It Runs | Scope | Latency |
|---|---|---|---|
| Pre-commit hook | Before git commit | Local developer machine | <2s for staged files |
| GitHub Actions workflow | On push/PR to protected branches | CI server, all changed files | 10-30s |
| Pre-merge check | Before merge to main | CI server, full diff | 10-30s |
The ClawGuard ecosystem provides no CI integration — all three repositories (maxxie114, superglue-ai, JaydenBeard) operate only as runtime middleware or CLI tools. CODITECT requires a shift-left approach where insecure patterns are caught before code reaches the main branch.
Key design questions:
- Trigger strategy: Should the gate run on every push, only on PRs, or both?
- File resolution: How should changed files be identified across push vs. PR contexts?
- Output format: How should findings be surfaced to developers?
- Fail threshold: At what severity should the pipeline block merge?
- Scanner architecture: Standalone script or integrated into the existing PatternEngine?
Decision
Implement a GitHub Actions workflow (security-scan.yml) that invokes a standalone Python scanner (scripts/ci-security-scan.py) reusing the existing PatternEngine and aggregate_score() from the core library.
Trigger Strategy
on:
push:
branches: [main]
pull_request:
branches: [main]
The workflow triggers on both push and PR events to main. Push triggers catch direct commits (e.g., maintainer hotfixes). PR triggers catch the standard development workflow and provide inline annotations on the diff.
File Resolution
The scanner resolves files to scan using a two-stage fallback:
- Explicit
--filesargument — used when the caller knows which files changed (e.g., GitHub Actions passes the changed file list). - Git diff resolution — if no
--filesprovided:- First tries
git diff --cached --name-only --diff-filter=ACM(pre-commit context: staged files). - Falls back to
git diff origin/main...HEAD --name-only --diff-filter=ACM(CI/PR context: branch diff).
- First tries
This dual approach allows the same scanner to serve both pre-commit hooks and CI pipelines without configuration changes.
Output Format
Findings are emitted as GitHub Actions workflow commands:
::error file=src/config.py,line=42::[SD-001] AWS Access Key detected (matched: 'AKIAIOSFODNN7EXAMPLE' in field 'content')
::warning file=src/utils.py,line=15::[DC-006] Sudo usage detected (matched: 'sudo' in field 'content')
criticalandhighseverity findings emit::errorannotations.mediumandlowseverity findings emit::warningannotations.- Annotations appear inline on the PR diff, directly on the offending line.
A summary table is printed to stdout (captured by job summary):
========================================================
CODITECT CI Security Scan — Summary
========================================================
Patterns loaded : 25
Files scanned : 3
Total findings : 2
Severity Count
------------ ------
critical 0
high 1
medium 1
low 0
========================================================
Fail Threshold
The scanner exits with code 1 (blocking) when any finding meets or exceeds --fail-on severity. Default: high.
python3 scripts/ci-security-scan.py --fail-on high
--fail-on | Blocks On |
|---|---|
critical | Only critical findings |
high | Critical + high (default) |
medium | Critical + high + medium |
low | Any finding |
This makes the gate strict by default (blocks on high+critical) while allowing per-repository tuning.
Scanner Architecture
The scanner is a standalone script that imports from the src.security package:
scripts/ci-security-scan.py
├── imports: PatternEngine, Severity (from src.security.patterns)
├── imports: aggregate_score (from src.security.scoring)
├── _get_changed_files_from_git() — file resolution
├── _scan_file() — per-file scanning via PatternEngine.match()
├── _emit_annotation() — GitHub Actions annotation format
├── _print_summary() — human-readable summary table
└── main() — CLI entry point with argparse
The scanner uses "Write" as the synthetic tool name when calling PatternEngine.match(). This means:
- Patterns with
applies_to: [Write]orapplies_to: [Bash, Write]are evaluated against file content. - Patterns restricted to
applies_to: [Bash]only are intentionally excluded — file content is not a shell command.
Workflow Permissions
permissions:
contents: read
Least-privilege: the workflow only needs read access to check out code and scan files. No write permissions are granted.
Consequences
Positive
- Shift-left enforcement: Insecure patterns are caught at PR time, before code reaches main. Developers see inline annotations directly on offending lines.
- Reuses existing engine: The scanner imports
PatternEngineandaggregate_score— no pattern duplication, no separate detection logic to maintain. - Dual-context support: Same scanner works in pre-commit hooks (staged files) and CI (branch diff) without configuration changes.
- Configurable threshold: Teams can tune
--fail-onper-repository. Security-critical repos usemedium, application repos usehigh. - Risk scoring integration: The
aggregate_score()call provides an informational risk score alongside the pass/fail decision, giving developers context about overall security posture. - Non-blocking warnings: Medium/low findings surface as warnings (visible but non-blocking), encouraging incremental improvement without blocking development velocity.
Negative
- File-level scanning only: The scanner reads file content as flat text. It does not parse ASTs, so patterns that require syntactic context (e.g., distinguishing a string literal from a variable name) may produce false positives. Mitigation: patterns are authored with this constraint in mind (ADR-002).
- No incremental scanning: The scanner rescans entire changed files, not just changed lines. For large files with one-line changes, this means scanning unchanged content. Acceptable for the current 25-pattern library; may need optimization at 100+ patterns.
- GitHub-specific annotations: The
::error/::warningformat is GitHub Actions-specific. GitLab CI or other platforms would need adapter output. Mitigation: the summary table is platform-agnostic.
Neutral
- The scanner does not implement
--outputfor JSON file export. Scan results are available via stdout capture. JSON export can be added when artifact archival requirements are defined.
Alternatives Considered
Alternative A: GitHub Actions Marketplace Action
Approach: Publish a reusable GitHub Action (coditect-ai/security-scan-action) that encapsulates the scanner with Docker or composite steps.
Deferred (not rejected) because:
- A marketplace action is the right distribution mechanism for external consumers. However, the scanner is currently used only within
coditect-dev-agent-security. Packaging as a marketplace action adds maintenance overhead (Dockerfile, action.yml, versioning) that is premature. - When the scanner is adopted by multiple CODITECT repositories, packaging as a marketplace action becomes worthwhile. This is a Phase 2 concern.
Alternative B: Pre-commit Hook Only (No CI)
Approach: Rely solely on a local pre-commit hook (scripts/ci-security-scan.py invoked by .pre-commit-config.yaml).
Rejected because:
- Pre-commit hooks are bypass-able (
--no-verify). A determined actor can skip them. - Pre-commit hooks do not run in CI — commits pushed directly to main (e.g., hotfixes, bot commits) would bypass scanning.
- Pre-commit hooks provide no PR-level visibility — other reviewers cannot see scan results.
Alternative C: Integrated GitHub App / Check Suite
Approach: Build a GitHub App that uses the Checks API to post detailed findings with annotations, code suggestions, and re-run capability.
Rejected because:
- Requires a hosted service (webhook receiver, OAuth flow, GitHub App registration). The CI scanner achieves equivalent PR-level visibility with zero infrastructure.
- The
::error/::warningannotations provide the same inline PR experience as Check Suite annotations for the current use case. - Appropriate for a commercial product offering; premature for internal tooling.
Implementation Notes
- Workflow file:
.github/workflows/security-scan.yml - Scanner script:
scripts/ci-security-scan.py - Dependencies: Python 3.10+, PyYAML (for pattern loading)
- Test coverage: 41 unit tests for PatternEngine (loading + matching), 6 for scoring, 9 for actions
- Known gap: The workflow references
--output scan-results.jsonfor artifact upload, but the scanner does not yet implement--output. The artifact upload step will no-op until this is added.
References
- ADR-001: Agent Security Layer Architecture (runtime interceptor design)
- ADR-002: Security Pattern Library Format (YAML pattern definitions)
- ADR-003: Fail-Open vs Fail-Closed Security Gate (gate behavior)
- ADR-004: Risk Scoring Model (aggregate_score formula)
- ADR-005: Malicious Supply Chain Detection (future pattern category)
- GitHub Actions workflow commands: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions