Skip to content

Commit 8148b43

Browse files
strickvlclaude
andcommitted
Add robust input validation for ENABLE_SLACK environment variable
- Add strict validation to only accept "true" or "false" values for ENABLE_SLACK - In Modal app creation: raise ValueError and abort deployment for invalid values - In incident reporting: log error but gracefully continue with disabled Slack - Prevents silent failures from typos like "True", "1", "yes", or "tru" - Provides clear error messages showing expected vs actual values This improves user experience by catching configuration errors early rather than silently defaulting to disabled Slack notifications. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 4e99af8 commit 8148b43

File tree

4 files changed

+59
-41
lines changed

4 files changed

+59
-41
lines changed

credit-scorer/modal_app/modal_deployment.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,16 @@ def create_modal_app(python_version: str = "3.12.9"):
8484
}
8585

8686
# Only add secrets if Slack notifications are explicitly enabled
87-
enable_slack = os.getenv("ENABLE_SLACK", "false").lower() == "true"
87+
enable_slack_raw = os.getenv("ENABLE_SLACK", "false").lower()
88+
if enable_slack_raw not in {"true", "false"}:
89+
logger.error(
90+
f"Invalid value for ENABLE_SLACK: '{enable_slack_raw}'. Expected 'true' or 'false'. Deployment aborted."
91+
)
92+
raise ValueError(
93+
f"Invalid ENABLE_SLACK value: '{enable_slack_raw}'. Deployment aborted."
94+
)
95+
96+
enable_slack = enable_slack_raw == "true"
8897
if enable_slack:
8998
try:
9099
app_config["secrets"] = [modal.Secret.from_name(SECRET_NAME)]
@@ -182,7 +191,16 @@ def _report_incident(incident_data: dict, model_checksum: str) -> dict:
182191
logger.warning(f"Could not write to local incident log: {e}")
183192

184193
# 2. Direct Slack notification for high/critical severity (not using ZenML)
185-
enable_slack = os.getenv("ENABLE_SLACK", "false").lower() == "true"
194+
enable_slack_raw = os.getenv("ENABLE_SLACK", "false").lower()
195+
if enable_slack_raw not in {"true", "false"}:
196+
logger.error(
197+
f"Invalid value for ENABLE_SLACK: '{enable_slack_raw}'. Expected 'true' or 'false'."
198+
)
199+
# Don't abort incident reporting, just skip Slack notification
200+
enable_slack = False
201+
else:
202+
enable_slack = enable_slack_raw == "true"
203+
186204
if incident["severity"] in ("high", "critical") and enable_slack:
187205
try:
188206
slack_token = os.getenv("SLACK_BOT_TOKEN")

credit-scorer/src/steps/deployment/generate_sbom.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,15 @@ def generate_sbom_html(sbom_data: Dict[str, Any], timestamp: str) -> str:
162162
<div class="content">
163163
<div class="card sbom-header">
164164
<h2>SBOM Information</h2>
165-
<p><strong>Format:</strong> {sbom_data.get('bomFormat', 'CycloneDX')}</p>
166-
<p><strong>Spec Version:</strong> {sbom_data.get('specVersion', 'N/A')}</p>
167-
<p><strong>Serial Number:</strong> <span class="monospace checksum">{sbom_data.get('serialNumber', 'N/A')}</span></p>
165+
<p><strong>Format:</strong> {sbom_data.get("bomFormat", "CycloneDX")}</p>
166+
<p><strong>Spec Version:</strong> {sbom_data.get("specVersion", "N/A")}</p>
167+
<p><strong>Serial Number:</strong> <span class="monospace checksum">{sbom_data.get("serialNumber", "N/A")}</span></p>
168168
<p><strong>Generated:</strong> {timestamp}</p>
169169
</div>
170170
171171
<div class="card sbom-metadata">
172172
<h2>Metadata</h2>
173-
<p><strong>Timestamp:</strong> {metadata.get('timestamp', 'N/A')}</p>
173+
<p><strong>Timestamp:</strong> {metadata.get("timestamp", "N/A")}</p>
174174
<p><strong>Total Components:</strong> {len(components)}</p>
175175
</div>
176176

credit-scorer/src/steps/deployment/post_run_annex.py

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def generate_enhanced_annex_iv_html(
253253
</tr>
254254
</table>
255255
256-
{generate_previous_versions_table(metadata.get('pipeline_runs', []))}
256+
{generate_previous_versions_table(metadata.get("pipeline_runs", []))}
257257
258258
<p><strong>Intended Purpose:</strong> To evaluate credit risk for loan applicants by providing an objective, fair, and transparent score based on financial history and demographic data.</p>
259259
</div>
@@ -262,22 +262,22 @@ def generate_enhanced_annex_iv_html(
262262
<h3>1(b) System Interactions</h3>
263263
<div class="info-grid">
264264
<div class="info-label">Stack Name:</div>
265-
<div class="info-value">{stack_info.get('name', 'Unknown')}</div>
265+
<div class="info-value">{stack_info.get("name", "Unknown")}</div>
266266
<div class="info-label">Stack ID:</div>
267-
<div class="info-value">{stack_info.get('id', 'Unknown')}</div>
267+
<div class="info-value">{stack_info.get("id", "Unknown")}</div>
268268
<div class="info-label">Created:</div>
269-
<div class="info-value">{stack_info.get('created', 'Unknown')}</div>
269+
<div class="info-value">{stack_info.get("created", "Unknown")}</div>
270270
</div>
271-
{generate_stack_components_table(metadata.get('stack_components', {}))}
271+
{generate_stack_components_table(metadata.get("stack_components", {}))}
272272
</div>
273273
274274
<div class="subsection">
275275
<h3>1(c) Software Versions</h3>
276276
<div class="info-grid">
277277
<div class="info-label">Pipeline Commit:</div>
278-
<div class="info-value">{git_info.get('commit', 'Unknown')}</div>
278+
<div class="info-value">{git_info.get("commit", "Unknown")}</div>
279279
<div class="info-label">Repository:</div>
280-
<div class="info-value">{git_info.get('repository', 'Unknown')}</div>
280+
<div class="info-value">{git_info.get("repository", "Unknown")}</div>
281281
</div>
282282
{generate_framework_versions_table(frameworks)}
283283
</div>
@@ -288,7 +288,7 @@ def generate_enhanced_annex_iv_html(
288288
<div class="info-label">Type:</div>
289289
<div class="info-value">Modal + FastAPI (Serverless API deployment with auto-scaling)</div>
290290
<div class="info-label">Environment:</div>
291-
<div class="info-value">{deployment_info.get('environment', 'Production') if deployment_info else 'Production'}</div>
291+
<div class="info-value">{deployment_info.get("environment", "Production") if deployment_info else "Production"}</div>
292292
<div class="info-label">Scaling:</div>
293293
<div class="info-value">Automatic</div>
294294
</div>
@@ -308,12 +308,12 @@ def generate_enhanced_annex_iv_html(
308308
<div class="subsection">
309309
<h3>2(a) Development Methods and Third-party Tools</h3>
310310
311-
{generate_pipeline_execution_history(metadata.get('pipeline_execution_history', []))}
311+
{generate_pipeline_execution_history(metadata.get("pipeline_execution_history", []))}
312312
313313
<h4 style="margin-top: 2rem;">Development Environment</h4>
314314
<div class="info-grid">
315315
<div class="info-label">Source Repository:</div>
316-
<div class="info-value">{git_info.get('repository', '[email protected]:zenml-io/zenml-projects.git')}</div>
316+
<div class="info-value">{git_info.get("repository", "[email protected]:zenml-io/zenml-projects.git")}</div>
317317
<div class="info-label">Version Control:</div>
318318
<div class="info-value">Git</div>
319319
<div class="info-label">CI/CD Platform:</div>
@@ -348,19 +348,19 @@ def generate_enhanced_annex_iv_html(
348348
<div class="metric-label">Accuracy</div>
349349
</div>
350350
<div class="metric-card">
351-
<div class="metric-value text-success">{model_metrics.get('f1_score', 0):.3f}</div>
351+
<div class="metric-value text-success">{model_metrics.get("f1_score", 0):.3f}</div>
352352
<div class="metric-label">F1 Score</div>
353353
</div>
354354
<div class="metric-card">
355-
<div class="metric-value">{model_metrics.get('auc_roc', 0):.3f}</div>
355+
<div class="metric-value">{model_metrics.get("auc_roc", 0):.3f}</div>
356356
<div class="metric-label">AUC-ROC</div>
357357
</div>
358358
<div class="metric-card">
359-
<div class="metric-value text-warning">{model_metrics.get('precision', 0):.3f}</div>
359+
<div class="metric-value text-warning">{model_metrics.get("precision", 0):.3f}</div>
360360
<div class="metric-label">Precision</div>
361361
</div>
362362
<div class="metric-card">
363-
<div class="metric-value text-danger">{model_metrics.get('recall', 0):.3f}</div>
363+
<div class="metric-value text-danger">{model_metrics.get("recall", 0):.3f}</div>
364364
<div class="metric-label">Recall</div>
365365
</div>
366366
</div>
@@ -393,7 +393,7 @@ def generate_enhanced_annex_iv_html(
393393
<div class="card-header">
394394
<h2 class="card-title">4. Appropriateness of Performance Metrics</h2>
395395
</div>
396-
<p>The selected metrics provide a balanced assessment: Accuracy ({accuracy:.1%}) measures overall predictive capability, AUC ({model_metrics.get('auc_roc', 0):.3f}) assesses discrimination ability, and fairness metrics ensure consistent performance across demographic groups.</p>
396+
<p>The selected metrics provide a balanced assessment: Accuracy ({accuracy:.1%}) measures overall predictive capability, AUC ({model_metrics.get("auc_roc", 0):.3f}) assesses discrimination ability, and fairness metrics ensure consistent performance across demographic groups.</p>
397397
</div>
398398
399399
<!-- 5. Risk Management -->
@@ -403,19 +403,19 @@ def generate_enhanced_annex_iv_html(
403403
</div>
404404
<div class="metrics-container">
405405
<div class="metric-card">
406-
<div class="metric-value text-danger">{risk_data.get('overall', 0):.3f}</div>
406+
<div class="metric-value text-danger">{risk_data.get("overall", 0):.3f}</div>
407407
<div class="metric-label">Overall Risk</div>
408408
</div>
409409
<div class="metric-card">
410-
<div class="metric-value text-warning">{risk_data.get('technical', 0):.3f}</div>
410+
<div class="metric-value text-warning">{risk_data.get("technical", 0):.3f}</div>
411411
<div class="metric-label">Technical Risk</div>
412412
</div>
413413
<div class="metric-card">
414-
<div class="metric-value">{risk_data.get('operational', 0):.3f}</div>
414+
<div class="metric-value">{risk_data.get("operational", 0):.3f}</div>
415415
<div class="metric-label">Operational Risk</div>
416416
</div>
417417
<div class="metric-card">
418-
<div class="metric-value text-success">{risk_data.get('compliance', 0):.3f}</div>
418+
<div class="metric-value text-success">{risk_data.get("compliance", 0):.3f}</div>
419419
<div class="metric-label">Compliance Risk</div>
420420
</div>
421421
</div>
@@ -561,10 +561,10 @@ def generate_previous_versions_table(pipeline_runs: list) -> str:
561561
)
562562
html += f"""
563563
<tr>
564-
<td>{run.get('name', 'Unknown')}</td>
565-
<td><span class="monospace">{run.get('id', 'Unknown')[:8]}</span></td>
566-
<td>{run.get('created', 'Unknown')}</td>
567-
<td><span class="status-indicator {status_class}"></span> {run.get('status', 'Unknown')}</td>
564+
<td>{run.get("name", "Unknown")}</td>
565+
<td><span class="monospace">{run.get("id", "Unknown")[:8]}</span></td>
566+
<td>{run.get("created", "Unknown")}</td>
567+
<td><span class="status-indicator {status_class}"></span> {run.get("status", "Unknown")}</td>
568568
</tr>
569569
"""
570570

@@ -780,10 +780,10 @@ def generate_stack_components_table(stack_components: Dict[str, Any]) -> str:
780780
for component in components:
781781
html += f"""
782782
<tr>
783-
<td>{component_type.replace('_', ' ').title()}</td>
784-
<td>{component.get('name', 'Unknown')}</td>
785-
<td>{component.get('flavor', 'Unknown')}</td>
786-
<td>{component.get('integration', 'Built-in')}</td>
783+
<td>{component_type.replace("_", " ").title()}</td>
784+
<td>{component.get("name", "Unknown")}</td>
785+
<td>{component.get("flavor", "Unknown")}</td>
786+
<td>{component.get("integration", "Built-in")}</td>
787787
</tr>
788788
"""
789789

@@ -877,11 +877,11 @@ def generate_deployment_info_section(deployment_info: Dict[str, Any]) -> str:
877877
<div class="info-label">Deployment Status:</div>
878878
<div class="info-value">{status_indicator}</div>
879879
<div class="info-label">Environment:</div>
880-
<div class="info-value">{deployment_info.get('environment', 'Unknown')}</div>
880+
<div class="info-value">{deployment_info.get("environment", "Unknown")}</div>
881881
<div class="info-label">API Endpoint:</div>
882-
<div class="info-value">{deployment_info.get('api_url', 'Not Available')}</div>
882+
<div class="info-value">{deployment_info.get("api_url", "Not Available")}</div>
883883
<div class="info-label">Deployment Time:</div>
884-
<div class="info-value">{deployment_info.get('deployment_time', 'Unknown')}</div>
884+
<div class="info-value">{deployment_info.get("deployment_time", "Unknown")}</div>
885885
</div>
886886
</div>
887887
"""
@@ -914,7 +914,7 @@ def generate_fairness_table(fairness_metrics: Dict[str, Any]) -> str:
914914

915915
html += f"""
916916
<tr>
917-
<td>{attr.replace('_', ' ').title()}</td>
917+
<td>{attr.replace("_", " ").title()}</td>
918918
<td>{di_ratio:.3f}</td>
919919
<td>{status_indicator}</td>
920920
</tr>

credit-scorer/src/steps/training/risk_assessment.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def generate_risk_visualization(risk_scores: Dict, run_id: str) -> HTMLString:
190190
</div>
191191
192192
<div class="timestamp">
193-
Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}
193+
Generated on {datetime.now().strftime("%Y-%m-%d %H:%M:%S UTC")}
194194
</div>
195195
</div>
196196
"""
@@ -210,13 +210,13 @@ def generate_hazards_html(hazards: List[Dict]) -> str:
210210

211211
html += f"""
212212
<div class="hazard-item {hazard_class}">
213-
<div class="hazard-id">{hazard.get('id', 'UNKNOWN')}</div>
213+
<div class="hazard-id">{hazard.get("id", "UNKNOWN")}</div>
214214
<div class="badge {badge_class}">
215215
{severity.upper()}
216216
</div>
217-
<div class="hazard-description">{hazard.get('description', 'No description available')}</div>
217+
<div class="hazard-description">{hazard.get("description", "No description available")}</div>
218218
<div class="hazard-mitigation">
219-
<strong>Mitigation:</strong> {hazard.get('mitigation', 'No mitigation specified')}
219+
<strong>Mitigation:</strong> {hazard.get("mitigation", "No mitigation specified")}
220220
</div>
221221
</div>
222222
"""

0 commit comments

Comments
 (0)