Skip to content

Commit 09de03d

Browse files
committed
Security Audit & CI/CD Integration: Performed a security audit of the codebase, resolved all Medium and High severity issues, and integrated automated security scanning into the CI/CD pipeline.
1 parent d8afb17 commit 09de03d

9 files changed

Lines changed: 61 additions & 11 deletions

File tree

.bandit.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Bandit configuration file
2+
# Ref: https://bandit.readthedocs.io/en/latest/config.html
3+
4+
# Exclude files from scanning
5+
exclude_dirs: ['tests', 'venv', '.venv', '__pycache__', '.git']
6+
7+
# Explicitly skip certain tests for specific files
8+
# (Not supported in .bandit.yaml directly, but we can configure global skips)
9+
# we will use the --skips flag in CI, but here we define the basics
10+
11+
# skips: ['B101'] # We could skip B101 globally, but it's better to exclude tests dir
12+
13+
# Profiles
14+
profiles:
15+
clinical_audit:
16+
include:
17+
- any_other_test_id_here
18+
exclude:
19+
- B101 # Assert used (common in tests, but we excluded tests dir already)

.github/workflows/pipeline.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,25 @@ jobs:
2323
run: |
2424
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
2525
26+
security-audit:
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
30+
- name: Set up Python 3.12
31+
uses: actions/setup-python@v5
32+
with:
33+
python-version: '3.12'
34+
- name: Install Security Tools
35+
run: |
36+
pip install bandit safety
37+
- name: Bandit Security Scan
38+
run: |
39+
# Scan excluding tests and venv, fail on Medium severity or higher
40+
bandit -r . -x ./tests,./venv,./.venv -ll
41+
- name: Safety Dependency Audit
42+
run: |
43+
safety check -r requirements.txt
44+
2645
test:
2746
needs: lint
2847
runs-on: ubuntu-latest

api/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,6 @@ def get_monitoring_status():
205205
}
206206

207207
if __name__ == "__main__":
208-
uvicorn.run(app, host="0.0.0.0", port=8000)
208+
host = os.getenv("HOST", "127.0.0.1")
209+
port = int(os.getenv("PORT", 8000))
210+
uvicorn.run(app, host=host, port=port)

app/main.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
from src.monitoring.engine import MonitoringEngine
5353
from app.components.footer import render_footer
5454
import streamlit.components.v1 as components
55-
import subprocess
5655
import sys
5756

5857
# Page Config
@@ -263,7 +262,7 @@ def user_input_features():
263262
st.info(f"Medical Grade AI | v{metadata.get('version', '1.0.0') if metadata else 'N/A'} Production")
264263
st.sidebar.markdown("---")
265264
st.sidebar.caption(f" **Clinical Engine**: v{metadata.get('version', '1.0.0') if metadata else 'N/A'}")
266-
st.sidebar.caption(f" **Audit Hash**: {hashlib.md5(str(metadata).encode()).hexdigest()[:8].upper() if metadata else 'N/A'}")
265+
st.sidebar.caption(f" **Audit Hash**: {hashlib.md5(str(metadata).encode(), usedforsecurity=False).hexdigest()[:8].upper() if metadata else 'N/A'}")
267266

268267
# --- MAIN DASHBOARD ---
269268
# 1. Safety & Trust Header
@@ -407,7 +406,7 @@ def user_input_features():
407406
# Check for simulation state
408407
opt_full = st.session_state.get('last_opt_full', None)
409408
confidence = safety_engine.calculate_confidence(probability[0][1])
410-
audit_hash = hashlib.md5(str(metadata).encode()).hexdigest()[:12].upper()
409+
audit_hash = hashlib.md5(str(metadata).encode(), usedforsecurity=False).hexdigest()[:12].upper()
411410

412411
radar_plot_path = None
413412
if opt_full:

logs/cardiosense.log

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,11 @@ ValueError: [TypeError("'numpy.bool' object is not iterable"), TypeError('vars()
342342
2026-04-06 21:22:18,632 | INFO | [API-ENGINE] | REQ [39751c2f-209d-4730-a0a4-0837150ae755] | GET /health | STATUS: 200 | DUR: 0.0020s
343343
2026-04-06 21:22:35,605 | INFO | [API-ENGINE] | REQ [b1925b6f-7680-470c-89a4-ae78ce9c1f7c] | GET /monitoring/status | STATUS: 200 | DUR: 0.5281s
344344
2026-04-06 21:23:03,067 | INFO | [API-ENGINE] | Shutting down Clinical Intelligence System...
345+
2026-04-11 11:24:58,739 | INFO | [API-ENGINE] | Initializing Clinical Intelligence System...
346+
2026-04-11 11:24:58,777 | INFO | [API-ENGINE] | Predictor model and preprocessor successfully loaded.
347+
2026-04-11 11:24:58,778 | INFO | [API-ENGINE] | Clinical metadata loaded. Active Model Version: 2.4.0
348+
2026-04-11 11:25:05,575 | INFO | [API-ENGINE] | REQ [fdab21b0-bef9-4eda-bd57-f187ff0b3441] | GET / | STATUS: 200 | DUR: 0.0041s
349+
2026-04-11 11:25:06,283 | INFO | [API-ENGINE] | REQ [7c1ce280-1cbf-455b-8087-57a27827cf74] | GET /favicon.ico | STATUS: 404 | DUR: 0.0005s
350+
2026-04-11 11:25:11,432 | INFO | [API-ENGINE] | REQ [0df41d96-6591-4aa3-8a81-9a70c28ada4c] | GET /docs | STATUS: 200 | DUR: 0.0005s
351+
2026-04-11 11:25:12,193 | INFO | [API-ENGINE] | REQ [1e75a09c-be4d-4f92-9ef7-b69c598e5d46] | GET /openapi.json | STATUS: 200 | DUR: 0.0169s
352+
2026-04-11 11:25:27,862 | INFO | [API-ENGINE] | Shutting down Clinical Intelligence System...

src/monitoring/engine.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ def run_drift_analysis(self, window_size: int = 500) -> Dict[str, Any]:
111111
drift_report.save_html(html_path)
112112
elif hasattr(drift_report, 'save'):
113113
drift_report.save(html_path)
114-
except Exception:
115-
# Silent fallback for environment-specific rendering limits
116-
pass
114+
except Exception as e:
115+
# Fallback for environment-specific rendering limits
116+
self._log(f"Report HTML save failed: {e}", "WARNING")
117117

118118
# Extract Summary Metrics (Bypassing missing methods via Direct Attribute Access)
119119
drift_share = 0.0

src/monitoring/logger.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ def log_feedback(self, request_id: str, outcome: int):
6969
def get_recent_logs(self, limit: int = 1000) -> pd.DataFrame:
7070
"""Retrieves recent logs as a pandas DataFrame for drift analysis."""
7171
conn = sqlite3.connect(self.db_path)
72-
query = f"SELECT * FROM inference_logs ORDER BY timestamp DESC LIMIT {limit}"
73-
df = pd.read_sql_query(query, conn)
72+
query = "SELECT * FROM inference_logs ORDER BY timestamp DESC LIMIT ?"
73+
df = pd.read_sql_query(query, conn, params=(int(limit),))
7474
conn.close()
7575

7676
if df.empty:

src/recommendation/engine.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ def generate_prioritized_recommendations(self, data: pd.DataFrame, probability:
113113
"rationale": f"SHAP analysis identifies {feat.upper()} as a top driver for this patient's risk profile.",
114114
"type": "Data-Driven"
115115
})
116-
except Exception:
116+
except Exception as e:
117+
# In a clinical environment, we log the failure to generate a specific recommendation
118+
# but allow the engine to proceed with other recommendations.
119+
print(f"DEBUG: Recommendation generation failed for factor {feat}: {e}")
117120
continue
118121

119122
# Sort: High -> Moderate -> Low

src/utils/report_generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def __init__(self, logo_path="app/assets/logo.png", audit_hash=None, version="2.
1515
self.version = version
1616
self.set_auto_page_break(auto=True, margin=15)
1717
self.report_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
18-
self.report_id = hashlib.md5(self.report_date.encode()).hexdigest()[:8].upper()
18+
self.report_id = hashlib.md5(self.report_date.encode(), usedforsecurity=False).hexdigest()[:8].upper()
1919
self.audit_hash = audit_hash or "N/A"
2020

2121
def header(self):

0 commit comments

Comments
 (0)