Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/bandit-baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"results": [
{
"code": "34 run_cmd,\n35 shell=True,\n36 capture_output=True,\n37 text=True,\n38 cwd=cwd,\n39 timeout=300,\n40 )\n41 output = {\n42 \"exit_code\": proc.returncode,\n43 \"stdout\": proc.stdout,\n",
"col_offset": 19,
"end_col_offset": 13,
"filename": "src/specify_cli/workflows/steps/shell/__init__.py",
"issue_confidence": "HIGH",
"issue_cwe": {
"id": 78,
"link": "https://cwe.mitre.org/data/definitions/78.html"
},
"issue_severity": "HIGH",
"issue_text": "subprocess call with shell=True identified, security issue.",
"line_number": 35,
"line_range": [
33,
34,
35,
36,
37,
38,
39,
40
],
"more_info": "https://bandit.readthedocs.io/en/1.9.4/plugins/b602_subprocess_popen_with_shell_equals_true.html",
"test_id": "B602",
"test_name": "subprocess_popen_with_shell_equals_true"
}
]
}
101 changes: 101 additions & 0 deletions .github/scripts/check_security_requirements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Check that committed security audit requirements are up to date."""

from __future__ import annotations

import os
import subprocess
import sys
from pathlib import Path


REPO_ROOT = Path(__file__).resolve().parents[2]
COMMITTED_REQUIREMENTS = REPO_ROOT / ".github" / "security-audit-requirements.txt"
DEPENDENCY_INPUTS = ("pyproject.toml", ".github/security-audit-requirements.txt")


def _dependency_diff_refs() -> tuple[str, str]:
base_ref = os.environ.get("DEPENDENCY_DIFF_BASE", "").strip()
head_ref = os.environ.get("DEPENDENCY_DIFF_HEAD", "").strip() or "HEAD"
if base_ref and not set(base_ref) <= {"0"}:
return base_ref, head_ref
return "HEAD^", "HEAD"


def _dependency_inputs_changed() -> bool:
base_ref, head_ref = _dependency_diff_refs()
try:
result = subprocess.run(
[
"git",
"diff",
"--name-only",
base_ref,
head_ref,
"--",
*DEPENDENCY_INPUTS,
],
check=True,
cwd=REPO_ROOT,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True,
)
except subprocess.CalledProcessError as exc:
print(
"Could not determine changed dependency inputs; checking requirements.",
file=sys.stderr,
)
if exc.stderr:
print(exc.stderr.strip(), file=sys.stderr)
return True

changed_inputs = [line for line in result.stdout.splitlines() if line]
if not changed_inputs:
print("Dependency audit inputs unchanged; sync check skipped.")
return False

print(f"Dependency audit inputs changed: {', '.join(changed_inputs)}")
return True


def main() -> int:
if not _dependency_inputs_changed():
return 0

generated_requirements = Path(os.environ["GENERATED_REQUIREMENTS"])
generated_requirements.parent.mkdir(parents=True, exist_ok=True)

subprocess.run(
[
"uv",
"pip",
"compile",
"pyproject.toml",
"--extra",
"test",
"--universal",
"--generate-hashes",
"--quiet",
"--no-header",
"--output-file",
str(generated_requirements),
],
check=True,
cwd=REPO_ROOT,
)

committed = COMMITTED_REQUIREMENTS.read_text(encoding="utf-8")
generated = generated_requirements.read_text(encoding="utf-8")
if committed == generated:
return 0

print(
"Regenerate .github/security-audit-requirements.txt with the documented "
"uv pip compile command.",
file=sys.stderr,
)
return 1


if __name__ == "__main__":
raise SystemExit(main())
318 changes: 318 additions & 0 deletions .github/security-audit-requirements.txt

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ jobs:
language: [ 'actions', 'python' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
with:
languages: ${{ matrix.language }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
with:
category: "/language:${{ matrix.language }}"
11 changes: 5 additions & 6 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0 # Fetch all history for git info

- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: '8.x'

Expand All @@ -48,10 +48,10 @@ jobs:
docfx docfx.json

- name: Setup Pages
uses: actions/configure-pages@v6
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6

- name: Upload artifact
uses: actions/upload-pages-artifact@v5
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
with:
path: 'docs/_site'

Expand All @@ -66,5 +66,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5

uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Run markdownlint-cli2
uses: DavidAnson/markdownlint-cli2-action@6b51ade7a9e4a75a7ad929842dd298a3804ebe8b # v23
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-trigger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_PAT }}
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down Expand Up @@ -86,4 +86,3 @@ jobs:
--notes-file release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

74 changes: 74 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Security Audit

permissions:
contents: read

on:
push:
branches: ["main"]
pull_request:
schedule:
- cron: "17 4 * * 1"
workflow_dispatch:

jobs:
dependency-audit:
name: Dependency audit (${{ matrix.os }}, Python ${{ matrix.python-version }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.11", "3.12", "3.13"]
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 2

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ matrix.python-version }}

- name: Compile scheduled audit requirements
if: ${{ github.event_name == 'schedule' }}
run: |
uv pip compile pyproject.toml --extra test --python-version "${{ matrix.python-version }}" --generate-hashes --quiet --output-file "${{ runner.temp }}/spec-kit-audit-requirements.txt"

- name: Run pip-audit (scheduled live resolution)
if: ${{ github.event_name == 'schedule' }}
run: uvx --from pip-audit==2.10.0 pip-audit --disable-pip --require-hashes -r "${{ runner.temp }}/spec-kit-audit-requirements.txt" --progress-spinner off

Comment thread
PascalThuet marked this conversation as resolved.
- name: Check committed audit requirements are current
if: ${{ github.event_name != 'schedule' }}
env:
DEPENDENCY_DIFF_BASE: ${{ github.event.pull_request.base.sha || github.event.before || '' }}
DEPENDENCY_DIFF_HEAD: ${{ github.sha }}
GENERATED_REQUIREMENTS: ${{ runner.temp }}/security-audit-requirements.txt
run: python .github/scripts/check_security_requirements.py

- name: Run pip-audit (committed requirements)
if: ${{ github.event_name != 'schedule' }}
run: uvx --from pip-audit==2.10.0 pip-audit --disable-pip --require-hashes -r .github/security-audit-requirements.txt --progress-spinner off

Comment thread
mnriem marked this conversation as resolved.
static-analysis:
name: Static analysis
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0

- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.13"

- name: Run Bandit
run: uvx --from bandit==1.9.4 bandit -r src -lll --baseline .github/bandit-baseline.json
Comment thread
PascalThuet marked this conversation as resolved.
2 changes: 1 addition & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10
with:
# Days of inactivity before an issue or PR becomes stale
days-before-stale: 150
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.13"

Expand All @@ -34,13 +34,13 @@ jobs:
python-version: ["3.11", "3.12", "3.13"]
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ matrix.python-version }}

Expand Down
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ uv run python -m pytest tests/test_agent_config_consistency.py -q

Run this when you change agent metadata, context update scripts, or integration wiring.

#### Security checks

```bash
uvx --from pip-audit==2.10.0 pip-audit --disable-pip --require-hashes -r .github/security-audit-requirements.txt --progress-spinner off
uvx --from bandit==1.9.4 bandit -r src -lll --baseline .github/bandit-baseline.json
```

Run these before changing dependency metadata, workflow execution code, subprocess usage, or security-sensitive paths. Pull request, push, and manual CI audits use the committed hashed requirements file so they stay deterministic. The scheduled CI audit also resolves the runtime and `test` extra dependency set across the supported Python and OS matrix to catch newly published advisories. If dependency metadata changes, refresh the committed audit input before running pip-audit:

```bash
uv pip compile pyproject.toml --extra test --universal --generate-hashes --quiet --no-header --output-file .github/security-audit-requirements.txt
```

### Manual testing

#### Testing setup
Expand Down
Loading