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.
- flowsec-0.5.2/.github/workflows/publish.yml +61 -0
- flowsec-0.5.2/.github/workflows/security.yml +112 -0
- flowsec-0.5.2/.gitignore +73 -0
- flowsec-0.5.2/AGENTS.md +361 -0
- flowsec-0.5.2/LICENSE +21 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/PKG-INFO +54 -35
- {flowsec-0.5.0 → flowsec-0.5.2}/README.md +52 -34
- flowsec-0.5.2/SECURITY.md +43 -0
- flowsec-0.5.2/docs/screenshots/flowsec-logo.png +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/pyproject.toml +1 -1
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/ai_narrative.py +1 -1
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/cli.py +54 -46
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/parser.py +2 -2
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/report.py +7 -5
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/scanner.py +66 -0
- flowsec-0.5.2/src/pipelineguard/scoring.py +24 -0
- flowsec-0.5.2/src/pipelineguard/templates/report.html +460 -0
- flowsec-0.5.0/.env.example +0 -6
- flowsec-0.5.0/.github/workflows/publish.yml +0 -32
- flowsec-0.5.0/.gitignore +0 -28
- flowsec-0.5.0/src/pipelineguard/scoring.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/.devcontainer/devcontainer.json +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/Makefile +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/docs/FULL_README.md +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/__init__.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/config.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/__init__.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/artifact_signing.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/base.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/broad_artifact_upload.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/container_runs_as_root.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/continue_on_error_security.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/dependency_pinning.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/deploy_all_branches.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/env_vars_in_logs.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/excessive_permissions.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/github_context_injection.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/hardcoded_secrets.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/insecure_curl.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/missing_env_protection.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/missing_oidc.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/missing_timeout.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/mutable_container_image.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/persist_credentials.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/privileged_docker.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/pull_request_target.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/secret_as_cli_arg.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/secrets_in_build_args.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/secrets_in_run.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/self_hosted_runner.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/unpinned_actions.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/unverified_install_script.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/workflow_dispatch_injection.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/src/pipelineguard/rules/workflow_run_trigger.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/__init__.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/azure_all_vulns.yml +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/github_all_vulns.yml +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/gitlab_all_vulns.yml +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/sample_azure_vulnerable.yml +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/sample_gitlab_vulnerable.yml +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/sample_workflow_clean.yml +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/fixtures/sample_workflow_vulnerable.yml +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/test_azure_rules.py +0 -0
- {flowsec-0.5.0 → flowsec-0.5.2}/tests/test_github_rules.py +0 -0
- {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
|
flowsec-0.5.2/.gitignore
ADDED
|
@@ -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
|
flowsec-0.5.2/AGENTS.md
ADDED
|
@@ -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.
|