diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 4618bdd..44bca09 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -26,6 +26,25 @@ env: PYTHONUNBUFFERED: "1" jobs: + secret-scan: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + pull-requests: read + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + fetch-depth: 0 + - name: Run gitleaks + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ github.token }} + GITLEAKS_CONFIG: .gitleaks.toml + GITLEAKS_ENABLE_COMMENTS: false + GITLEAKS_ARGS: --no-git + dependency-review: if: > github.event_name == 'pull_request' && @@ -61,10 +80,29 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -e ".[full]" - python -m pip freeze --exclude-editable > .pip-audit-requirements.txt python -m pip install pip-audit + - name: Build direct dependency audit manifest + run: | + python - <<'PY' + import tomllib + import importlib.metadata + from packaging.requirements import Requirement + from pathlib import Path + + project = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8")) + direct_requires = list(project["project"].get("dependencies", [])) + direct_requires.extend(project["project"]["optional-dependencies"].get("full", [])) + + requirements_path = Path(".pip-audit-direct-requirements.txt") + with requirements_path.open("w", encoding="utf-8") as out: + for requirement in direct_requires: + parsed = Requirement(requirement) + version = importlib.metadata.version(parsed.name) + out.write(f"{parsed.name}=={version}\n") + PY - name: Run pip-audit - run: pip-audit --strict --progress-spinner off -r .pip-audit-requirements.txt --ignore-vuln CVE-2026-4539 + run: | + pip-audit --strict --progress-spinner off --no-deps --disable-pip -r .pip-audit-direct-requirements.txt codeql: if: > diff --git a/.gitleaks.toml b/.gitleaks.toml index 27bae28..4f0e1e2 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -3,33 +3,29 @@ title = "Structorium gitleaks config" [extend] useDefault = true -[[allowlists]] -description = "Blank example env placeholders are not secrets" +[allowlist] +description = "Intentional secret-shaped fixtures and template placeholders" paths = [ - '''^\.env\.example$''', + '''(^|/)tests/detectors/security/test_rules\.py$''', + '''(^|/)tests/detectors/security/test_security\.py$''', + '''(^|/)structorium/tests/detectors/security/test_rules\.py$''', + '''(^|/)structorium/tests/detectors/security/test_security\.py$''', + '''(^|/)\.env\.example$''', + '''(^|/)docker-compose\.neo4j\.yml$''', + '''(^|/)\.venv/''', + '''(^|/)__pycache__/''', + '''(^|/)\.pkg-smoke/''', + '''(^|/)dist/''', ] regexes = [ '''(?m)^(OPENAI_API_KEY|COHERE_API_KEY|TURBOPUFFER_API_KEY|NEO4J_PASSWORD)=$''', -] - -[[allowlists]] -description = "Intentional secret-shaped fixtures for detector coverage" -paths = [ - '''^tests/detectors/security/test_rules\.py$''', - '''^tests/detectors/security/test_security\.py$''', -] -regexes = [ '''AKIA1234567890ABCDEF''', '''ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkl''', '''sk_live_abcdefghijklmnopqrst''', '''eyJhbGciOiJIUzI1NiJ9\.eyJzdWIiOiIxMjM0NTY3ODkwIn0\.abc123_-xyz''', -] - -[[allowlists]] -description = "Ignore generated caches and local virtualenvs during filesystem scans" -paths = [ - '''(^|/)\.venv/''', - '''(^|/)__pycache__/''', - '''(^|/)\.pkg-smoke/''', - '''(^|/)dist/''', + '''NEO4J_AUTH=\$\{NEO4J_USERNAME:-neo4j\}/\$\{NEO4J_PASSWORD:-changeme\}''', + '''OPENAI_API_KEY=$''', + '''COHERE_API_KEY=$''', + '''TURBOPUFFER_API_KEY=$''', + '''NEO4J_PASSWORD=$''', ]