Route CI/CD security checks from an AI-assisted analysis of Git changes
Project description
secorch
secorch is a command-line tool that analyzes the latest Git commit and produces a validated DevSecOps routing decision. It combines four focused contexts of the commit, its message, file status, affected path, and textual patch to recommend which CI/CD security checks are relevant.
The result is printed for humans, rendered as a PDF audit report, and published as GitHub Actions outputs for downstream jobs.
Contents
- secorch
What secorch Does
- Reads the current
HEADcommit from a Git repository. - Gives four specialized LLM agents a different context of that same commit.
- Combines their findings using an explicit evidence-precedence policy.
- Routes six supported security checks to either
requiredorskip. - Produces a PDF containing the evidence, decision, reasons, and uncertainties.
- Exposes values that CI workflows can use in conditions.
[!IMPORTANT] secorch does not run the security scanners itself. It decides which downstream checks should run. It also does not inspect uncommitted, staged-only, or historical changes: analysis is scoped to the latest commit only.
Architecture
The CLI is a small Typer library based Python application with two commands:
init and analyze. The secorch command calls secorch.__main__:main, which
invokes the Typer app. Typer parses the command and typed options, applies their
defaults, and dispatches to the matching command function.
flowchart TD
ENTRY[secorch command] --> MAIN[__main__.py: main]
MAIN --> APP[Typer app in cli.py]
APP --> PARSE{Parse command and options}
INIT_OPTIONS[--output<br/>--force] --> INIT
PARSE -->|init| INIT[commands/init.py]
INIT --> INIT_LOGIC{Output already exists?}
INIT_LOGIC -->|no, or --force| CONFIG_FILE[Write TOML configuration]
INIT_LOGIC -->|yes, without --force| ERROR[User-facing error and exit 1]
PARSE -->|analyze| ANALYZE[commands/analyze.py]
ANALYZE_OPTIONS[--repository<br/>--config<br/>--report<br/>--github-output] --> ANALYZE
ANALYZE --> PREFLIGHT[Validate repository and config]
PREFLIGHT -->|invalid| ERROR
PREFLIGHT -->|valid| ORCHESTRATE[Run analysis and validate decision]
ORCHESTRATE --> OUTPUTS[Print result, write PDF, publish CI outputs]
cli.py registers the command functions with Typer, while each file in
commands/ owns its option declarations and command-level control flow. The
analyze command delegates Git access, model execution, decision validation,
and report generation to focused modules; the execution-flow diagram below
shows that internal process in detail.
How secorch Works
The analyze command first validates prerequisites, then invokes four
logically independent analyzers (LLM agents). None of these analyzers consumes
another analyzer's output; each receives and examines a different context of the
same commit. The current implementation schedules them sequentially,
but their evidence is combined only after all four have completed. Their labeled
responses are then passed to the dependent final decision agent, whose JSON is
accepted only after strict application-side validation.
flowchart TD
CLI[secorch analyze] --> PRE{Validate inputs}
PRE -->|repository and config valid| HEAD[Resolve repository HEAD]
PRE -->|invalid| EXIT[Print error and exit 1]
HEAD --> MSG[Commit message analyzer]
HEAD --> STATUS[File status analyzer]
HEAD --> PATHS[Affected path analyzer]
HEAD --> DIFF[Textual patch analyzer]
MSG --> COMBINE[Final decision agent]
STATUS --> COMBINE
PATHS --> COMBINE
DIFF --> COMBINE
COMBINE -->|invalid| FAILPDF[Write failure PDF]
FAILPDF --> EXIT
COMBINE -->|valid| PDF[PDF audit report]
PDF --> GHO[GitHub Actions outputs]
GHO --> CONSOLE[Console summary]
Analyzers and Inputs
All four contexts are derived from the same HEAD commit and used with
different analyzers:
| Context and Analyzer | Git input | What it contributes |
|---|---|---|
| Commit message | Complete commit message | Author intent and stated context |
| File status | git diff-tree --root --name-status -r -M -C |
Added, modified, deleted, and renamed |
| Affected path | Paths parsed from the same name-status data | Affected files, directories, logical components, and change concentration |
| Textual patch (code diff) | git show --patch --no-color --no-ext-diff --find-renames --find-copies |
Implemented behavior, important code changes, and security implications visible in the patch |
The repository path may point at the repository itself or a directory inside it; GitPython searches parent directories. Empty repositories are rejected. Root commits and empty commits are handled explicitly, while binary-only changes may provide limited textual evidence.
Decision Logic
When evidence conflicts, the final agent uses this precedence order:
flowchart LR
A["Textual patch (code diff)<br/>highest priority"] --> B[Affected path]
B --> C[File status]
C --> D[Commit message<br/>lowest priority]
A check is required when evidence supports running it or when the available
evidence is insufficient to safely skip it. A check is skip only when there is
enough evidence that it is irrelevant to the commit. The decision agent never
claims that a check has already run or passed.
The supported checks are:
| Check | Intended routing target |
|---|---|
secret_scanning |
Exposed credentials, tokens, keys, and other secrets |
sca |
Dependency manifests, lockfiles, and dependency risk |
sast |
Static analysis of application or library source code |
iac_scanning |
Infrastructure-as-code and deployment configuration |
container_scanning |
Container definitions, images, and related build inputs |
dast |
Runtime-facing behavior suitable for dynamic application testing |
Before any successful result is published, secorch requires:
risk_levelto be exactlylow,medium, orhigh.- every supported check to appear exactly once.
- every priority to be exactly
requiredorskip. - a non-empty summary and reason for every check.
uncertaintiesto be a list of strings.
Requirements and Installation
secorch requires Python 3.12+, Git, an OpenAI API key, and a Git repository with at least one commit.
Install from a checkout with uv:
git clone https://github.com/faizanmsiddiqui/secorch.git
cd secorch
uv sync
Use uv run secorch in the examples below.
Quick Start
uv run secorch init
export OPENAI_API_KEY="your-api-key"
uv run secorch analyze
This analyzes the current repository's latest commit, prints all four analyses
and the final routing decision, and creates artifacts/secorch-report.pdf.
Development and Testing
uv sync --dev
uv run ruff format .
uv run ruff check .
uv run mypy
uv run pytest
Configuration
The default secorch.toml is:
[model]
provider = "openai"
name = "gpt-5.4-mini"
temperature = 0.0
Generate it with secorch init, copy secorch.example.toml, or select another
file with --config. The model table and all three model values are validated.
[!IMPORTANT] OpenAI is currently the only supported provider. Keep
OPENAI_API_KEYin the environment or a CI secret; never store it in the TOML file or commit it.
Commands and Inputs
secorch init
Generates the default configuration file.
secorch init [--output PATH] [--force]
| Option | Default | Description |
|---|---|---|
--output, -o |
secorch.toml |
Configuration file to create |
--force, -f |
false |
Replace the file if it already exists |
secorch analyze
Analyzes the repository's current HEAD commit.
secorch analyze \
--repository . \
--config secorch.toml \
--report artifacts/secorch-report.pdf \
--github-output /path/to/github-output
| Input | Default | Description |
|---|---|---|
--repository, -r |
. |
Repository or a directory within one |
--config, -c |
secorch.toml |
TOML model configuration |
--report |
secorch-report.pdf |
Destination for the PDF audit report |
--github-output |
$GITHUB_OUTPUT, when set |
File to append CI outputs to; an explicit path takes precedence |
OPENAI_API_KEY |
none | Required environment variable used by the OpenAI client |
Run secorch COMMAND --help for the generated CLI help.
Outputs
PDF Audit Report
On success, the report includes:
- generation time in UTC and the full commit SHA.
- completed or failed status.
- overall risk, summary, check priorities, and reasons.
- uncertainties reported by the decision agent.
- every analyzer output that completed.
Pandoc converts an intermediate Markdown report into a styled A4 PDF using WeasyPrint. The destination directory is created automatically, and the completed PDF replaces the destination atomically. If analysis fails after the commit has been resolved, secorch attempts to write a failure report containing the error and any analyzer results collected before the failure.
GitHub Actions Outputs
When GITHUB_OUTPUT is present or --github-output PATH is supplied, secorch
appends these values:
risk_level=medium
checks={"container_scanning":"skip","dast":"skip","iac_scanning":"skip","sast":"required","sca":"required","secret_scanning":"required"}
report=artifacts/secorch-report.pdf
check_secret_scanning=required
check_sca=required
check_sast=required
check_iac_scanning=skip
check_container_scanning=skip
check_dast=skip
checks is compact, key-sorted JSON. The individual check_<name> values are
convenient for simple step conditions.
Console
stdout contains each labeled analyzer response followed by the final risk level, all six check priorities, and the PDF path. This is the detailed human view; CI integrations should consume the structured GitHub outputs instead of parsing console text.
Failure Behavior
analyze exits with status 1 for invalid paths, missing or invalid
configuration, repository errors, model or provider failures (including missing
credentials), or an invalid decision contract. Preflight failures occur before a
commit is resolved and do not produce a report. Analysis-stage failures produce
a failure PDF when report rendering itself remains available. GitHub outputs are
written only after a valid decision and successful report generation.
GitHub Actions Example
name: DevSecOps Pipeline
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
permissions:
contents: read
jobs:
orchestrate:
name: Analyze and Orchestrate Security Checks
runs-on: ubuntu-latest
outputs:
checks: ${{ steps.secorch.outputs.checks }}
risk_level: ${{ steps.secorch.outputs.risk_level }}
report: ${{ steps.secorch.outputs.report }}
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install secorch package
run: python -m pip install .
- name: Generate default configuration
run: secorch init --force
- name: Analyze latest commit
id: secorch
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: secorch analyze --report artifacts/secorch-report.pdf
- name: Upload decision report
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: secorch-decision-report
path: artifacts/secorch-report.pdf
if-no-files-found: warn
retention-days: 14
secret-scanning:
name: Secret Scanning
needs: orchestrate
if: ${{ fromJSON(needs.orchestrate.outputs.checks).secret_scanning != 'skip' }}
runs-on: ubuntu-latest
steps:
- name: Secret Scanning (dummy)
run: echo "TODO Run secret scanning"
sca:
name: SCA
needs: orchestrate
if: ${{ fromJSON(needs.orchestrate.outputs.checks).sca != 'skip' }}
runs-on: ubuntu-latest
steps:
- name: SCA (dummy)
run: echo "TODO Run software composition analysis"
sast:
name: SAST
needs: orchestrate
if: ${{ fromJSON(needs.orchestrate.outputs.checks).sast != 'skip' }}
runs-on: ubuntu-latest
steps:
- name: SAST (dummy)
run: echo "TODO Run static application security testing"
iac-scanning:
name: IaC Scanning
needs: orchestrate
if: ${{ fromJSON(needs.orchestrate.outputs.checks).iac_scanning != 'skip' }}
runs-on: ubuntu-latest
steps:
- name: IaC Scanning (dummy)
run: echo "TODO Run infrastructure-as-code scanning"
container-scanning:
name: Container Scanning
needs: orchestrate
if: ${{ fromJSON(needs.orchestrate.outputs.checks).container_scanning != 'skip' }}
runs-on: ubuntu-latest
steps:
- name: Container Scanning (dummy)
run: echo "TODO Run container image scanning"
dast:
name: DAST
needs: orchestrate
if: ${{ fromJSON(needs.orchestrate.outputs.checks).dast != 'skip' }}
runs-on: ubuntu-latest
steps:
- name: DAST (dummy)
run: echo "TODO Run dynamic application security testing"
License
secorch is available under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file secorch-0.1.0.tar.gz.
File metadata
- Download URL: secorch-0.1.0.tar.gz
- Upload date:
- Size: 17.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d91377f877da2e61dfb8f74aa8b9e17c74e0da8bb7517077d2f866feaa080ca
|
|
| MD5 |
4f76e0444d78ad9e760d447c776d41c0
|
|
| BLAKE2b-256 |
a284f882096cef47bcf5679e145fdf15bb1c4d871d2dfe26e176488d83b07b27
|
File details
Details for the file secorch-0.1.0-py3-none-any.whl.
File metadata
- Download URL: secorch-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0af2eb9af7c14576bf613bd6001e31c56c0a0a0dd9467fab90a7d8d19cd6b12
|
|
| MD5 |
83bd96bdff4af926c97a9cf5bf61c8e0
|
|
| BLAKE2b-256 |
a437df5c8b2b6f8efd06695baf0cd799eb60eca6068b44d2b19e8040068dba4b
|