flowsec 0.5.2__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flowsec-0.5.2 → flowsec-0.5.3}/PKG-INFO +74 -168
- flowsec-0.5.3/PYPI_README.md +153 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/docs/FULL_README.md +142 -81
- {flowsec-0.5.2 → flowsec-0.5.3}/pyproject.toml +2 -2
- {flowsec-0.5.2 → flowsec-0.5.3}/.devcontainer/devcontainer.json +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/.github/workflows/publish.yml +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/.github/workflows/security.yml +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/.gitignore +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/AGENTS.md +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/LICENSE +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/Makefile +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/README.md +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/SECURITY.md +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/docs/screenshots/flowsec-logo.png +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/__init__.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/ai_narrative.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/cli.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/config.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/parser.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/report.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/__init__.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/artifact_signing.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/base.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/broad_artifact_upload.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/container_runs_as_root.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/continue_on_error_security.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/dependency_pinning.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/deploy_all_branches.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/env_vars_in_logs.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/excessive_permissions.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/github_context_injection.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/hardcoded_secrets.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/insecure_curl.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/missing_env_protection.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/missing_oidc.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/missing_timeout.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/mutable_container_image.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/persist_credentials.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/privileged_docker.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/pull_request_target.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/secret_as_cli_arg.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/secrets_in_build_args.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/secrets_in_run.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/self_hosted_runner.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/unpinned_actions.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/unverified_install_script.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/workflow_dispatch_injection.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/workflow_run_trigger.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/scanner.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/scoring.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/templates/report.html +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/__init__.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/azure_all_vulns.yml +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/github_all_vulns.yml +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/gitlab_all_vulns.yml +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/sample_azure_vulnerable.yml +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/sample_gitlab_vulnerable.yml +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/sample_workflow_clean.yml +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/sample_workflow_vulnerable.yml +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/test_azure_rules.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/test_github_rules.py +0 -0
- {flowsec-0.5.2 → flowsec-0.5.3}/tests/test_gitlab_rules.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowsec
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: CI/CD pipeline security analyzer — scans GitHub Actions, GitLab CI, and Azure DevOps configs for attack vectors
|
|
5
5
|
Author-email: Vansh Bhardwaj <Scorpio.vansh@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -33,24 +33,66 @@ Requires-Dist: responses>=0.25.3; extra == 'dev'
|
|
|
33
33
|
Requires-Dist: ruff>=0.4.4; extra == 'dev'
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
<img src="docs/screenshots/flowsec-logo.png" alt="FlowSec" width="520" />
|
|
38
|
-
</p>
|
|
36
|
+
# FlowSec
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
<a href="https://codespaces.new/VanshBhardwaj1945/FlowSec"><img src="https://github.com/codespaces/badge.svg" alt="Open in GitHub Codespaces" /></a>
|
|
42
|
-
<a href="https://badge.fury.io/py/flowsec"><img src="https://badge.fury.io/py/flowsec.svg" alt="PyPI version" /></a>
|
|
43
|
-
</p>
|
|
38
|
+
A Python command-line security tool that scans CI/CD pipeline configurations for attack vectors across **GitHub Actions**, **GitLab CI**, and **Azure DevOps**. Every finding maps to a MITRE ATT&CK technique and an OWASP CICD Top 10 category.
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
>
|
|
47
|
-
> The pipeline is the attack surface. FlowSec treats it that way.
|
|
40
|
+
The pipeline is the attack surface. FlowSec treats it that way.
|
|
48
41
|
|
|
49
|
-
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install flowsec
|
|
48
|
+
```
|
|
50
49
|
|
|
51
50
|
---
|
|
52
51
|
|
|
53
|
-
##
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Scan a GitHub Actions repo
|
|
56
|
+
export GITHUB_TOKEN=your_token
|
|
57
|
+
flowsec scan --github --repo owner/repo
|
|
58
|
+
|
|
59
|
+
# Scan a local GitHub Actions workflow file
|
|
60
|
+
flowsec scan --github --file .github/workflows/ci.yml
|
|
61
|
+
|
|
62
|
+
# Scan a GitLab CI file
|
|
63
|
+
flowsec scan --gitlab --file .gitlab-ci.yml
|
|
64
|
+
|
|
65
|
+
# Scan an Azure DevOps pipeline file
|
|
66
|
+
flowsec scan --azure --file azure-pipelines.yml
|
|
67
|
+
|
|
68
|
+
# Generate an HTML report
|
|
69
|
+
flowsec scan --github --repo owner/repo --output report.html
|
|
70
|
+
|
|
71
|
+
# Generate AI attack narratives (requires ANTHROPIC_API_KEY)
|
|
72
|
+
flowsec scan --github --repo owner/repo --ai
|
|
73
|
+
|
|
74
|
+
# Fail pipeline if findings at or above threshold
|
|
75
|
+
flowsec scan --github --repo owner/repo --fail-on critical
|
|
76
|
+
|
|
77
|
+
# Ignore specific rules
|
|
78
|
+
flowsec scan --github --repo owner/repo --ignore FS006 --ignore FS011
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Platforms and Tokens
|
|
84
|
+
|
|
85
|
+
| Platform | File scan | Remote scan | Token required |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| GitHub Actions | `--github --file` | `--github --repo owner/repo` | `GITHUB_TOKEN` (remote only) |
|
|
88
|
+
| GitLab CI | `--gitlab --file` | `--gitlab --repo namespace/project` | `GITLAB_TOKEN` (remote only) |
|
|
89
|
+
| Azure DevOps | `--azure --file` | `--azure --repo org/project` | `AZURE_DEVOPS_TOKEN` (always — Azure requires auth even for public projects) |
|
|
90
|
+
|
|
91
|
+
Set tokens in a `.env` file in your working directory or as environment variables. FlowSec loads `.env` automatically.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 26 Security Rules
|
|
54
96
|
|
|
55
97
|
| ID | Rule | Severity | MITRE | OWASP |
|
|
56
98
|
|---|---|---|---|---|
|
|
@@ -83,127 +125,20 @@ Description-Content-Type: text/markdown
|
|
|
83
125
|
|
|
84
126
|
---
|
|
85
127
|
|
|
86
|
-
##
|
|
87
|
-
|
|
88
|
-
| Platform | File scan | Remote repo scan |
|
|
89
|
-
|---|---|---|
|
|
90
|
-
| GitHub Actions | `flowsec scan --github --file workflow.yml` | `flowsec scan --github --repo owner/repo` |
|
|
91
|
-
| GitLab CI | `flowsec scan --gitlab --file .gitlab-ci.yml` | `flowsec scan --gitlab --repo namespace/project` |
|
|
92
|
-
| Azure DevOps | `flowsec scan --azure --file azure-pipelines.yml` | `flowsec scan --azure --repo org/project` |
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Real findings on a real repo
|
|
97
|
-
|
|
98
|
-
Scanned `VanshBhardwaj1945/cloud-resume-challenge-azure` — 13 findings across 2 workflow files:
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
[CRITICAL] FS002 - Unpinned Action — Supply Chain Attack Vector
|
|
102
|
-
File: .github/workflows/backend.main.yaml
|
|
103
|
-
Action 'actions/checkout@v4' is not pinned to a commit hash
|
|
128
|
+
## Risk Score
|
|
104
129
|
|
|
105
|
-
|
|
106
|
-
File: .github/workflows/backend.main.yaml
|
|
107
|
-
Pipeline permissions set to 'None' — GitHub defaults apply
|
|
108
|
-
|
|
109
|
-
[HIGH] FS004 - Missing OIDC — Long-Lived Cloud Credential in Use
|
|
110
|
-
File: .github/workflows/backend.main.yaml
|
|
111
|
-
Cloud provider action present but no id-token: write permission
|
|
112
|
-
|
|
113
|
-
[LOW] FS006 - Missing Timeout — Job Runs Up to 6 Hours Unchecked
|
|
114
|
-
File: .github/workflows/backend.main.yaml
|
|
115
|
-
Job 'build-and-deploy' has no timeout — GitHub default is 6 hours
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Stack
|
|
121
|
-
|
|
122
|
-
| Layer | Tools |
|
|
123
|
-
|---|---|
|
|
124
|
-
| Language | Python 3.11 |
|
|
125
|
-
| GitHub Connection | PyGithub |
|
|
126
|
-
| YAML Parsing | PyYAML with custom line-tracking loader |
|
|
127
|
-
| Terminal Output | rich |
|
|
128
|
-
| HTML Reports | Jinja2 — interactive filtering, expandable findings, PDF export |
|
|
129
|
-
| AI Narratives | Anthropic Claude API with local caching |
|
|
130
|
-
| Linting | ruff, mypy, bandit |
|
|
131
|
-
| Packaging | hatch, pyproject.toml |
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Quick Start
|
|
136
|
-
|
|
137
|
-
Click the Codespaces button above to run FlowSec in your browser with zero setup. Or install from PyPI:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
pip install flowsec
|
|
141
|
-
export GITHUB_TOKEN=your_token_here
|
|
142
|
-
flowsec scan --github --repo owner/repo
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Or run from source:
|
|
146
|
-
|
|
147
|
-
```bash
|
|
148
|
-
git clone https://github.com/VanshBhardwaj1945/FlowSec.git
|
|
149
|
-
cd FlowSec
|
|
150
|
-
python3 -m venv .venv
|
|
151
|
-
source .venv/bin/activate
|
|
152
|
-
pip install -e ".[dev]"
|
|
153
|
-
cp .env.example .env
|
|
154
|
-
# Add your GITHUB_TOKEN to .env
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## CLI
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
# Scan a GitHub Actions repo (remote)
|
|
163
|
-
flowsec scan --github --repo owner/repo
|
|
164
|
-
|
|
165
|
-
# Scan a local GitHub Actions file
|
|
166
|
-
flowsec scan --github --file .github/workflows/ci.yml
|
|
167
|
-
|
|
168
|
-
# Scan a GitLab CI file (local)
|
|
169
|
-
flowsec scan --gitlab --file .gitlab-ci.yml
|
|
170
|
-
|
|
171
|
-
# Scan a GitLab CI repo (remote — requires GITLAB_TOKEN)
|
|
172
|
-
flowsec scan --gitlab --repo namespace/project
|
|
173
|
-
|
|
174
|
-
# Scan an Azure DevOps file (local)
|
|
175
|
-
flowsec scan --azure --file azure-pipelines.yml
|
|
176
|
-
|
|
177
|
-
# Scan an Azure DevOps repo (remote — requires AZURE_DEVOPS_TOKEN)
|
|
178
|
-
flowsec scan --azure --repo org/project
|
|
179
|
-
|
|
180
|
-
# Generate an HTML report
|
|
181
|
-
flowsec scan --github --repo owner/repo --output report.html
|
|
182
|
-
|
|
183
|
-
# Generate AI attack narratives (requires ANTHROPIC_API_KEY)
|
|
184
|
-
flowsec scan --github --repo owner/repo --ai
|
|
185
|
-
|
|
186
|
-
# Ignore specific rules
|
|
187
|
-
flowsec scan --github --repo owner/repo --ignore FS006 --ignore FS011
|
|
188
|
-
|
|
189
|
-
# Fail pipeline if findings at or above threshold
|
|
190
|
-
flowsec scan --github --repo owner/repo --fail-on critical
|
|
191
|
-
|
|
192
|
-
# Everything at once
|
|
193
|
-
flowsec scan --github --repo owner/repo --ai --output report.html --fail-on high
|
|
194
|
-
```
|
|
130
|
+
Every scan produces a 0-100 risk score using severity-weighted diminishing returns and exponential normalization. A single critical finding scores ~28. A heavily vulnerable pipeline scores ~90-97. The score cannot exceed 100.
|
|
195
131
|
|
|
196
132
|
---
|
|
197
133
|
|
|
198
134
|
## Rule Suppression
|
|
199
135
|
|
|
200
|
-
Suppress specific rules inline with `--ignore`:
|
|
201
|
-
|
|
202
136
|
```bash
|
|
137
|
+
# CLI flag
|
|
203
138
|
flowsec scan --github --repo owner/repo --ignore FS006 --ignore FS011
|
|
204
|
-
```
|
|
205
139
|
|
|
206
|
-
|
|
140
|
+
# .flowsec.yml in your repo root
|
|
141
|
+
```
|
|
207
142
|
|
|
208
143
|
```yaml
|
|
209
144
|
ignore:
|
|
@@ -217,8 +152,6 @@ ignore:
|
|
|
217
152
|
|
|
218
153
|
## Use as a Pipeline Gate
|
|
219
154
|
|
|
220
|
-
Add FlowSec to your own GitHub Actions workflow to automatically block PRs that introduce security misconfigurations:
|
|
221
|
-
|
|
222
155
|
```yaml
|
|
223
156
|
name: FlowSec Security Scan
|
|
224
157
|
on: [push, pull_request]
|
|
@@ -227,56 +160,29 @@ jobs:
|
|
|
227
160
|
runs-on: ubuntu-latest
|
|
228
161
|
steps:
|
|
229
162
|
- uses: actions/checkout@v4
|
|
230
|
-
-
|
|
231
|
-
|
|
232
|
-
- name: Run FlowSec
|
|
233
|
-
run: flowsec scan --github --file .github/workflows/ci.yml --fail-on critical
|
|
163
|
+
- run: pip install flowsec
|
|
164
|
+
- run: flowsec scan --github --file .github/workflows/ci.yml --fail-on critical
|
|
234
165
|
```
|
|
235
166
|
|
|
236
|
-
`--fail-on` supports `critical`, `high`, `medium`, and `low` thresholds.
|
|
237
|
-
|
|
238
167
|
---
|
|
239
168
|
|
|
240
|
-
##
|
|
241
|
-
|
|
242
|
-
| Component | Status |
|
|
243
|
-
|---|---|
|
|
244
|
-
| Rule engine — BaseRule, Finding, Severity | Complete |
|
|
245
|
-
| YAML parser with line number tracking | Complete |
|
|
246
|
-
| 26 security rules FS001-FS026 | Complete |
|
|
247
|
-
| MITRE ATT&CK + OWASP CICD Top 10 mapping | Complete |
|
|
248
|
-
| Platform-aware rule engine (GitHub/GitLab/Azure) | Complete |
|
|
249
|
-
| GitHub Actions scanner — remote repo and local file | Complete |
|
|
250
|
-
| GitLab CI scanner — remote repo and local file | Complete |
|
|
251
|
-
| Azure DevOps scanner — remote repo and local file | Complete |
|
|
252
|
-
| CLI — `--github/--gitlab/--azure` + `--file/--repo` | Complete |
|
|
253
|
-
| Rich terminal output with risk score (0-100) | Complete |
|
|
254
|
-
| HTML report with filtering, PDF export | Complete |
|
|
255
|
-
| AI attack narratives with local caching | Complete |
|
|
256
|
-
| Line number tracking | Complete |
|
|
257
|
-
| Pipeline gate `--fail-on` | Complete |
|
|
258
|
-
| Rule suppression `--ignore` and `.flowsec.yml` | Complete |
|
|
259
|
-
| GitHub Codespace config | Complete |
|
|
260
|
-
| PyPI publish with OIDC trusted publishing | Complete |
|
|
261
|
-
| CI security workflow (gitleaks, bandit, pip-audit, self-scan) | Complete |
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
## Roadmap
|
|
266
|
-
|
|
267
|
-
**Phase 2 — Expansion**
|
|
268
|
-
Jenkins support, AWS CodePipeline, 20+ rule library, Homebrew formula.
|
|
169
|
+
## Security
|
|
269
170
|
|
|
270
|
-
|
|
171
|
+
FlowSec is built to the same standard it enforces:
|
|
271
172
|
|
|
272
|
-
|
|
173
|
+
- All GitHub Actions pinned to commit SHAs with least-privilege permissions
|
|
174
|
+
- PyPI publishing via OIDC trusted publishing — no long-lived tokens
|
|
175
|
+
- Own CI runs gitleaks, bandit, pip-audit, and FlowSec self-scan on every push
|
|
176
|
+
- Branch protection on main — force push blocked, 4 required status checks
|
|
177
|
+
- YAML parsed with a SafeLoader subclass — no code execution risk
|
|
178
|
+
- HTML reports use Jinja2 autoescape — XSS from finding content prevented
|
|
273
179
|
|
|
274
|
-
|
|
180
|
+
Report vulnerabilities via GitHub Security Advisories or email Scorpio.vansh@gmail.com.
|
|
275
181
|
|
|
276
182
|
---
|
|
277
183
|
|
|
278
|
-
##
|
|
184
|
+
## License
|
|
279
185
|
|
|
280
|
-
|
|
186
|
+
MIT — see LICENSE on GitHub.
|
|
281
187
|
|
|
282
|
-
**
|
|
188
|
+
**Source:** https://github.com/VanshBhardwaj1945/FlowSec
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# FlowSec
|
|
2
|
+
|
|
3
|
+
A Python command-line security tool that scans CI/CD pipeline configurations for attack vectors across **GitHub Actions**, **GitLab CI**, and **Azure DevOps**. Every finding maps to a MITRE ATT&CK technique and an OWASP CICD Top 10 category.
|
|
4
|
+
|
|
5
|
+
The pipeline is the attack surface. FlowSec treats it that way.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install flowsec
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Scan a GitHub Actions repo
|
|
21
|
+
export GITHUB_TOKEN=your_token
|
|
22
|
+
flowsec scan --github --repo owner/repo
|
|
23
|
+
|
|
24
|
+
# Scan a local GitHub Actions workflow file
|
|
25
|
+
flowsec scan --github --file .github/workflows/ci.yml
|
|
26
|
+
|
|
27
|
+
# Scan a GitLab CI file
|
|
28
|
+
flowsec scan --gitlab --file .gitlab-ci.yml
|
|
29
|
+
|
|
30
|
+
# Scan an Azure DevOps pipeline file
|
|
31
|
+
flowsec scan --azure --file azure-pipelines.yml
|
|
32
|
+
|
|
33
|
+
# Generate an HTML report
|
|
34
|
+
flowsec scan --github --repo owner/repo --output report.html
|
|
35
|
+
|
|
36
|
+
# Generate AI attack narratives (requires ANTHROPIC_API_KEY)
|
|
37
|
+
flowsec scan --github --repo owner/repo --ai
|
|
38
|
+
|
|
39
|
+
# Fail pipeline if findings at or above threshold
|
|
40
|
+
flowsec scan --github --repo owner/repo --fail-on critical
|
|
41
|
+
|
|
42
|
+
# Ignore specific rules
|
|
43
|
+
flowsec scan --github --repo owner/repo --ignore FS006 --ignore FS011
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Platforms and Tokens
|
|
49
|
+
|
|
50
|
+
| Platform | File scan | Remote scan | Token required |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| GitHub Actions | `--github --file` | `--github --repo owner/repo` | `GITHUB_TOKEN` (remote only) |
|
|
53
|
+
| GitLab CI | `--gitlab --file` | `--gitlab --repo namespace/project` | `GITLAB_TOKEN` (remote only) |
|
|
54
|
+
| Azure DevOps | `--azure --file` | `--azure --repo org/project` | `AZURE_DEVOPS_TOKEN` (always — Azure requires auth even for public projects) |
|
|
55
|
+
|
|
56
|
+
Set tokens in a `.env` file in your working directory or as environment variables. FlowSec loads `.env` automatically.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 26 Security Rules
|
|
61
|
+
|
|
62
|
+
| ID | Rule | Severity | MITRE | OWASP |
|
|
63
|
+
|---|---|---|---|---|
|
|
64
|
+
| FS001 | Hardcoded Secret — Plaintext Credential in Workflow | CRITICAL | T1552.001 | CICD-SEC-6 |
|
|
65
|
+
| FS002 | Unpinned Action — Supply Chain Attack Vector | CRITICAL | T1195.001 | CICD-SEC-3 |
|
|
66
|
+
| FS003 | Excessive Permissions — Overprivileged Workflow Token | HIGH | T1078 | CICD-SEC-5 |
|
|
67
|
+
| FS004 | Missing OIDC — Long-Lived Cloud Credential in Use | HIGH | T1552.004 | CICD-SEC-6 |
|
|
68
|
+
| FS005 | Pull Request Target — Secrets Exposed to Fork Code | CRITICAL | T1611 | CICD-SEC-4 |
|
|
69
|
+
| FS006 | Missing Timeout — Job Runs Up to 6 Hours Unchecked | LOW | T1499 | CICD-SEC-10 |
|
|
70
|
+
| FS007 | Self-Hosted Runner — Persistent Environment Risk | HIGH | T1053 | CICD-SEC-7 |
|
|
71
|
+
| FS008 | Missing Artifact Signing — No Tamper Protection | MEDIUM | T1553 | CICD-SEC-8 |
|
|
72
|
+
| FS009 | Unpinned Dependency — Package Installed Without Version Lock | HIGH | T1195.002 | CICD-SEC-3 |
|
|
73
|
+
| FS010 | Secret in Run Command — Plaintext Credential in Shell Step | CRITICAL | T1552.001 | CICD-SEC-6 |
|
|
74
|
+
| FS011 | GitHub Context Injection — Untrusted Event Data in Run Step | CRITICAL | T1059.004 | CICD-SEC-4 |
|
|
75
|
+
| FS012 | Missing Environment Protection — Deploy Job Has No Approval Gate | HIGH | T1078 | CICD-SEC-5 |
|
|
76
|
+
| FS013 | Workflow Dispatch Injection — Unvalidated Input in Shell Command | CRITICAL | T1059 | CICD-SEC-9 |
|
|
77
|
+
| FS014 | Mutable Container Image — Unpinned Image Tag in Pipeline | MEDIUM | T1195.001 | CICD-SEC-3 |
|
|
78
|
+
| FS015 | Persist Credentials — GitHub Token Remains in Git Config After Checkout | MEDIUM | T1552.001 | CICD-SEC-6 |
|
|
79
|
+
| FS016 | workflow_run Trigger — Privileged Execution from Untrusted Workflow | HIGH | T1059 | CICD-SEC-1 |
|
|
80
|
+
| FS017 | Security Scan Silenced — Failures Suppressed with continue-on-error | MEDIUM | T1562.001 | CICD-SEC-7 |
|
|
81
|
+
| FS018 | Secret as CLI Argument — Credential Exposed in Process List | HIGH | T1552 | CICD-SEC-6 |
|
|
82
|
+
| FS019 | Unverified Install Script — Remote Code Fetched and Executed Directly | HIGH | T1195.002 | CICD-SEC-3 |
|
|
83
|
+
| FS020 | Container Running as Root — Elevated Privilege in Pipeline | HIGH | T1611 | CICD-SEC-7 |
|
|
84
|
+
| FS021 | Secret in Docker Build Argument — Credential Stored in Image History | HIGH | T1552.001 | CICD-SEC-6 |
|
|
85
|
+
| FS022 | Broad Artifact Upload — Entire Workspace Exposed as Artifact | MEDIUM | T1560 | CICD-SEC-9 |
|
|
86
|
+
| FS023 | Insecure curl — SSL Verification Disabled in Pipeline | HIGH | T1071 | CICD-SEC-3 |
|
|
87
|
+
| FS024 | Privileged Docker Container — Full Host Access Granted in Pipeline | CRITICAL | T1611 | CICD-SEC-7 |
|
|
88
|
+
| FS025 | Environment Variables Printed to Logs — Secrets Exposed in Pipeline Output | MEDIUM | T1552.001 | CICD-SEC-6 |
|
|
89
|
+
| FS026 | Unguarded Deploy — Deployment Job Runs on Untrusted Branches | HIGH | T1078 | CICD-SEC-1 |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Risk Score
|
|
94
|
+
|
|
95
|
+
Every scan produces a 0-100 risk score using severity-weighted diminishing returns and exponential normalization. A single critical finding scores ~28. A heavily vulnerable pipeline scores ~90-97. The score cannot exceed 100.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Rule Suppression
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# CLI flag
|
|
103
|
+
flowsec scan --github --repo owner/repo --ignore FS006 --ignore FS011
|
|
104
|
+
|
|
105
|
+
# .flowsec.yml in your repo root
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
ignore:
|
|
110
|
+
- rule_id: FS006
|
|
111
|
+
reason: "We use external timeout management"
|
|
112
|
+
- rule_id: FS011
|
|
113
|
+
reason: "Branch protection managed at org level"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Use as a Pipeline Gate
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
name: FlowSec Security Scan
|
|
122
|
+
on: [push, pull_request]
|
|
123
|
+
jobs:
|
|
124
|
+
security:
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
steps:
|
|
127
|
+
- uses: actions/checkout@v4
|
|
128
|
+
- run: pip install flowsec
|
|
129
|
+
- run: flowsec scan --github --file .github/workflows/ci.yml --fail-on critical
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Security
|
|
135
|
+
|
|
136
|
+
FlowSec is built to the same standard it enforces:
|
|
137
|
+
|
|
138
|
+
- All GitHub Actions pinned to commit SHAs with least-privilege permissions
|
|
139
|
+
- PyPI publishing via OIDC trusted publishing — no long-lived tokens
|
|
140
|
+
- Own CI runs gitleaks, bandit, pip-audit, and FlowSec self-scan on every push
|
|
141
|
+
- Branch protection on main — force push blocked, 4 required status checks
|
|
142
|
+
- YAML parsed with a SafeLoader subclass — no code execution risk
|
|
143
|
+
- HTML reports use Jinja2 autoescape — XSS from finding content prevented
|
|
144
|
+
|
|
145
|
+
Report vulnerabilities via GitHub Security Advisories or email Scorpio.vansh@gmail.com.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT — see LICENSE on GitHub.
|
|
152
|
+
|
|
153
|
+
**Source:** https://github.com/VanshBhardwaj1945/FlowSec
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## What is this project?
|
|
4
4
|
|
|
5
|
-
FlowSec is a published Python command
|
|
5
|
+
FlowSec is a published Python command-line security tool that scans CI/CD pipeline configurations for attack vectors. Point it at a GitHub repository, a GitLab project, or an Azure DevOps pipeline and it pulls the config, runs 26 security rules against it, and returns a prioritized list of findings — each mapped to a MITRE ATT&CK technique and an OWASP CICD Top 10 category.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
pip install flowsec
|
|
9
|
-
|
|
9
|
+
export GITHUB_TOKEN=your_token
|
|
10
|
+
flowsec scan --github --repo owner/repo
|
|
10
11
|
```
|
|
11
12
|
|
|
12
13
|
---
|
|
@@ -15,7 +16,7 @@ flowsec scan --repo owner/repo
|
|
|
15
16
|
|
|
16
17
|
Most security tooling audits application code. Almost nothing audits the pipelines that build, test, and deploy that code — and those pipelines are one of the most dangerous attack surfaces in a modern software organization.
|
|
17
18
|
|
|
18
|
-
A CI/CD pipeline runs automatically on every push. It has access to production secrets, cloud credentials, and deployment infrastructure. It pulls dependencies from external sources. It often runs with elevated permissions. And in most organizations,
|
|
19
|
+
A CI/CD pipeline runs automatically on every push. It has access to production secrets, cloud credentials, and deployment infrastructure. It pulls dependencies from external sources. It often runs with elevated permissions. And in most organizations, pipeline config files are treated as an afterthought — written once, never reviewed, sitting in git forever.
|
|
19
20
|
|
|
20
21
|
FlowSec treats pipeline configuration files the same way a penetration tester would — as configs to audit for attack vectors before an attacker finds them first.
|
|
21
22
|
|
|
@@ -27,16 +28,17 @@ FlowSec treats pipeline configuration files the same way a penetration tester wo
|
|
|
27
28
|
|---|---|
|
|
28
29
|
| Python 3.11 | Core language |
|
|
29
30
|
| PyGithub | GitHub API — fetches workflow files from target repos |
|
|
30
|
-
|
|
|
31
|
+
| python-gitlab | GitLab API — fetches .gitlab-ci.yml from remote projects |
|
|
32
|
+
| httpx | Azure DevOps REST API — fetches azure-pipelines.yml |
|
|
33
|
+
| PyYAML | Parses YAML pipeline configs into Python dicts with line tracking |
|
|
31
34
|
| rich | Colored terminal output — findings tables, risk score summary |
|
|
32
35
|
| anthropic | Claude API — generates AI attack narratives per finding |
|
|
33
|
-
| jinja2 | HTML report templating |
|
|
36
|
+
| jinja2 | HTML report templating (autoescape enabled) |
|
|
34
37
|
| python-dotenv | Loads API credentials from .env locally |
|
|
35
38
|
| ruff | Linting and formatting |
|
|
36
39
|
| mypy | Type checking |
|
|
37
40
|
| bandit | Scans FlowSec's own Python code for security issues |
|
|
38
41
|
| hatch | Build and PyPI publishing |
|
|
39
|
-
| Docker | Multi-stage container packaging |
|
|
40
42
|
|
|
41
43
|
---
|
|
42
44
|
|
|
@@ -44,18 +46,18 @@ FlowSec treats pipeline configuration files the same way a penetration tester wo
|
|
|
44
46
|
|
|
45
47
|
```
|
|
46
48
|
FlowSec/
|
|
47
|
-
├── .devcontainer/
|
|
48
|
-
│ └── devcontainer.json — GitHub Codespace config
|
|
49
49
|
├── .github/
|
|
50
50
|
│ └── workflows/
|
|
51
|
-
│
|
|
51
|
+
│ ├── publish.yml — PyPI publish on v* tag push (OIDC trusted publishing)
|
|
52
|
+
│ └── security.yml — Security scans on every push/PR (gitleaks, bandit, pip-audit, self-scan)
|
|
52
53
|
├── src/
|
|
53
54
|
│ └── pipelineguard/
|
|
54
55
|
│ ├── cli.py — argparse CLI, Rich terminal output
|
|
55
|
-
│ ├── scanner.py —
|
|
56
|
+
│ ├── scanner.py — All scan_* functions and remote API fetchers
|
|
56
57
|
│ ├── parser.py — YAML to Python dict with line number tracking
|
|
57
58
|
│ ├── report.py — HTML report generation via Jinja2
|
|
58
|
-
│ ├── ai_narrative.py — Claude API integration with local
|
|
59
|
+
│ ├── ai_narrative.py — Claude API integration with local MD5 cache
|
|
60
|
+
│ ├── scoring.py — Risk score: 0-100, diminishing returns + exponential normalization
|
|
59
61
|
│ └── rules/
|
|
60
62
|
│ ├── base.py — BaseRule ABC, Finding dataclass, Severity enum
|
|
61
63
|
│ ├── hardcoded_secrets.py — FS001
|
|
@@ -68,6 +70,7 @@ FlowSec/
|
|
|
68
70
|
│ ├── artifact_signing.py — FS008
|
|
69
71
|
│ ├── dependency_pinning.py — FS009
|
|
70
72
|
│ ├── secrets_in_run.py — FS010
|
|
73
|
+
│ ├── github_context_injection.py — FS011
|
|
71
74
|
│ ├── missing_env_protection.py — FS012
|
|
72
75
|
│ ├── workflow_dispatch_injection.py — FS013
|
|
73
76
|
│ ├── mutable_container_image.py — FS014
|
|
@@ -84,15 +87,23 @@ FlowSec/
|
|
|
84
87
|
│ ├── env_vars_in_logs.py — FS025
|
|
85
88
|
│ └── deploy_all_branches.py — FS026
|
|
86
89
|
├── tests/
|
|
87
|
-
│
|
|
88
|
-
│
|
|
89
|
-
│
|
|
90
|
-
│
|
|
91
|
-
│
|
|
90
|
+
│ ├── fixtures/
|
|
91
|
+
│ │ ├── github_all_vulns.yml — GitHub Actions fixture (every FS rule fires)
|
|
92
|
+
│ │ ├── gitlab_all_vulns.yml — GitLab CI fixture (every applicable rule fires)
|
|
93
|
+
│ │ └── azure_all_vulns.yml — Azure DevOps fixture (every applicable rule fires)
|
|
94
|
+
│ ├── test_github_rules.py
|
|
95
|
+
│ ├── test_gitlab_rules.py
|
|
96
|
+
│ └── test_azure_rules.py
|
|
97
|
+
├── docs/
|
|
98
|
+
│ ├── FULL_README.md — This file
|
|
99
|
+
│ └── screenshots/
|
|
100
|
+
│ └── flowsec-logo.png
|
|
92
101
|
├── pyproject.toml
|
|
93
|
-
├──
|
|
94
|
-
├──
|
|
95
|
-
|
|
102
|
+
├── PYPI_README.md — PyPI page description (no images, clean text)
|
|
103
|
+
├── README.md — GitHub repo README (with logo, badges)
|
|
104
|
+
├── SECURITY.md — Responsible disclosure policy
|
|
105
|
+
├── AGENTS.md — Guide for AI agents working on this codebase
|
|
106
|
+
└── LICENSE — MIT License
|
|
96
107
|
```
|
|
97
108
|
|
|
98
109
|
---
|
|
@@ -102,31 +113,35 @@ FlowSec/
|
|
|
102
113
|
Five layers. Each has one job and hands its output to the next.
|
|
103
114
|
|
|
104
115
|
**Layer 1 — Connect**
|
|
105
|
-
For GitHub, PyGithub fetches every `.yml` and `.yaml` file inside `.github/workflows/` using a personal access token. For GitLab
|
|
116
|
+
For GitHub, PyGithub fetches every `.yml` and `.yaml` file inside `.github/workflows/` using a personal access token. For GitLab, `python-gitlab` fetches `.gitlab-ci.yml` from the project's default branch. For Azure DevOps, `httpx` calls the Azure DevOps REST API with a PAT. All paths produce raw YAML text. For local file scans, the file is read directly.
|
|
106
117
|
|
|
107
118
|
**Layer 2 — Parse**
|
|
108
|
-
A custom PyYAML loader called `LineLoader` converts raw YAML into a Python dictionary while preserving line number information for every key — stored as hidden `__line_KEYNAME__` entries. This is what allows findings to report the exact line a hardcoded secret lives on rather than just the file name.
|
|
119
|
+
A custom PyYAML loader called `LineLoader` (a subclass of `yaml.SafeLoader`) converts raw YAML into a Python dictionary while preserving line number information for every key — stored as hidden `__line_KEYNAME__` entries. This is what allows findings to report the exact line a hardcoded secret lives on rather than just the file name.
|
|
109
120
|
|
|
110
121
|
**Layer 3 — Scan**
|
|
111
|
-
The rule engine calls `check(config, file_path, platform)` on every rule in the `RULES` list. The `platform` parameter tells each rule which part of the config to inspect
|
|
122
|
+
The rule engine calls `check(config, file_path, platform)` on every rule in the `RULES` list. The `platform` parameter (`"github"`, `"gitlab"`, or `"azure"`) tells each rule which part of the config to inspect. All findings from all rules across all files are collected into one flat list.
|
|
112
123
|
|
|
113
124
|
**Layer 4 — Score**
|
|
114
|
-
Findings are aggregated into a
|
|
125
|
+
Findings are aggregated into a risk score (0-100) using severity-weighted diminishing returns:
|
|
126
|
+
- Each additional finding of the same severity contributes less than the previous: `weight / sqrt(n)`
|
|
127
|
+
- Weights: CRITICAL=10, HIGH=5, MEDIUM=3, LOW=1
|
|
128
|
+
- Normalized via exponential decay: `round(100 * (1 - e^(-raw/30)))`
|
|
129
|
+
- Score asymptotes to 100 — it cannot exceed it
|
|
115
130
|
|
|
116
131
|
**Layer 5 — Output**
|
|
117
|
-
Three output modes
|
|
132
|
+
Three output modes: the terminal gets a Rich colored table with severity badges, MITRE and OWASP columns, and a summary panel with the 0-100 risk score. An HTML report is generated with `--output`. AI attack narratives are generated with `--ai` via the Claude API and cached locally in `~/.flowsec_cache.json`.
|
|
118
133
|
|
|
119
134
|
---
|
|
120
135
|
|
|
121
136
|
## Platform Support
|
|
122
137
|
|
|
123
|
-
| Platform |
|
|
124
|
-
|
|
125
|
-
| GitHub Actions | `--
|
|
126
|
-
| GitLab CI | `--gitlab .gitlab-ci.yml` |
|
|
127
|
-
| Azure DevOps | `--azure azure-pipelines.yml` |
|
|
138
|
+
| Platform | File scan | Remote repo scan | Required token |
|
|
139
|
+
|---|---|---|---|
|
|
140
|
+
| GitHub Actions | `--github --file workflow.yml` | `--github --repo owner/repo` | `GITHUB_TOKEN` (for remote) |
|
|
141
|
+
| GitLab CI | `--gitlab --file .gitlab-ci.yml` | `--gitlab --repo namespace/project` | `GITLAB_TOKEN` (for remote) |
|
|
142
|
+
| Azure DevOps | `--azure --file azure-pipelines.yml` | `--azure --repo org/project` | `AZURE_DEVOPS_TOKEN` (remote only — Azure requires auth even for public projects) |
|
|
128
143
|
|
|
129
|
-
Rules that are GitHub-specific — FS002, FS003, FS004, FS005, FS008, FS011, FS012, FS013, FS015, FS016 — return no findings for GitLab and Azure. Rules that apply to all platforms
|
|
144
|
+
Rules that are GitHub-specific — FS002, FS003, FS004, FS005, FS008, FS011, FS012, FS013, FS015, FS016 — return no findings for GitLab and Azure. Rules that apply to all platforms adapt their field lookups based on the platform parameter.
|
|
130
145
|
|
|
131
146
|
---
|
|
132
147
|
|
|
@@ -136,11 +151,11 @@ Built around three components in `src/pipelineguard/rules/base.py`.
|
|
|
136
151
|
|
|
137
152
|
**Severity** is an enum — CRITICAL, HIGH, MEDIUM, LOW. Using an enum instead of plain strings means a typo throws an error immediately rather than silently breaking a report.
|
|
138
153
|
|
|
139
|
-
**Finding** is a dataclass every rule returns when it detects a problem. Every finding has the same shape regardless of which rule or platform produced it
|
|
154
|
+
**Finding** is a dataclass every rule returns when it detects a problem. Every finding has the same shape regardless of which rule or platform produced it: rule ID, title, severity, description, remediation, MITRE technique, OWASP category, file path, line number, and AI narrative.
|
|
140
155
|
|
|
141
156
|
**BaseRule** is an abstract base class every rule inherits from. It enforces that every rule implements `check(config, file_path, platform)`. If a new rule is written without it, Python throws an error at import time.
|
|
142
157
|
|
|
143
|
-
Adding a new rule is four steps: create a file in `src/pipelineguard/rules/`, inherit from `BaseRule`, implement `check()`, add it to `RULES` in `scanner.py`. Nothing else changes.
|
|
158
|
+
Adding a new rule is four steps: create a file in `src/pipelineguard/rules/`, inherit from `BaseRule`, implement `check()`, add it to `RULES` in `scanner.py`. Nothing else changes. Next available ID: **FS027**.
|
|
144
159
|
|
|
145
160
|
---
|
|
146
161
|
|
|
@@ -175,6 +190,8 @@ Adding a new rule is four steps: create a file in `src/pipelineguard/rules/`, in
|
|
|
175
190
|
| FS025 | Environment Variables Printed to Logs — Secrets Exposed in Pipeline Output | MEDIUM | T1552.001 | CICD-SEC-6 | All |
|
|
176
191
|
| FS026 | Unguarded Deploy — Deployment Job Runs on Untrusted Branches | HIGH | T1078 | CICD-SEC-1 | All |
|
|
177
192
|
|
|
193
|
+
### Rule Details
|
|
194
|
+
|
|
178
195
|
**FS001** scans env variables across all platforms for suspicious names — API_KEY, PASSWORD, TOKEN, SECRET — whose values don't reference a secret manager. `${{ secrets.X }}` is safe. A hardcoded string is not.
|
|
179
196
|
|
|
180
197
|
**FS002** flags any `uses:` field not pinned to a full 40-character git commit hash. Tags like `@v3` and branches like `@main` are mutable — an attacker who compromises the action repo can push malicious code that every unpinned pipeline pulls automatically. Same attack pattern as SolarWinds.
|
|
@@ -195,38 +212,37 @@ Adding a new rule is four steps: create a file in `src/pipelineguard/rules/`, in
|
|
|
195
212
|
|
|
196
213
|
**FS010** scans shell commands for credential patterns — `password=`, `token=`, `Authorization: Bearer` — followed by values that don't reference a secret manager. Credentials in shell commands appear in pipeline logs and git history.
|
|
197
214
|
|
|
215
|
+
**FS011** flags `${{ github.event.issue.title }}`, `${{ github.head_ref }}`, `${{ github.event.pull_request.body }}`, and similar untrusted GitHub context expressions used directly inside `run:` steps. These values come from external actors and become arbitrary command injection if interpolated unquoted into shell.
|
|
198
216
|
|
|
199
|
-
**FS012** flags deployment jobs — identified by keywords like `deploy`, `release`, `prod` in the job name — that have no `environment:` key. Without GitHub Environments, deployments to production happen automatically with no human approval.
|
|
217
|
+
**FS012** flags deployment jobs — identified by keywords like `deploy`, `release`, `prod` in the job name — that have no `environment:` key. Without GitHub Environments, deployments to production happen automatically with no human approval gate.
|
|
200
218
|
|
|
201
219
|
**FS013** flags `workflow_dispatch` workflows where `${{ inputs.* }}` appears unquoted in shell commands. An attacker with access to trigger the workflow can inject arbitrary shell commands through the input fields.
|
|
202
220
|
|
|
203
|
-
**
|
|
204
|
-
|
|
205
|
-
**FS021** flags `docker build --build-arg` commands where the argument name matches credential patterns — API_KEY, TOKEN, PASSWORD. Build arguments are stored in image layer history and readable by anyone with access to the image via `docker history`.
|
|
221
|
+
**FS014** flags container images referenced in pipeline configs without a digest pin. Tags like `:latest` or `:v1` are mutable. Only a `@sha256:` digest guarantees the exact image bytes your pipeline will run.
|
|
206
222
|
|
|
207
|
-
**
|
|
223
|
+
**FS015** flags `actions/checkout` steps that do not explicitly set `persist-credentials: false`. The GitHub token is written to the local git config for the duration of the job — any step that runs afterward can read it.
|
|
208
224
|
|
|
209
|
-
**
|
|
225
|
+
**FS016** flags `workflow_run:` as a trigger. Like `pull_request_target`, `workflow_run` runs in the context of the base branch with access to secrets, but is triggered by a workflow from a fork.
|
|
210
226
|
|
|
211
|
-
**
|
|
227
|
+
**FS017** flags security scanning steps — Trivy, Snyk, Bandit, Semgrep, Checkov, Grype, Gitleaks — that have `continue-on-error: true` (GitHub), `allow_failure: true` (GitLab), or `continueOnError: true` (Azure). Silencing scanner failures means a finding that would block a deploy is silently swallowed.
|
|
212
228
|
|
|
213
|
-
**
|
|
229
|
+
**FS018** flags shell commands where a secret or credential is passed as a named CLI flag — `--token`, `--password`, `--api-key` — with a value from the secrets context. CLI arguments are visible to any process that can read `/proc/<pid>/cmdline` on Linux.
|
|
214
230
|
|
|
215
|
-
**
|
|
231
|
+
**FS019** flags `curl ... | bash` and `wget ... | bash` patterns — fetching a remote script and piping it directly to a shell interpreter without any checksum verification.
|
|
216
232
|
|
|
217
|
-
**
|
|
233
|
+
**FS020** flags `docker run` commands without a `--user` flag or with `--user root`. Containers running as root have elevated privileges that expand the blast radius if the pipeline is compromised.
|
|
218
234
|
|
|
219
|
-
**
|
|
235
|
+
**FS021** flags `docker build --build-arg` commands where the argument name matches credential patterns. Build arguments are stored in image layer history and readable via `docker history`.
|
|
220
236
|
|
|
221
|
-
**
|
|
237
|
+
**FS022** flags artifact upload steps that use overly broad path patterns — `.`, `*`, `**` — that would upload the entire workspace. Workspaces can contain `.env` files, build outputs with embedded secrets, and any files written by previous steps.
|
|
222
238
|
|
|
223
|
-
**
|
|
239
|
+
**FS023** flags `curl -k` and `curl --insecure` in run commands. Disabling SSL verification allows a man-in-the-middle attacker to serve malicious content to your pipeline.
|
|
224
240
|
|
|
225
|
-
**FS024** flags `docker run --privileged`, `--cap-add SYS_ADMIN`, and `--security-opt seccomp=unconfined
|
|
241
|
+
**FS024** flags `docker run --privileged`, `--cap-add SYS_ADMIN`, and `--security-opt seccomp=unconfined`. A privileged container has full access to the host kernel and devices — it can escape the container boundary entirely.
|
|
226
242
|
|
|
227
|
-
**FS025** flags
|
|
243
|
+
**FS025** flags `env`, `printenv`, and `echo $VARIABLE` in pipeline steps. These dump environment variables to pipeline logs which are visible to all repo contributors.
|
|
228
244
|
|
|
229
|
-
**FS026** flags deployment jobs that run without branch guards
|
|
245
|
+
**FS026** flags deployment jobs that run without branch guards — on any platform. Without branch guards, a push to any feature branch can trigger a production deployment.
|
|
230
246
|
|
|
231
247
|
---
|
|
232
248
|
|
|
@@ -234,43 +250,47 @@ Adding a new rule is four steps: create a file in `src/pipelineguard/rules/`, in
|
|
|
234
250
|
|
|
235
251
|
```bash
|
|
236
252
|
# GitHub Actions — remote repo
|
|
237
|
-
flowsec scan --repo
|
|
253
|
+
flowsec scan --github --repo owner/repo
|
|
238
254
|
|
|
239
255
|
# GitHub Actions — local file
|
|
240
|
-
flowsec scan --file .github/workflows/ci.yml
|
|
256
|
+
flowsec scan --github --file .github/workflows/ci.yml
|
|
257
|
+
|
|
258
|
+
# GitLab CI — remote project (requires GITLAB_TOKEN)
|
|
259
|
+
flowsec scan --gitlab --repo namespace/project
|
|
260
|
+
|
|
261
|
+
# GitLab CI — local file
|
|
262
|
+
flowsec scan --gitlab --file .gitlab-ci.yml
|
|
241
263
|
|
|
242
|
-
#
|
|
243
|
-
flowsec scan --
|
|
264
|
+
# Azure DevOps — remote (requires AZURE_DEVOPS_TOKEN)
|
|
265
|
+
flowsec scan --azure --repo org/project
|
|
244
266
|
|
|
245
|
-
# Azure DevOps
|
|
246
|
-
flowsec scan --azure azure-pipelines.yml
|
|
267
|
+
# Azure DevOps — local file
|
|
268
|
+
flowsec scan --azure --file azure-pipelines.yml
|
|
247
269
|
|
|
248
270
|
# HTML report
|
|
249
|
-
flowsec scan --repo owner/repo --output report.html
|
|
271
|
+
flowsec scan --github --repo owner/repo --output report.html
|
|
250
272
|
|
|
251
|
-
# AI attack narratives
|
|
252
|
-
flowsec scan --repo owner/repo --ai
|
|
273
|
+
# AI attack narratives (requires ANTHROPIC_API_KEY)
|
|
274
|
+
flowsec scan --github --repo owner/repo --ai
|
|
253
275
|
|
|
254
|
-
# Pipeline gate
|
|
255
|
-
flowsec scan --repo owner/repo --fail-on critical
|
|
276
|
+
# Pipeline gate — exit code 1 if findings at or above threshold
|
|
277
|
+
flowsec scan --github --repo owner/repo --fail-on critical
|
|
256
278
|
|
|
257
|
-
#
|
|
258
|
-
flowsec scan --repo owner/repo --ignore FS006 --ignore FS011
|
|
279
|
+
# Suppress specific rules
|
|
280
|
+
flowsec scan --github --repo owner/repo --ignore FS006 --ignore FS011
|
|
259
281
|
|
|
260
282
|
# Everything at once
|
|
261
|
-
flowsec scan --repo owner/repo --ai --output report.html --fail-on high --ignore FS006
|
|
283
|
+
flowsec scan --github --repo owner/repo --ai --output report.html --fail-on high --ignore FS006
|
|
262
284
|
```
|
|
263
285
|
|
|
264
286
|
---
|
|
265
287
|
|
|
266
288
|
## Rule Suppression
|
|
267
289
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
**Via CLI flag** — pass `--ignore` one or more times:
|
|
290
|
+
**Via CLI flag:**
|
|
271
291
|
|
|
272
292
|
```bash
|
|
273
|
-
flowsec scan --repo owner/repo --ignore FS006 --ignore FS011
|
|
293
|
+
flowsec scan --github --repo owner/repo --ignore FS006 --ignore FS011
|
|
274
294
|
```
|
|
275
295
|
|
|
276
296
|
**Via config file** — create `.flowsec.yml` in the directory where you run FlowSec:
|
|
@@ -283,7 +303,9 @@ ignore:
|
|
|
283
303
|
reason: "Branch protection managed at org level"
|
|
284
304
|
```
|
|
285
305
|
|
|
286
|
-
Both methods can be used together
|
|
306
|
+
Both methods can be used together. The `reason` field is optional but recommended for audit trails.
|
|
307
|
+
|
|
308
|
+
---
|
|
287
309
|
|
|
288
310
|
## Pipeline Gate
|
|
289
311
|
|
|
@@ -300,10 +322,10 @@ jobs:
|
|
|
300
322
|
- name: Install FlowSec
|
|
301
323
|
run: pip install flowsec
|
|
302
324
|
- name: Run FlowSec
|
|
303
|
-
run: flowsec scan --file .github/workflows/ --fail-on critical
|
|
325
|
+
run: flowsec scan --github --file .github/workflows/ci.yml --fail-on critical
|
|
304
326
|
```
|
|
305
327
|
|
|
306
|
-
`--fail-on` exits with code 1 if findings at or above the threshold are found. Supported
|
|
328
|
+
`--fail-on` exits with code 1 if findings at or above the threshold are found. Supported: `critical`, `high`, `medium`, `low`.
|
|
307
329
|
|
|
308
330
|
---
|
|
309
331
|
|
|
@@ -315,9 +337,10 @@ When `--ai` is passed, FlowSec calls the Claude API per finding and generates a
|
|
|
315
337
|
Attack Vector: how the attacker exploits this specific misconfiguration
|
|
316
338
|
What They Gain: what access or capability they obtain
|
|
317
339
|
Blast Radius: realistic worst case impact
|
|
340
|
+
Ways to Fix: remediation guidance
|
|
318
341
|
```
|
|
319
342
|
|
|
320
|
-
Narratives are cached in `~/.flowsec_cache.json` using
|
|
343
|
+
Narratives are cached in `~/.flowsec_cache.json` using a content hash of the finding's rule ID and description. The same finding is never sent to the API twice.
|
|
321
344
|
|
|
322
345
|
---
|
|
323
346
|
|
|
@@ -325,7 +348,7 @@ Narratives are cached in `~/.flowsec_cache.json` using an MD5 hash of the findin
|
|
|
325
348
|
|
|
326
349
|
Generated with `--output report.html`. A self-contained single file — no internet connection needed.
|
|
327
350
|
|
|
328
|
-
- Summary cards per severity and
|
|
351
|
+
- Summary cards per severity and 0-100 risk score — clickable to filter
|
|
329
352
|
- Findings overview table with MITRE and OWASP columns
|
|
330
353
|
- Expandable finding cards with description, remediation, and AI narrative
|
|
331
354
|
- OWASP tags in green, MITRE tags in blue
|
|
@@ -333,6 +356,34 @@ Generated with `--output report.html`. A self-contained single file — no inter
|
|
|
333
356
|
|
|
334
357
|
---
|
|
335
358
|
|
|
359
|
+
## Risk Score (0-100)
|
|
360
|
+
|
|
361
|
+
The risk score uses severity-weighted diminishing returns normalized to 0-100:
|
|
362
|
+
|
|
363
|
+
1. For each severity level, each additional finding contributes `weight / sqrt(n)` — finding the same issue in 10 jobs is worse than 1 job, but not 10x worse
|
|
364
|
+
2. Weights: CRITICAL=10, HIGH=5, MEDIUM=3, LOW=1
|
|
365
|
+
3. Final normalization: `round(100 * (1 - e^(-raw/30)))` — asymptotes to 100, never exceeds it
|
|
366
|
+
|
|
367
|
+
A single critical finding scores ~28. A heavily vulnerable real pipeline scores ~90-97. A score of 100 is practically unreachable.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Security Design
|
|
372
|
+
|
|
373
|
+
FlowSec is built to the same standard it enforces.
|
|
374
|
+
|
|
375
|
+
- **Own CI:** gitleaks (secret scanning), bandit (SAST), pip-audit (dependency CVEs), and FlowSec self-scan on every push
|
|
376
|
+
- **PyPI publishing:** OIDC trusted publishing — no long-lived API tokens. Sigstore attestations generated automatically by PyPI
|
|
377
|
+
- **GitHub Actions:** all actions pinned to commit SHAs, `persist-credentials: false`, explicit least-privilege permissions, timeouts on every job
|
|
378
|
+
- **Branch protection on main:** force push blocked, direct deletion blocked, 4 required status checks before merge
|
|
379
|
+
- **YAML parsing:** `LineLoader` is a subclass of `yaml.SafeLoader` — arbitrary code execution via YAML is not possible
|
|
380
|
+
- **HTML reports:** Jinja2 `autoescape=select_autoescape(["html"])` — XSS from finding content is prevented
|
|
381
|
+
- **Tokens:** loaded from environment variables / `.env` only — never passed as CLI arguments
|
|
382
|
+
- **Remote HTTP:** explicit `timeout=15` on all external requests — no indefinite hangs
|
|
383
|
+
- **Responsible disclosure:** see `SECURITY.md`
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
336
387
|
## Real Findings — cloud-resume-challenge-azure
|
|
337
388
|
|
|
338
389
|
```
|
|
@@ -361,6 +412,19 @@ Generated with `--output report.html`. A self-contained single file — no inter
|
|
|
361
412
|
|
|
362
413
|
---
|
|
363
414
|
|
|
415
|
+
## Environment Variables
|
|
416
|
+
|
|
417
|
+
| Variable | Required for | Where to get it |
|
|
418
|
+
|---|---|---|
|
|
419
|
+
| `GITHUB_TOKEN` | `--github --repo` | GitHub Settings → Developer settings → Personal access tokens |
|
|
420
|
+
| `GITLAB_TOKEN` | `--gitlab --repo` | GitLab Settings → Access Tokens |
|
|
421
|
+
| `AZURE_DEVOPS_TOKEN` | `--azure --repo` | Azure DevOps → User Settings → Personal Access Tokens |
|
|
422
|
+
| `ANTHROPIC_API_KEY` | `--ai` | console.anthropic.com |
|
|
423
|
+
|
|
424
|
+
Store these in a `.env` file in the project root. FlowSec loads it automatically via `python-dotenv`. The `.env` file is gitignored.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
364
428
|
## Roadmap
|
|
365
429
|
|
|
366
430
|
**Phase 2 — Expansion**
|
|
@@ -368,17 +432,14 @@ Jenkins support, AWS CodePipeline, 20+ rule library, Homebrew formula.
|
|
|
368
432
|
|
|
369
433
|
---
|
|
370
434
|
|
|
371
|
-
## Issues
|
|
435
|
+
## Known Issues and Design Decisions
|
|
372
436
|
|
|
373
437
|
| Issue | Resolution |
|
|
374
438
|
|---|---|
|
|
375
439
|
| PyYAML converts `on` to Python `True` | Known behavior — no rules inspect the trigger block |
|
|
376
|
-
|
|
|
377
|
-
|
|
|
378
|
-
|
|
|
379
|
-
|
|
|
380
|
-
|
|
|
381
|
-
|
|
|
382
|
-
| `__line_` keys picked up by hardcoded secrets rule | Added `startswith("__line_")` filter |
|
|
383
|
-
| Line numbers returning 0 | Rewrote `check()` to access env dicts directly |
|
|
384
|
-
| GitLab curl command parsed as dict | Colon in `Authorization: Bearer` triggered YAML key parsing — wrapped in single quotes |
|
|
440
|
+
| `__line_` keys match credential patterns | All rules skip keys starting with `__line_` |
|
|
441
|
+
| Azure DevOps API requires auth for public projects | `AZURE_DEVOPS_TOKEN` is required even for public repos — Azure redirects unauthenticated requests to login |
|
|
442
|
+
| GitLab curl `Authorization: Bearer` parsed as dict | Colon in value triggers YAML key parsing — fixtures wrap in single quotes |
|
|
443
|
+
| hatch 1.14.1 breaks with virtualenv 21.x | `virtualenv<21` must be pinned alongside hatch — virtualenv 21 removed `propose_interpreters` |
|
|
444
|
+
| cosign sign-blob fails with glob + multiple files in v3.x | Removed cosign; PyPI OIDC trusted publishing generates Sigstore attestations natively |
|
|
445
|
+
| Node.js 20 action deprecation (June 16, 2026) | `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"` set in both workflow files |
|
|
@@ -4,9 +4,9 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "flowsec"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.3"
|
|
8
8
|
description = "CI/CD pipeline security analyzer — scans GitHub Actions, GitLab CI, and Azure DevOps configs for attack vectors"
|
|
9
|
-
readme = "
|
|
9
|
+
readme = "PYPI_README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
license = { text = "MIT" }
|
|
12
12
|
keywords = ["security", "devsecops", "cicd", "github-actions", "sast", "pipeline-security"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|