-
Notifications
You must be signed in to change notification settings - Fork 0
231 lines (215 loc) · 9.78 KB
/
Copy pathsecurity.yml
File metadata and controls
231 lines (215 loc) · 9.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
name: Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "0 6 * * 1"
permissions:
contents: read
concurrency:
group: security-${{ github.ref }}
cancel-in-progress: true
jobs:
bandit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.11"
- name: Install Bandit
run: python -m pip install --upgrade pip bandit
- name: Run Bandit
run: |
bandit -r src sdk --ini .bandit --severity-level medium -f json -o /tmp/bandit-current.json || true
python scripts/bandit_diff.py .bandit-baseline.json /tmp/bandit-current.json
safety:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.11"
- name: Resolve Safety dependency inputs
run: |
python - <<'PY'
import os
import subprocess
import sys
import tempfile
from pathlib import Path
import tomllib
root = Path(".")
output_dir = root / ".tmp-security"
output_dir.mkdir(exist_ok=True)
def load_project_dependencies(pyproject_path: Path) -> list[str]:
if not pyproject_path.exists():
return []
data = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
return list(data.get("project", {}).get("dependencies", []))
def load_requirements(requirements_path: Path) -> list[str]:
if not requirements_path.exists():
return []
requirements: list[str] = []
for line in requirements_path.read_text(encoding="utf-8").splitlines():
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
requirements.append(stripped)
return requirements
def write_requirements(target: Path, entries: list[str]) -> None:
deduped = list(dict.fromkeys(entries))
target.write_text("\n".join(deduped) + "\n", encoding="utf-8")
def resolve_requirements(name: str, entries: list[str], target: Path) -> int:
temp_input = output_dir / f"{name}.in"
write_requirements(temp_input, entries)
with tempfile.TemporaryDirectory(prefix=f"safety-{name}-", dir=output_dir) as temp_dir:
venv_dir = Path(temp_dir) / "venv"
subprocess.run([sys.executable, "-m", "venv", str(venv_dir)], check=True)
scripts_dir = venv_dir / ("Scripts" if os.name == "nt" else "bin")
python = scripts_dir / ("python.exe" if os.name == "nt" else "python")
subprocess.run([str(python), "-m", "pip", "install", "--upgrade", "pip"], check=True)
subprocess.run([str(python), "-m", "pip", "install", "-r", str(temp_input)], check=True)
freeze = subprocess.run(
[str(python), "-m", "pip", "freeze"],
check=True,
capture_output=True,
text=True,
)
resolved = [line for line in freeze.stdout.splitlines() if line.strip()]
if any("==" not in line for line in resolved):
raise SystemExit(f"{name} requirements were not fully resolved")
target.write_text("\n".join(resolved) + "\n", encoding="utf-8")
print(f"Resolved {len(resolved)} packages for {name} -> {target}")
return len(resolved)
main_count = resolve_requirements(
"main",
load_project_dependencies(root / "pyproject.toml")
+ load_requirements(root / "requirements.txt"),
output_dir / "requirements-main.txt",
)
sdk_count = resolve_requirements(
"sdk",
load_project_dependencies(root / "sdk" / "pyproject.toml"),
output_dir / "requirements-sdk.txt",
)
# Drop intra-monorepo deps (agentflow-client / agentflow-runtime) — they
# are not on PyPI yet during the v1.1.0 publish run and are scanned via
# the "main" / "sdk" buckets above anyway.
integrations_deps = [
dep for dep in load_project_dependencies(root / "integrations" / "pyproject.toml")
if not dep.lower().startswith(("agentflow-client", "agentflow-runtime"))
]
integrations_count = resolve_requirements(
"integrations",
integrations_deps,
output_dir / "requirements-integrations.txt",
)
summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
if summary_path:
summary = Path(summary_path)
with summary.open("a", encoding="utf-8") as handle:
handle.write("## Safety dependency scope\n")
handle.write(
f"- Main app runtime: resolved install of `pyproject.toml` `[project.dependencies]` + `requirements.txt` ({main_count} packages)\n"
)
handle.write(
f"- SDK runtime: resolved install of `sdk/pyproject.toml` `[project.dependencies]` ({sdk_count} packages)\n"
)
handle.write(
f"- Integrations runtime: resolved install of `integrations/pyproject.toml` `[project.dependencies]` ({integrations_count} packages)\n"
)
handle.write("- Exclusions: dev/CI/test extras, local tooling, and unrelated Docker image packages\n")
PY
- name: Install Safety
run: python -m pip install --upgrade pip "safety<3"
- name: Verify Safety fails on a known vulnerable pin
run: |
printf 'urllib3==1.24.1\n' > .tmp-security/requirements-regression.txt
if safety check -r .tmp-security/requirements-regression.txt > .tmp-security/safety-regression.log 2>&1; then
cat .tmp-security/safety-regression.log
echo "Safety unexpectedly passed the vulnerable regression probe"
exit 1
fi
if ! grep -q "urllib3" .tmp-security/safety-regression.log; then
cat .tmp-security/safety-regression.log
echo "Safety failed the regression probe for an unexpected reason"
exit 1
fi
- name: Run Safety
run: |
# 88512: Cross-ecosystem false positive — PyUp advisory text
# references @langchain/google-cloud-sql-pg (npm) but is matched
# against Python langchain<1.2.24, which does not exist (latest is
# 1.2.15). Remove this ignore when PyUp corrects the entry.
safety check \
--ignore 88512 \
-r .tmp-security/requirements-main.txt \
-r .tmp-security/requirements-sdk.txt \
-r .tmp-security/requirements-integrations.txt
npm-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
- name: Install TS SDK deps from lockfile
working-directory: sdk-ts
run: npm ci
- name: npm audit
working-directory: sdk-ts
run: npm audit --audit-level=moderate
trivy:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Build API image
env:
COMPOSE_PROJECT_NAME: agentflow-security
# docker compose validates env vars across all services even when
# building only one. agentflow-api itself does not consume these.
CLICKHOUSE_USER: scan-only
CLICKHOUSE_PASSWORD: scan-only
GF_SECURITY_ADMIN_USER: scan-only
GF_SECURITY_ADMIN_PASSWORD: scan-only
run: |
docker compose -f docker-compose.prod.yml build agentflow-api
if (-not (docker image inspect agentflow-security-agentflow-api:latest 2>$null)) {
throw "agentflow-security-agentflow-api:latest was not built"
}
docker tag agentflow-security-agentflow-api:latest agentflow-api:security-scan
shell: pwsh
- name: Generate CycloneDX SBOM
# Pinned to release tag (audit p9 #3); update by bumping the
# tag, never re-pin to @master (allows upstream to alter scanner).
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: agentflow-api:security-scan
format: cyclonedx
output: agentflow-api.cdx.json
- name: Upload CycloneDX SBOM
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agentflow-api-sbom-cyclonedx
path: agentflow-api.cdx.json
if-no-files-found: error
- name: Run Trivy scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: agentflow-api:security-scan
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
ignore-unfixed: true
exit-code: "1"
- name: Upload Trivy scan results
if: always()
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: trivy-results.sarif