Skip to content
Closed
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
21 changes: 21 additions & 0 deletions .github/workflows/ai-governance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,27 @@ jobs:
echo "$body" | grep -Eqi "TDM\s*compliance:\s*(https?://)" || { echo "::error::Provide 'TDM compliance: <link>' (dataset/source register)"; exit 1; }
fi
fi
- name: Run Bandit (respect backend config; exclude tests)
shell: bash
run: |
set -e
python -m pip install --upgrade pip
pip install bandit==1.8.6
# Scan backend Python code; honor backend pyproject.toml and exclude tests as per local config
if [ -d rdm-review-dashboard-backend/src ]; then
if [ -f rdm-review-dashboard-backend/pyproject.toml ]; then
bandit -q -r rdm-review-dashboard-backend/src -c rdm-review-dashboard-backend/pyproject.toml -f json -o bandit.json || true
else
bandit -q -r rdm-review-dashboard-backend/src -x rdm-review-dashboard-backend/tests -f json -o bandit.json || true
fi
else
echo '{"results":[]}' > bandit.json
fi
- name: Upload Bandit report (artifact only)
uses: actions/upload-artifact@v4
with:
name: bandit-report
path: bandit.json
scancode:
if: ${{ inputs.run_scancode }}
runs-on: ubuntu-latest
Expand Down
55 changes: 49 additions & 6 deletions .github/workflows/code-review-agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ jobs:
if: steps.diff.outputs.has_py == 'true'
run: |
python -m pip install --upgrade pip
pip install ruff==0.5.7 bandit==1.7.9
# Align Bandit version with local dev to ensure consistent config parsing (pyproject.toml support)
pip install ruff==0.5.7 bandit==1.8.6

- name: Run Ruff (style/quality)
if: steps.diff.outputs.has_py == 'true'
Expand All @@ -66,13 +67,36 @@ jobs:
shell: bash
run: |
mapfile -t files < py_changed.txt || true
if [ ${#files[@]} -gt 0 ]; then
bandit -q -f json -o bandit.json "${files[@]}" || true
# Exclude test files to match local Bandit config (tests/*)
filtered=()
for f in "${files[@]}"; do
# Skip any path segment named 'tests'
if [[ "$f" == tests/* ]] || [[ "$f" == */tests/* ]]; then
continue
fi
filtered+=("$f")
done
if [ ${#filtered[@]} -gt 0 ]; then
if [ -f rdm-review-dashboard-backend/pyproject.toml ]; then
# Load Bandit configuration from backend pyproject.toml to ensure excludes match local runs
bandit -q -f json -o bandit.json -c rdm-review-dashboard-backend/pyproject.toml "${filtered[@]}" || true
else
bandit -q -f json -o bandit.json "${filtered[@]}" || true
fi
else
echo '{"results":[]}' > bandit.json
fi

- name: Comment review summary
- name: Upload analysis artifacts
if: steps.diff.outputs.has_py == 'true'
uses: actions/upload-artifact@v4
with:
name: python-review-artifacts
path: |
ruff.json
bandit.json

- name: Comment review summary (truncated)
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand Down Expand Up @@ -123,6 +147,8 @@ jobs:
banditByFile.set(fn, arr);
}

const FILE_LIMIT = 10; // max files to show per tool
const PER_FILE_LIMIT = 5; // max findings per file
const mk = (arr) => arr.map(x => `- L${x.line}${x.col?':C'+x.col:''} ${x.code? '['+x.code+'] ':''}${x.msg}`).join('\n');
const mkb = (arr) => arr.map(x => `- L${x.line} [${x.sev}/${x.conf}] ${x.msg}`).join('\n');

Expand All @@ -136,13 +162,30 @@ jobs:
} else {
body += `Analyzed ${pyFiles.length} Python file(s).\n\n`;
body += `Ruff findings: ${ruffCount}\n`;
let printedFiles = 0;
for (const [file, items] of ruffByFile.entries()) {
body += `\n${file}\n${mk(items)}\n`;
if (printedFiles >= FILE_LIMIT) { break; }
const shown = items.slice(0, PER_FILE_LIMIT);
const rest = items.length - shown.length;
body += `\n${file}\n${mk(shown)}\n`;
if (rest > 0) body += `… and ${rest} more in this file\n`;
printedFiles++;
}
const moreFilesR = ruffByFile.size - printedFiles;
if (moreFilesR > 0) body += `… and ${moreFilesR} more files (see artifacts)\n`;

body += `\nBandit findings: ${banditCount}\n`;
printedFiles = 0;
for (const [file, items] of banditByFile.entries()) {
body += `\n${file}\n${mkb(items)}\n`;
if (printedFiles >= FILE_LIMIT) { break; }
const shown = items.slice(0, PER_FILE_LIMIT);
const rest = items.length - shown.length;
body += `\n${file}\n${mkb(shown)}\n`;
if (rest > 0) body += `… and ${rest} more in this file\n`;
printedFiles++;
}
const moreFilesB = banditByFile.size - printedFiles;
if (moreFilesB > 0) body += `… and ${moreFilesB} more files (see artifacts)\n`;
if (ruffCount === 0 && banditCount === 0) {
body += `\nNo issues found. ✅`;
} else {
Expand Down
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,16 @@ venv: ## Create a Python virtual environment in .venv
test: venv ## Run backend unit tests in .venv
. .venv/bin/activate \
&& python -m pip install -r rdm-review-dashboard-backend/requirements.txt -r rdm-review-dashboard-backend/requirements-dev.txt \
&& python -m pytest -q rdm-review-dashboard-backend/tests
&& python -m pytest -q rdm-review-dashboard-backend/tests

.PHONY: bandit
bandit: venv ## Run Bandit (non-strict) and always exit 0; use 'make bandit-strict' to fail on findings
. .venv/bin/activate \
&& python -m pip install -r rdm-review-dashboard-backend/requirements-dev.txt \
&& bandit --exit-zero -q -r rdm-review-dashboard-backend/src -x rdm-review-dashboard-backend/tests

.PHONY: bandit-strict
bandit-strict: venv ## Run Bandit and fail on any findings
. .venv/bin/activate \
&& python -m pip install -r rdm-review-dashboard-backend/requirements-dev.txt \
&& bandit -q -r rdm-review-dashboard-backend/src -x rdm-review-dashboard-backend/tests
16 changes: 16 additions & 0 deletions ai-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,17 @@ If any expected file is absent upstream when bootstrapping, warn and proceed wit
- Update README or inline docs when behavior or interfaces change.
- Use `log_provenance` to append AI-Assistance details to the PR body.

- Comments policy

- Prefer self-explanatory code over comments: clear names, small functions, and tests that document behavior.
- Avoid inline comments unless strictly necessary. Acceptable cases:
- Required license/attribution headers.
- Public API docstrings and deprecation notes (concise and actionable).
- Temporary workarounds linked to an upstream issue or ticket (include TODO to remove).
- Security annotations only when a vetted false positive cannot be refactored away (link to rationale/issue).
- Don’t restate the obvious; remove stale or misleading comments when editing nearby code.
- Prefer brief module/class/function docstrings for public surfaces over scattered inline remarks.

- Security, privacy, and IP

- Never include secrets/PII; scrub logs; avoid leaking tokens.
Expand Down Expand Up @@ -381,6 +392,11 @@ If any expected file is absent upstream when bootstrapping, warn and proceed wit
- PR review resolution:
- Addressed Copilot nit by replacing broad `Exception` with `KeyError` for dict-like access and deletion in `services/locks.py`.
- Resolved the two Copilot review threads (locks nit addressed; filesystem note acknowledged).
- Security annotations policy:
- Avoid inline `# nosec` comments unless strictly necessary (e.g., a vetted false positive that cannot be refactored away). Prefer:
- Parameterization and safe construction patterns (SQL placeholders, constant-only clause assembly).
- Tool configuration or non-strict runs locally (`make bandit`) and strict in CI (`make bandit-strict`) when needed.
- Tests that assert safety properties (e.g., correct SQL placeholders, timeouts applied) to prevent regression.
- Follow-ups (optional):
- Consider pinning reusable governance workflow to a stable tag/SHA for regulated environments.
- Add real UI unit tests under `rdm-review-dashboard-ui/src/**/*.spec.ts` to enable meaningful UI CI coverage instead of skipping/empty-suite handling.
Expand Down
7 changes: 7 additions & 0 deletions rdm-review-dashboard-backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[tool.bandit]
# Exclude tests from Bandit scans and ignore shelve usage warnings in trusted local services.
skips = ["B101"]
exclude = [
"tests/*"
]
# Note: We use targeted # nosec comments for B301/B403 where appropriate.
1 change: 1 addition & 0 deletions rdm-review-dashboard-backend/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pytest
pytest-mock
bandit
5 changes: 4 additions & 1 deletion rdm-review-dashboard-backend/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ def configure_users(settings):

if __name__ == "__main__":
configure()
uvicorn.run(api, port=8000, host="0.0.0.0")
# Bind host is configurable; default to loopback for local safety.
_host = os.getenv("UVICORN_HOST", "127.0.0.1")
_port = int(os.getenv("UVICORN_PORT", "8000"))
uvicorn.run(api, port=_port, host=_host)
else:
configure()
Loading