Quality and security gates¶
This document defines how Node Wire enforces security scanning in CI.
This repository enforces security gates at both PR time and publish time.
CI quality gates¶
Workflow: .github/workflows/quality-gates.yml
Runs on every pull request and on pushes to main.
Required jobs:
bandit: writesbandit-report.json(with--exit-zeroso low/medium findings do not fail the job before the gate), prints a log summary, uploads the artifact, then fails only on high-severity findings in the enforce step.
Workflow: .github/workflows/codeql.yml
Runs GitHub CodeQL static analysis for Python on pull requests, pushes to main, and weekly (Mondays). No repository secrets are required.
Workflow: .github/workflows/pytest.yml
Runs the full test suite on Linux, macOS, and Windows (Python 3.11 and 3.12
matrix) with coverage on every pull request and push to main.
Playground integration tests remain manual (workflow_dispatch) on Ubuntu only.
Workflow: .github/workflows/lint.yml also runs lockfile-check (uv lock --check) to fail PRs when pyproject.toml changes without an updated uv.lock.
Workflow: .github/workflows/secret-scan.yml
Runs Gitleaks on pull requests, pushes to
main, weekly (Mondays), and on manual dispatch. The workflow checks
out full git history (fetch-depth: 0) so secrets in past commits are
scanned, not only the working tree.
Required checks to add in branch protection:
Lint and Type Check / Lockfile freshnessQuality gates / Bandit security scanCodeQL / Analyze (Python)Secret scan / Gitleaks secret scanCI – Pytest / Run pytest (ubuntu-latest, Python 3.11)CI – Pytest / Run pytest (ubuntu-latest, Python 3.12)CI – Pytest / Run pytest (macos-latest, Python 3.11)CI – Pytest / Run pytest (macos-latest, Python 3.12)CI – Pytest / Run pytest (windows-latest, Python 3.11)CI – Pytest / Run pytest (windows-latest, Python 3.12)Python package security PR checks / Vulnerability scan (packages/runtime)Python package security PR checks / Vulnerability scan (packages/connectors/http_generic)Python package security PR checks / Vulnerability scan (packages/connectors/stripe)Python package security PR checks / Vulnerability scan (packages/connectors/smtp)Python package security PR checks / Vulnerability scan (packages/connectors/google_drive)Python package security PR checks / Vulnerability scan (packages/connectors/fhir_cerner)Python package security PR checks / Vulnerability scan (packages/connectors/fhir_epic)Python package security PR checks / Vulnerability scan (packages/connectors/salesforce)Python package security PR checks / Vulnerability scan (packages/connectors/slack)
Configure branch protection so pull requests cannot merge unless all required checks pass.
CVE scanning policy¶
- PR and push-to-main scanning runs in
.github/workflows/security-pr.yml. - Release-time scanning remains in
.github/workflows/publish.ymlas defense in depth. - The PR/push gate (
security-pr.yml) runspip-auditwith no--fail-onthreshold, so it blocks on any vulnerability. The release workflow (publish.yml) usespip-audit --fail-on HIGHas defense in depth. - Scheduled scans catch newly disclosed CVEs even when code does not change.
Monorepo install note: Connector packages under packages/connectors/* declare node-wire-runtime>=1.0.0 as a normal PyPI dependency name. The security workflow installs packages/runtime from the checkout together with each matrix package (pip install packages/runtime "<matrix path>") so pip can resolve node-wire-runtime without requiring a published wheel on PyPI. Locally, mirror that when auditing a single connector: pip install packages/runtime packages/connectors/<name>.
Secret scanning¶
Workflow: .github/workflows/secret-scan.yml (Gitleaks).
Policy:
- Scan on every PR and push to
main, plus a weekly scheduled run. - Full repository history is included (
fetch-depth: 0). - Findings fail the workflow; remediate by rotating exposed credentials and removing secrets from the codebase (never commit live secrets).
Run locally¶
Install Gitleaks (e.g. brew install gitleaks
on macOS), then from the repository root:
# Working tree (staged + unstaged changes vs HEAD)
gitleaks detect --source . --redact --verbose
# Full git history (matches CI intent)
gitleaks detect --source . --redact --verbose --log-opts="--all"
If GitHub Advanced Security secret scanning is enabled at the organization level, treat it as defense in depth; the in-repo workflow provides auditable CI evidence.
Run checks locally¶
# Install dev tools from committed lockfile
uv sync --frozen --all-extras --dev
# Security gate (matches CI failure threshold)
uv run bandit -c pyproject.toml -r src --severity-level high
# Optional: JSON report + same summary as CI logs
uv run bandit -c pyproject.toml -r src -f json -o bandit-report.json --exit-zero
python scripts/bandit_report_summary.py bandit-report.json
# Tests + coverage (run via pytest.yml in CI)
uv run pytest tests/ -v
Deterministic pytest environment¶
To keep pytest collection and REST app startup deterministic, tests/conftest.py sets a fixed environment before imports:
NW_REST_LOAD_DOTENV=falseso REST startup does not merge a repo-root.envover test variables.NW_CONFIG_PATH=tests/fixtures/connectors_for_tests.yamlso optional connectors outside the pytest allowlist remainenabled: false(for exampleslackandsalesforce).NW_ALLOWED_CONNECTORS=http_generic,smtp,stripe,google_drive,fhir_epic,fhir_cernerso only the supported test connector set is loaded during collection.
Do not rely on .env values during pytest collection. The test harness intentionally overrides them so local developer state does not affect CI or test outcomes.
Pre-commit¶
Bandit policy¶
Bandit is configured in pyproject.toml under [tool.bandit].
Exit codes and CI behavior¶
By default, Bandit exits with a non-zero status whenever it reports any finding, including low and medium severity. That affects -f json -o ... the same as text output.
CI splits responsibilities:
- JSON artifact + log summary —
bandit ... -f json -o bandit-report.json --exit-zeroso the workflow always produces the report and runsscripts/bandit_report_summary.pyfor readable logs. Low/medium issues are visible here without failing the job. - Enforcement —
bandit ... --severity-level highfails the job only on high-severity findings (matches branch-protection intent).
Locally, mirror CI with the commands in Run checks locally.
Scope¶
Policy:
- Scan target:
src/(runtime, bindings, in-tree connector implementations installed via the root package). - Exclude:
.venv,venv,tests,playground,dist,htmlcov. - CI enforcement threshold:
--severity-level high. - Packages tree: connector distributions under
packages/connectors/*are audited for CVEs in.github/workflows/security-pr.yml(pip-audit). Run Bandit against those paths separately if you need SAST on a standalone checkout.
If legacy findings block adoption, create a baseline once and track deltas:
bandit -c pyproject.toml -r src -f json -o bandit-baseline.json --exit-zero
bandit -c pyproject.toml -r src --baseline bandit-baseline.json --severity-level high
SBOM generation¶
CycloneDX SBOM (sbom.json) is generated by:
scripts/run-compliance-checks.shfor local compliance runs..github/workflows/github-release.ymlat release time (attached to the GitHub Release).
Acceptance criteria mapping¶
- Security scan runs on every PR: enforced by
quality-gates.yml(Bandit) andcodeql.yml(CodeQL). - Builds fail on high-severity Bandit findings: Bandit gate in CI.
- Static analysis visible in GitHub Security tab: CodeQL upload from CI.
- Tests run on every PR: enforced by
pytest.yml(Linux/macOS/Windows × Python 3.11/3.12). - Developers run checks locally: documented commands and pre-commit (Bandit).
- Config version-controlled:
pyproject.toml,.pre-commit-config.yaml, workflow files.