flowsec 0.5.0__tar.gz → 0.5.2__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 (65) hide show
  1. flowsec-0.5.2/.github/workflows/publish.yml +61 -0
  2. flowsec-0.5.2/.github/workflows/security.yml +112 -0
  3. flowsec-0.5.2/.gitignore +73 -0
  4. flowsec-0.5.2/AGENTS.md +361 -0
  5. flowsec-0.5.2/LICENSE +21 -0
  6. {flowsec-0.5.0 → flowsec-0.5.2}/PKG-INFO +54 -35
  7. {flowsec-0.5.0 → flowsec-0.5.2}/README.md +52 -34
  8. flowsec-0.5.2/SECURITY.md +43 -0
  9. flowsec-0.5.2/docs/screenshots/flowsec-logo.png +0 -0
  10. {flowsec-0.5.0 → flowsec-0.5.2}/pyproject.toml +1 -1
  11. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/ai_narrative.py +1 -1
  12. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/cli.py +54 -46
  13. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/parser.py +2 -2
  14. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/report.py +7 -5
  15. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/scanner.py +66 -0
  16. flowsec-0.5.2/src/pipelineguard/scoring.py +24 -0
  17. flowsec-0.5.2/src/pipelineguard/templates/report.html +460 -0
  18. flowsec-0.5.0/.env.example +0 -6
  19. flowsec-0.5.0/.github/workflows/publish.yml +0 -32
  20. flowsec-0.5.0/.gitignore +0 -28
  21. flowsec-0.5.0/src/pipelineguard/scoring.py +0 -0
  22. {flowsec-0.5.0 → flowsec-0.5.2}/.devcontainer/devcontainer.json +0 -0
  23. {flowsec-0.5.0 → flowsec-0.5.2}/Makefile +0 -0
  24. {flowsec-0.5.0 → flowsec-0.5.2}/docs/FULL_README.md +0 -0
  25. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/__init__.py +0 -0
  26. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/config.py +0 -0
  27. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/__init__.py +0 -0
  28. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/artifact_signing.py +0 -0
  29. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/base.py +0 -0
  30. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/broad_artifact_upload.py +0 -0
  31. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/container_runs_as_root.py +0 -0
  32. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/continue_on_error_security.py +0 -0
  33. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/dependency_pinning.py +0 -0
  34. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/deploy_all_branches.py +0 -0
  35. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/env_vars_in_logs.py +0 -0
  36. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/excessive_permissions.py +0 -0
  37. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/github_context_injection.py +0 -0
  38. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/hardcoded_secrets.py +0 -0
  39. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/insecure_curl.py +0 -0
  40. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/missing_env_protection.py +0 -0
  41. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/missing_oidc.py +0 -0
  42. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/missing_timeout.py +0 -0
  43. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/mutable_container_image.py +0 -0
  44. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/persist_credentials.py +0 -0
  45. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/privileged_docker.py +0 -0
  46. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/pull_request_target.py +0 -0
  47. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/secret_as_cli_arg.py +0 -0
  48. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/secrets_in_build_args.py +0 -0
  49. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/secrets_in_run.py +0 -0
  50. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/self_hosted_runner.py +0 -0
  51. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/unpinned_actions.py +0 -0
  52. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/unverified_install_script.py +0 -0
  53. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/workflow_dispatch_injection.py +0 -0
  54. {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/workflow_run_trigger.py +0 -0
  55. {flowsec-0.5.0 → flowsec-0.5.2}/tests/__init__.py +0 -0
  56. {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/azure_all_vulns.yml +0 -0
  57. {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/github_all_vulns.yml +0 -0
  58. {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/gitlab_all_vulns.yml +0 -0
  59. {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/sample_azure_vulnerable.yml +0 -0
  60. {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/sample_gitlab_vulnerable.yml +0 -0
  61. {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/sample_workflow_clean.yml +0 -0
  62. {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/sample_workflow_vulnerable.yml +0 -0
  63. {flowsec-0.5.0 → flowsec-0.5.2}/tests/test_azure_rules.py +0 -0
  64. {flowsec-0.5.0 → flowsec-0.5.2}/tests/test_github_rules.py +0 -0
  65. {flowsec-0.5.0 → flowsec-0.5.2}/tests/test_gitlab_rules.py +0 -0
@@ -0,0 +1,61 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ env:
9
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ security:
16
+ name: Security Gate
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 10
19
+ permissions:
20
+ contents: read
21
+
22
+ steps:
23
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
24
+ with:
25
+ fetch-depth: 0
26
+ persist-credentials: false
27
+
28
+ - name: Run Gitleaks
29
+ uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
30
+ env:
31
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
+
33
+ publish:
34
+ name: Build and Publish
35
+ needs: security
36
+ if: startsWith(github.ref, 'refs/tags/v')
37
+ runs-on: ubuntu-latest
38
+ timeout-minutes: 15
39
+ environment: pypi
40
+ permissions:
41
+ id-token: write
42
+ contents: read
43
+
44
+ steps:
45
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
46
+ with:
47
+ persist-credentials: false
48
+
49
+ - name: Set up Python
50
+ uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
51
+ with:
52
+ python-version: "3.11"
53
+
54
+ - name: Install hatch
55
+ run: pip install hatch==1.14.1 "virtualenv<21"
56
+
57
+ - name: Build package
58
+ run: hatch build
59
+
60
+ - name: Publish to PyPI
61
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
@@ -0,0 +1,112 @@
1
+ name: Security Scan
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["main"]
8
+
9
+ env:
10
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ gitleaks:
17
+ name: Detect Secrets (Gitleaks)
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 10
20
+ permissions:
21
+ contents: read
22
+ security-events: write
23
+
24
+ steps:
25
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
26
+ with:
27
+ fetch-depth: 0
28
+ persist-credentials: false
29
+
30
+ - name: Run Gitleaks
31
+ uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
32
+ env:
33
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34
+
35
+ bandit:
36
+ name: Python SAST (Bandit)
37
+ runs-on: ubuntu-latest
38
+ timeout-minutes: 10
39
+ permissions:
40
+ contents: read
41
+
42
+ steps:
43
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
44
+ with:
45
+ persist-credentials: false
46
+
47
+ - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
48
+ with:
49
+ python-version: "3.11"
50
+
51
+ - name: Install bandit
52
+ run: pip install bandit==1.8.3
53
+
54
+ - name: Run Bandit
55
+ run: bandit -r src/ -ll -x src/pipelineguard/templates/
56
+
57
+ pip-audit:
58
+ name: Dependency Vulnerabilities (pip-audit)
59
+ runs-on: ubuntu-latest
60
+ timeout-minutes: 10
61
+ permissions:
62
+ contents: read
63
+
64
+ steps:
65
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
66
+ with:
67
+ persist-credentials: false
68
+
69
+ - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
70
+ with:
71
+ python-version: "3.11"
72
+
73
+ - name: Install project and pip-audit
74
+ run: pip install pip-audit==2.9.0 -e .
75
+
76
+ - name: Audit project dependencies
77
+ run: |
78
+ pip-audit --desc \
79
+ --ignore-vuln PYSEC-2026-196 \
80
+ --ignore-vuln GHSA-58qw-9mgm-455v \
81
+ --ignore-vuln GHSA-jp4c-xjxw-mgf9 \
82
+ --ignore-vuln GHSA-4gg8-gxpx-9rph
83
+
84
+ flowsec:
85
+ name: FlowSec Self-Scan
86
+ runs-on: ubuntu-latest
87
+ timeout-minutes: 10
88
+ permissions:
89
+ contents: read
90
+
91
+ steps:
92
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
93
+ with:
94
+ persist-credentials: false
95
+
96
+ - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
97
+ with:
98
+ python-version: "3.11"
99
+
100
+ - name: Install FlowSec from source
101
+ run: pip install -e .
102
+
103
+ - name: Scan publish workflow
104
+ run: |
105
+ flowsec scan --github --file .github/workflows/publish.yml \
106
+ --ignore FS008 --ignore FS009 --ignore FS012 --ignore FS026 \
107
+ --fail-on high
108
+
109
+ - name: Scan security workflow
110
+ run: |
111
+ flowsec scan --github --file .github/workflows/security.yml \
112
+ --fail-on high
@@ -0,0 +1,73 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .venv/
9
+ venv/
10
+ env/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .pytest_cache/
14
+ .tox/
15
+ .hatch/
16
+ htmlcov/
17
+ .coverage
18
+ .coverage.*
19
+ coverage.xml
20
+ coverage.json
21
+ pip-selfcheck.json
22
+
23
+ # Secrets — never commit these
24
+ .env
25
+ .env.*
26
+ *.env
27
+ *.pem
28
+ *.key
29
+ *.p12
30
+ *.pfx
31
+ *.cer
32
+ *.crt
33
+ cosign.key
34
+ *.token
35
+ *.secret
36
+ .netrc
37
+ secrets.yml
38
+ secrets.yaml
39
+ id_rsa
40
+ id_dsa
41
+ id_ed25519
42
+ id_ecdsa
43
+
44
+ # Sigstore / cosign artifacts
45
+ *.bundle
46
+ *.sig
47
+ *.sigstore
48
+
49
+ # Claude Code local config
50
+ .claude/
51
+
52
+ # Editor and IDE
53
+ .vscode/
54
+ .idea/
55
+ *.swp
56
+ *.swo
57
+ *~
58
+ *.bak
59
+ *.tmp
60
+
61
+ # Logs
62
+ *.log
63
+ logs/
64
+
65
+ # Reports (generated output — not source)
66
+ *.html
67
+ !src/pipelineguard/templates/*.html
68
+
69
+ # OS
70
+ .DS_Store
71
+ ehthumbs.db
72
+ Thumbs.db
73
+ Desktop.ini
@@ -0,0 +1,361 @@
1
+ # FlowSec — Agent Guide
2
+
3
+ This document is the authoritative reference for any AI agent working on this codebase. Read it fully before touching anything.
4
+
5
+ ---
6
+
7
+ ## What This Project Is
8
+
9
+ FlowSec is a static analysis security tool that scans CI/CD pipeline YAML files for attack vectors. It supports three platforms: GitHub Actions, GitLab CI, and Azure DevOps. Every finding maps to a MITRE ATT&CK technique and an OWASP CICD Top 10 category.
10
+
11
+ It is a real, published Python package on PyPI (`pip install flowsec`). Changes to core logic affect real users.
12
+
13
+ ---
14
+
15
+ ## Project Structure
16
+
17
+ ```
18
+ FlowSec/
19
+ ├── src/pipelineguard/
20
+ │ ├── cli.py # Entry point — argument parsing, display logic
21
+ │ ├── scanner.py # All scan_* functions and remote fetchers
22
+ │ ├── parser.py # YAML loader with line number tracking
23
+ │ ├── config.py # Loads .flowsec.yml ignore rules
24
+ │ ├── report.py # HTML report generation (Jinja2)
25
+ │ ├── ai_narrative.py # Claude API integration for attack narratives
26
+ │ ├── scoring.py # Risk score calculation (0-100, diminishing returns)
27
+ │ └── rules/
28
+ │ ├── base.py # BaseRule, Finding, Severity — the contract every rule implements
29
+ │ ├── hardcoded_secrets.py # FS001
30
+ │ ├── unpinned_actions.py # FS002
31
+ │ ├── excessive_permissions.py # FS003
32
+ │ ├── missing_oidc.py # FS004
33
+ │ ├── pull_request_target.py # FS005
34
+ │ ├── missing_timeout.py # FS006
35
+ │ ├── self_hosted_runner.py # FS007
36
+ │ ├── artifact_signing.py # FS008
37
+ │ ├── dependency_pinning.py # FS009
38
+ │ ├── secrets_in_run.py # FS010
39
+ │ ├── github_context_injection.py # FS011
40
+ │ ├── missing_env_protection.py # FS012
41
+ │ ├── workflow_dispatch_injection.py # FS013
42
+ │ ├── mutable_container_image.py # FS014
43
+ │ ├── persist_credentials.py # FS015
44
+ │ ├── workflow_run_trigger.py # FS016
45
+ │ ├── continue_on_error_security.py # FS017
46
+ │ ├── secret_as_cli_arg.py # FS018
47
+ │ ├── unverified_install_script.py # FS019
48
+ │ ├── container_runs_as_root.py # FS020
49
+ │ ├── secrets_in_build_args.py # FS021
50
+ │ ├── broad_artifact_upload.py # FS022
51
+ │ ├── insecure_curl.py # FS023
52
+ │ ├── privileged_docker.py # FS024
53
+ │ ├── env_vars_in_logs.py # FS025
54
+ │ └── deploy_all_branches.py # FS026
55
+ ├── tests/
56
+ │ ├── fixtures/
57
+ │ │ ├── github_all_vulns.yml # GitHub Actions fixture — every FS rule should fire
58
+ │ │ ├── gitlab_all_vulns.yml # GitLab CI fixture — every applicable rule should fire
59
+ │ │ └── azure_all_vulns.yml # Azure DevOps fixture — every applicable rule should fire
60
+ │ ├── test_github_rules.py
61
+ │ ├── test_gitlab_rules.py
62
+ │ └── test_azure_rules.py
63
+ ├── .github/workflows/
64
+ │ ├── publish.yml # Publishes to PyPI on v* tags via trusted publishing (OIDC)
65
+ │ └── security.yml # Security scans on push/PR: gitleaks, bandit, pip-audit, flowsec
66
+ ├── pyproject.toml # Version, dependencies, hatch build config
67
+ ├── SECURITY.md # Responsible disclosure policy
68
+ ├── AGENTS.md # This file
69
+ └── README.md # PyPI-facing documentation
70
+ ```
71
+
72
+ ---
73
+
74
+ ## How to Run It
75
+
76
+ The project uses a virtual environment at `.venv/`. Always use `.venv/bin/flowsec`, not a system `flowsec`.
77
+
78
+ **CLI structure:** `--github/--gitlab/--azure` (platform, required) + `--file/--repo` (target, one required).
79
+
80
+ ```bash
81
+ # GitHub Actions — local file
82
+ .venv/bin/flowsec scan --github --file .github/workflows/ci.yml
83
+
84
+ # GitHub Actions — remote repo (requires GITHUB_TOKEN in .env)
85
+ .venv/bin/flowsec scan --github --repo owner/repo
86
+
87
+ # GitLab CI — local file
88
+ .venv/bin/flowsec scan --gitlab --file .gitlab-ci.yml
89
+
90
+ # GitLab CI — remote repo (requires GITLAB_TOKEN in .env)
91
+ .venv/bin/flowsec scan --gitlab --repo namespace/project
92
+
93
+ # Azure DevOps — local file
94
+ .venv/bin/flowsec scan --azure --file azure-pipelines.yml
95
+
96
+ # Azure DevOps — remote repo (requires AZURE_DEVOPS_TOKEN in .env)
97
+ .venv/bin/flowsec scan --azure --repo org/project
98
+
99
+ # With HTML report
100
+ .venv/bin/flowsec scan --github --file workflow.yml --output report.html
101
+
102
+ # With AI narratives (requires ANTHROPIC_API_KEY in .env)
103
+ .venv/bin/flowsec scan --github --file workflow.yml --ai
104
+
105
+ # Fail on severity threshold (for use in CI)
106
+ .venv/bin/flowsec scan --github --file workflow.yml --fail-on critical
107
+
108
+ # Suppress specific rules
109
+ .venv/bin/flowsec scan --github --file workflow.yml --ignore FS006 --ignore FS003
110
+ ```
111
+
112
+ To validate that everything is working, run against the three fixture files:
113
+
114
+ ```bash
115
+ .venv/bin/flowsec scan --github --file tests/fixtures/github_all_vulns.yml
116
+ .venv/bin/flowsec scan --gitlab --file tests/fixtures/gitlab_all_vulns.yml
117
+ .venv/bin/flowsec scan --azure --file tests/fixtures/azure_all_vulns.yml
118
+ ```
119
+
120
+ Expected output: GitHub ~36 findings, GitLab ~29 findings, Azure ~24 findings.
121
+
122
+ ---
123
+
124
+ ## How the Rule Engine Works
125
+
126
+ ### The platform flag is everything
127
+
128
+ Every rule's `check()` method receives a `platform` argument (`"github"`, `"gitlab"`, or `"azure"`). Rules branch on this to parse the correct YAML structure. This is the most critical thing to understand.
129
+
130
+ ```
131
+ --github --file → scan_file() → platform="github"
132
+ --github --repo → scan_repo() → platform="github" (fetches via GitHub API)
133
+ --gitlab --file → scan_gitlab_file() → platform="gitlab"
134
+ --gitlab --repo → scan_gitlab_repo() → platform="gitlab" (fetches via python-gitlab)
135
+ --azure --file → scan_azure_file() → platform="azure"
136
+ --azure --repo → scan_azure_repo() → platform="azure" (fetches via Azure DevOps REST API)
137
+ ```
138
+
139
+ **Never use `--github --file` on a GitLab or Azure YAML.** It will pass `platform="github"` and produce wrong results — most rules will find nothing, and FS003 will fire as a false positive (Azure/GitLab files have no `permissions:` key).
140
+
141
+ ### YAML structure per platform
142
+
143
+ GitHub Actions:
144
+ ```yaml
145
+ jobs:
146
+ build:
147
+ steps:
148
+ - run: pip install ...
149
+ env:
150
+ SECRET: hardcoded
151
+ ```
152
+
153
+ GitLab CI — top-level job keys with `script:` arrays:
154
+ ```yaml
155
+ build:
156
+ script:
157
+ - pip install ...
158
+ variables:
159
+ SECRET: hardcoded
160
+ ```
161
+
162
+ Azure DevOps — `jobs:` list for job-level checks, top-level keys with `script:` for script checks:
163
+ ```yaml
164
+ jobs:
165
+ - job: build
166
+ steps:
167
+ - script: echo hi
168
+
169
+ build-scripts:
170
+ script:
171
+ - pip install ...
172
+ ```
173
+
174
+ Rules that only apply to one platform return `[]` immediately:
175
+ ```python
176
+ if platform != "github":
177
+ return []
178
+ ```
179
+
180
+ Rules that support multiple platforms branch with `if/elif`:
181
+ ```python
182
+ if platform == "github":
183
+ # parse jobs[].steps[].run
184
+ elif platform in ("gitlab", "azure"):
185
+ # iterate top-level keys, look for script: arrays
186
+ ```
187
+
188
+ ### The parser
189
+
190
+ `parse_pipeline_with_lines(content)` returns a standard Python dict. It uses a custom `LineLoader` that injects `__line_{key}__` entries alongside every string value so rules can report accurate line numbers. When iterating dict keys in rules, always skip keys that start with `__line_`.
191
+
192
+ ### The Finding dataclass
193
+
194
+ ```python
195
+ @dataclass
196
+ class Finding:
197
+ rule_id: str # e.g. "FS001"
198
+ title: str
199
+ severity: Severity # Severity.CRITICAL / HIGH / MEDIUM / LOW
200
+ description: str # specific to the finding instance
201
+ remediation: str
202
+ mitre_technique: str # e.g. "T1552.001"
203
+ file_path: str
204
+ line_number: int = 0
205
+ narrative: str = "" # populated only with --ai flag
206
+ owasp_category: str = "" # e.g. "CICD-SEC-6"
207
+ ```
208
+
209
+ ### Adding a new rule
210
+
211
+ 1. Create `src/pipelineguard/rules/fsXXX_name.py` following the pattern of any existing rule.
212
+ 2. Implement `BaseRule` with `rule_id`, `title`, `severity`, and a `check()` method.
213
+ 3. Import and instantiate it in `scanner.py` — add it to the `RULES` list.
214
+ 4. Add the vulnerability pattern to each relevant fixture file (`github_all_vulns.yml`, `gitlab_all_vulns.yml`, `azure_all_vulns.yml`).
215
+ 5. Add a test assertion in each relevant `test_*_rules.py`.
216
+ 6. Add it to the rule table in `README.md`.
217
+
218
+ Rule IDs are sequential. The next available ID is FS027.
219
+
220
+ ---
221
+
222
+ ## Testing
223
+
224
+ Tests use pytest. Run from the project root with the venv active:
225
+
226
+ ```bash
227
+ .venv/bin/python -m pytest tests/ -v
228
+ ```
229
+
230
+ Each test file has:
231
+ - Individual tests per rule asserting the rule ID appears in the scan results
232
+ - A `test_all_*_rules_detected` test that asserts the full expected set fires
233
+
234
+ The fixtures are intentionally full of every vulnerability pattern. Do not "fix" them — they are meant to be vulnerable.
235
+
236
+ ---
237
+
238
+ ## Git Workflow — Branch Protection Is Active
239
+
240
+ The `main` branch has branch protection enforced. Force pushes and direct deletions are blocked for all contributors (owner bypasses this, but should still follow the workflow).
241
+
242
+ **Required status checks before merging to main:**
243
+ - Detect Secrets (Gitleaks)
244
+ - Python SAST (Bandit)
245
+ - Dependency Vulnerabilities (pip-audit)
246
+ - FlowSec Self-Scan
247
+
248
+ ### How to push changes as the repo owner
249
+
250
+ The owner can push directly to main (the "Bypassed rule violations" message in Actions is expected):
251
+
252
+ ```bash
253
+ git add <files>
254
+ git commit -m "your message"
255
+ git push origin main
256
+ ```
257
+
258
+ ### How contributors must work (open-source PRs)
259
+
260
+ Contributors cannot push to main directly. They must:
261
+
262
+ ```bash
263
+ # 1. Fork the repo, then clone their fork
264
+ git clone https://github.com/THEIR_USERNAME/FlowSec.git
265
+ cd FlowSec
266
+
267
+ # 2. Create a feature branch
268
+ git checkout -b feature/their-change
269
+
270
+ # 3. Make changes, commit
271
+ git add <files>
272
+ git commit -m "description of change"
273
+
274
+ # 4. Push to their fork
275
+ git push origin feature/their-change
276
+
277
+ # 5. Open a PR on GitHub — all 4 status checks must pass before merge
278
+ ```
279
+
280
+ ### For agents making changes
281
+
282
+ Create a branch, commit, push, and note the PR URL for the user to review and merge:
283
+
284
+ ```bash
285
+ git checkout -b fix/your-change-name
286
+ git add <files>
287
+ git commit -m "fix: description"
288
+ git push origin fix/your-change-name
289
+ # Then open a PR via the GitHub UI or gh CLI
290
+ ```
291
+
292
+ **Never use `git push --force` on main.** It will be blocked for contributors and should never be done by the owner either — it risks corrupting the tag/commit relationship the publish workflow depends on.
293
+
294
+ ---
295
+
296
+ ## Safeguards — Read Before Making Changes
297
+
298
+ ### Never mix platform flags with the wrong YAML format
299
+ `--github` is for GitHub Actions YAML. `--gitlab` is for GitLab CI YAML. `--azure` is for Azure DevOps YAML. Running the wrong platform flag against the wrong file produces misleading results silently — it won't error, it'll just miss everything or fire false positives.
300
+
301
+ ### Never edit the fixture files to remove vulnerabilities
302
+ `tests/fixtures/github_all_vulns.yml`, `gitlab_all_vulns.yml`, and `azure_all_vulns.yml` are test inputs. They are supposed to contain every vulnerability pattern. Cleaning them up will cause tests to fail.
303
+
304
+ ### Never remove a rule from the RULES list in scanner.py without also removing its import and test
305
+ The list in `scanner.py` is the single source of truth for which rules run. If a rule is not in this list, it will never execute regardless of whether the file exists.
306
+
307
+ ### Never bump the version without tagging
308
+ The publish workflow in `.github/workflows/publish.yml` triggers only on `v*` tags. A version bump in `pyproject.toml` alone does nothing — you must also create and push a git tag matching the version (e.g. `git tag v0.5.2 && git push origin v0.5.2`).
309
+
310
+ ### Never create a tag for a version already on PyPI
311
+ PyPI rejects duplicate versions. Check existing tags with `git tag -l` before creating one. If a tag already exists and you need to retag a different commit, delete and recreate it — confirm with the user first.
312
+
313
+ ### Never force-push tags either
314
+ If you need to move a tag, delete it locally and remotely first, then recreate:
315
+ ```bash
316
+ git tag -d vX.Y.Z
317
+ git push origin :refs/tags/vX.Y.Z
318
+ git tag vX.Y.Z
319
+ git push origin vX.Y.Z
320
+ ```
321
+ This is acceptable only when a version has NOT yet been successfully published to PyPI.
322
+
323
+ ### The .env file is not committed
324
+ `GITHUB_TOKEN`, `GITLAB_TOKEN`, `AZURE_DEVOPS_TOKEN`, and `ANTHROPIC_API_KEY` live in `.env` (loaded via `python-dotenv`). Never commit this file. Remote scan flags and `--ai` will fail without the relevant token.
325
+
326
+ ### FS003 is GitHub-only by design
327
+ FS003 (Excessive Permissions) fires when a GitHub workflow has `permissions: write-all` or no `permissions` key at all (which defaults to broad access). It returns `[]` for GitLab and Azure. If you see FS003 appearing on a GitLab or Azure scan, the wrong platform flag was used.
328
+
329
+ ---
330
+
331
+ ## Publishing to PyPI
332
+
333
+ The project uses PyPI trusted publishing (OIDC) — no API token needed. The workflow in `.github/workflows/publish.yml` handles everything automatically when a `v*` tag is pushed. PyPI generates Sigstore attestations automatically for OIDC-published packages.
334
+
335
+ Steps to release a new version:
336
+ 1. Update `version = "X.Y.Z"` in `pyproject.toml`
337
+ 2. Commit and push to main
338
+ 3. Tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z`
339
+
340
+ The GitHub Actions workflow will build with `hatch build` and publish automatically. Monitor it in the Actions tab.
341
+
342
+ The README.md is used as the PyPI long description. Hatchling detects `text/markdown` from the `.md` extension automatically — no extra config needed.
343
+
344
+ ### Known dependency pinning requirements in publish.yml
345
+ `hatch==1.14.1` must be installed alongside `"virtualenv<21"`. virtualenv 21.x broke the `propose_interpreters` API that hatch 1.14.1 depends on. Always pin both together until hatch is upgraded.
346
+
347
+ ---
348
+
349
+ ## Key Files to Know
350
+
351
+ | File | Why it matters |
352
+ |---|---|
353
+ | `src/pipelineguard/scanner.py` | The `RULES` list controls which rules run. Every new rule must be added here. |
354
+ | `src/pipelineguard/rules/base.py` | The `BaseRule`, `Finding`, and `Severity` contracts. All rules inherit from here. |
355
+ | `src/pipelineguard/cli.py` | Maps CLI flags to scanner functions. The `platform` value is set here. |
356
+ | `src/pipelineguard/parser.py` | The YAML loader. Produces the `config` dict every rule receives. |
357
+ | `src/pipelineguard/scoring.py` | Risk score: severity-weighted with diminishing returns, normalized to 0-100. |
358
+ | `pyproject.toml` | Version number and build config. |
359
+ | `tests/fixtures/` | The ground truth for what each platform's YAML looks like and what should be detected. |
360
+ | `.github/workflows/publish.yml` | PyPI release pipeline. Triggered by `v*` tags. |
361
+ | `.github/workflows/security.yml` | Security checks on every push/PR to main. |
flowsec-0.5.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vansh Bhardwaj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.