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.
Files changed (62) hide show
  1. {flowsec-0.5.2 → flowsec-0.5.3}/PKG-INFO +74 -168
  2. flowsec-0.5.3/PYPI_README.md +153 -0
  3. {flowsec-0.5.2 → flowsec-0.5.3}/docs/FULL_README.md +142 -81
  4. {flowsec-0.5.2 → flowsec-0.5.3}/pyproject.toml +2 -2
  5. {flowsec-0.5.2 → flowsec-0.5.3}/.devcontainer/devcontainer.json +0 -0
  6. {flowsec-0.5.2 → flowsec-0.5.3}/.github/workflows/publish.yml +0 -0
  7. {flowsec-0.5.2 → flowsec-0.5.3}/.github/workflows/security.yml +0 -0
  8. {flowsec-0.5.2 → flowsec-0.5.3}/.gitignore +0 -0
  9. {flowsec-0.5.2 → flowsec-0.5.3}/AGENTS.md +0 -0
  10. {flowsec-0.5.2 → flowsec-0.5.3}/LICENSE +0 -0
  11. {flowsec-0.5.2 → flowsec-0.5.3}/Makefile +0 -0
  12. {flowsec-0.5.2 → flowsec-0.5.3}/README.md +0 -0
  13. {flowsec-0.5.2 → flowsec-0.5.3}/SECURITY.md +0 -0
  14. {flowsec-0.5.2 → flowsec-0.5.3}/docs/screenshots/flowsec-logo.png +0 -0
  15. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/__init__.py +0 -0
  16. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/ai_narrative.py +0 -0
  17. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/cli.py +0 -0
  18. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/config.py +0 -0
  19. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/parser.py +0 -0
  20. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/report.py +0 -0
  21. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/__init__.py +0 -0
  22. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/artifact_signing.py +0 -0
  23. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/base.py +0 -0
  24. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/broad_artifact_upload.py +0 -0
  25. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/container_runs_as_root.py +0 -0
  26. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/continue_on_error_security.py +0 -0
  27. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/dependency_pinning.py +0 -0
  28. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/deploy_all_branches.py +0 -0
  29. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/env_vars_in_logs.py +0 -0
  30. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/excessive_permissions.py +0 -0
  31. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/github_context_injection.py +0 -0
  32. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/hardcoded_secrets.py +0 -0
  33. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/insecure_curl.py +0 -0
  34. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/missing_env_protection.py +0 -0
  35. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/missing_oidc.py +0 -0
  36. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/missing_timeout.py +0 -0
  37. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/mutable_container_image.py +0 -0
  38. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/persist_credentials.py +0 -0
  39. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/privileged_docker.py +0 -0
  40. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/pull_request_target.py +0 -0
  41. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/secret_as_cli_arg.py +0 -0
  42. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/secrets_in_build_args.py +0 -0
  43. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/secrets_in_run.py +0 -0
  44. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/self_hosted_runner.py +0 -0
  45. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/unpinned_actions.py +0 -0
  46. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/unverified_install_script.py +0 -0
  47. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/workflow_dispatch_injection.py +0 -0
  48. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/rules/workflow_run_trigger.py +0 -0
  49. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/scanner.py +0 -0
  50. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/scoring.py +0 -0
  51. {flowsec-0.5.2 → flowsec-0.5.3}/src/pipelineguard/templates/report.html +0 -0
  52. {flowsec-0.5.2 → flowsec-0.5.3}/tests/__init__.py +0 -0
  53. {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/azure_all_vulns.yml +0 -0
  54. {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/github_all_vulns.yml +0 -0
  55. {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/gitlab_all_vulns.yml +0 -0
  56. {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/sample_azure_vulnerable.yml +0 -0
  57. {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/sample_gitlab_vulnerable.yml +0 -0
  58. {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/sample_workflow_clean.yml +0 -0
  59. {flowsec-0.5.2 → flowsec-0.5.3}/tests/fixtures/sample_workflow_vulnerable.yml +0 -0
  60. {flowsec-0.5.2 → flowsec-0.5.3}/tests/test_azure_rules.py +0 -0
  61. {flowsec-0.5.2 → flowsec-0.5.3}/tests/test_github_rules.py +0 -0
  62. {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.2
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
- <p align="center">
37
- <img src="docs/screenshots/flowsec-logo.png" alt="FlowSec" width="520" />
38
- </p>
36
+ # FlowSec
39
37
 
40
- <p align="center">
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
- > A Python 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 OWASP CICD Top 10 category.
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
- **[Full documentation](docs/FULL_README.md)**
42
+ ---
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install flowsec
48
+ ```
50
49
 
51
50
  ---
52
51
 
53
- ## What it catches
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
- ## Platforms supported
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
- [HIGH] FS003 - Excessive Permissions — Overprivileged Workflow Token
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
- Or create a `.flowsec.yml` in your repo root for persistent suppression:
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
- - name: Install FlowSec
231
- run: pip install flowsec
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
- ## Status
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
- ## Security
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
- See [SECURITY.md](SECURITY.md) for the responsible disclosure policy.
180
+ Report vulnerabilities via GitHub Security Advisories or email Scorpio.vansh@gmail.com.
275
181
 
276
182
  ---
277
183
 
278
- ## About this project
184
+ ## License
279
185
 
280
- Built as a security engineering portfolio project. The goal was to build an actual security tool — not run existing ones — and document every decision along the way.
186
+ MIT — see LICENSE on GitHub.
281
187
 
282
- **[Read the full documentation](docs/FULL_README.md)**
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 line security tool that scans CI/CD pipeline configurations for attack vectors. Point it at a GitHub repository, a GitLab CI file, or an Azure DevOps pipeline and it pulls the config, runs 26 security rules against it, and hands back a prioritized list of findings — each mapped to a MITRE ATT&CK technique and an OWASP CICD Top 10 category.
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
- flowsec scan --repo owner/repo
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, the pipeline config files are treated as an afterthought — written once, never reviewed, sitting in git forever.
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
- | PyYAML | Parses YAML pipeline configs into Python dictionaries |
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
- │ └── publish.yml — PyPI publish on tag push
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 — GitHub API connection and rule orchestration
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 caching
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
- │ └── fixtures/
88
- │ ├── sample_workflow_vulnerable.yml
89
- │ ├── sample_workflow_clean.yml
90
- │ ├── sample_gitlab_vulnerable.yml
91
- │ └── sample_azure_vulnerable.yml
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
- ├── Makefile
94
- ├── Dockerfile
95
- └── docker-compose.yml
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 and Azure DevOps, local files are read directly. All three paths produce raw YAML text.
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 — one rule file handles all three platforms. All findings from all rules across all files are collected into one flat list.
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 weighted risk score — CRITICAL×10, HIGH×5, MEDIUM×3, LOW×1.
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. The terminal gets a Rich colored table with severity badges, MITRE and OWASP columns, and a summary panel. An HTML report is generated with `--output`. AI attack narratives are generated with `--ai` via the Claude API and cached locally.
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 | Flag | Pipeline File |
124
- |---|---|---|
125
- | GitHub Actions | `--repo owner/repo` or `--file workflow.yml` | `.github/workflows/*.yml` |
126
- | GitLab CI | `--gitlab .gitlab-ci.yml` | `.gitlab-ci.yml` |
127
- | Azure DevOps | `--azure azure-pipelines.yml` | `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 — FS001, FS006, FS007, FS009, FS010, FS014, FS017, FS018, FS019, FS020, FS021, FS022, FS023, FS024, FS025, FS026 — adapt their field lookups based on the platform parameter.
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 — rule ID, title, severity, description, remediation, MITRE technique, OWASP category, file path, line number, and AI narrative.
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
- **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.
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
- **FS023** flags `curl -k` and `curl --insecure` in run commands. Disabling SSL verification allows a man-in-the-middle attacker to intercept the connection and serve malicious content — scripts, binaries, or dependencies — to your pipeline.
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
- **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 — PR authors, issue reporters — and if interpolated unquoted into shell, they become arbitrary command injection. This is one of the most common critical findings in real-world GitHub Actions audits.
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
- **FS014** flags container images referenced in pipeline configs without a digest pin. Tags like `:latest` or `:v1` are mutable — the image they point to can be replaced after your pipeline is written. Only a `@sha256:` digest guarantees the exact image bytes your pipeline will run. GitHub checks `container:`, `services:`, and `uses: docker://` in job definitions. GitLab and Azure check `image:` at both the global and per-job level.
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
- **FS015** flags `actions/checkout` steps that do not explicitly set `persist-credentials: false`. When credentials are persisted, the GitHub token is written to the local git config for the duration of the job — any step that runs afterward, including third-party actions, can read it and make authenticated API calls.
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
- **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 it is triggered by a workflow from a fork. The triggering workflow is defined in the fork — an attacker can craft it to exfiltrate secrets or trigger privileged operations.
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
- **FS017** flags security scanning steps — Trivy, Snyk, Bandit, Semgrep, Checkov, Grype, Gitleaks, and others — that have `continue-on-error: true` (GitHub), `allow_failure: true` (GitLab), or `continueOnError: true` (Azure) set. Silencing scanner failures means a finding that would block a deploy is silently swallowed. The pipeline succeeds and the vulnerability ships.
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
- **FS018** flags shell commands where a secret or credential is passed as a named CLI flag — `--token`, `--password`, `--api-key`, `--secret` — with a value from the secrets context or an environment variable. CLI arguments are visible to any process that can read `/proc/<pid>/cmdline` on Linux, appear in shell history, and are logged by process monitoring tools.
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
- **FS019** flags `curl ... | bash` and `wget ... | bash` patterns — fetching a remote script and piping it directly to a shell interpreter without any checksum verification. This is a common way to install tools in CI/CD pipelines and a straightforward supply chain attack vector. If the remote server or CDN is compromised, every pipeline using this pattern executes attacker-controlled code.
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
- **FS022** flags artifact upload steps that use overly broad path patterns — `.`, `*`, `**` — that would upload the entire workspace as a pipeline artifact. Workspaces can contain checked-out source code, `.env` files, build outputs with embedded secrets, and any files written by previous steps including malicious ones.
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` in pipeline commands. A privileged container has full access to the host kernel and devices — it can read host memory, mount host filesystems, and escape the container boundary entirely. Running privileged containers in a CI/CD pipeline gives any code that executes inside them root-equivalent access to the runner host.
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 commands like `env`, `printenv`, and `echo $VARIABLE` in pipeline steps. These dump environment variables to pipeline logs which are visible to all repo contributors and sometimes publicly accessible on open source repos.
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. On GitHub, this means a broad push trigger or pull_request trigger with no `if: github.ref == 'refs/heads/main'` condition on the deploy job. On GitLab, a deploy job with no `only:` or `rules:` restriction. On Azure, a deploy-named step with no `condition:` referencing `SourceBranch`. Without branch guards, any push to any branch — including a feature branch from an external contributor — can trigger a production deployment.
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 VanshBhardwaj1945/cloud-resume-challenge-azure
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
- # GitLab CI
243
- flowsec scan --gitlab .gitlab-ci.yml
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
- # Ignore Findings
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
- FlowSec supports suppressing specific rules in two ways.
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 — FlowSec merges them automatically. The `reason` field is optional but recommended for audit trails. The config file approach is better for teams since it lives in the repo and is reviewable in git history.
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 thresholds: `critical`, `high`, `medium`, `low`.
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 an MD5 hash of the finding's rule ID and description. The same finding is never sent to the API twice — subsequent scans return cached narratives instantly at zero cost.
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 overall risk score — clickable to filter
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 Resolved
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
- | pip install blocked by macOS system Python | Created venv with `python3 -m venv .venv` |
377
- | venv created at wrong path | Deleted and recreated at `.venv` |
378
- | Click Arena has no `.github/workflows/` folder | Uses Jenkins — tested against `cloud-resume-challenge-azure` |
379
- | 404 on first scanner test | Wrong repo — switched to one using GitHub Actions |
380
- | Python 3.14 editable install not creating `.pth` file | Hatchling compatibility issue — fixed with `PYTHONPATH` in `.zshrc` |
381
- | GitHub push protection blocked commit | Real token in `.env.example` — revoked and replaced with placeholder |
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.2"
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 = "README.md"
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