Skip to content

Harden SMS provider configuration#96

Open
jamilahmadzai wants to merge 2 commits into
BAWES-Universe:masterfrom
jamilahmadzai:security/sms-provider-hardening-55
Open

Harden SMS provider configuration#96
jamilahmadzai wants to merge 2 commits into
BAWES-Universe:masterfrom
jamilahmadzai:security/sms-provider-hardening-55

Conversation

@jamilahmadzai
Copy link
Copy Markdown

@jamilahmadzai jamilahmadzai commented May 16, 2026

/claim #55

Contributes to #55.

Scope

This is a narrow SMS provider credential and transport hardening slice. It removes the checked-in SMS provider username, password, sender, and hardcoded provider endpoint from SMSComponent, wires the component through runtime environment variables, fails closed when required provider settings are missing, and rejects non-HTTPS provider endpoints so credentials are not posted over plaintext HTTP.

It also documents the required variables and adds a static regression guard to prevent the SMS provider literals from being reintroduced.

Safety Boundary

No live SMS provider account, live AWS/IAM/S3 access, candidate data, or private production data was accessed. This PR does not include secret values.

Demo

Privacy-safe validation demo: https://github.com/jamilahmadzai/studenthub/releases/download/studenthub-55-sms-provider-demo-20260516/studenthub-55-sms-provider-demo.mp4

Verification

python3 scripts/check-sms-provider-hardening.py
# SMS provider hardening check passed.

python3 -m py_compile scripts/check-sms-provider-hardening.py

docker run --rm -v "$PWD":/app -w /app php:8.2-cli php -l common/components/SMSComponent.php
# No syntax errors detected in common/components/SMSComponent.php

docker run --rm -v "$PWD":/app -w /app php:8.2-cli php -l common/config/main.php
# No syntax errors detected in common/config/main.php

git diff --check

Full SMS delivery smoke testing was not run because real provider credentials and endpoint values are intentionally not present in the local environment.

Summary by CodeRabbit

  • Bug Fixes

    • Hardened SMS sending: requires HTTPS endpoint, validates credentials before sending, uses configured credentials, and adds a request timeout to avoid leaking secrets or hanging requests.
  • Documentation

    • Added setup docs describing required SMS provider environment variables and failure behavior when misconfigured.
  • Chores

    • Added a repository check script that enforces the SMS provider hardening rules.

Review Change Stack

Payout

PayPal: jamilurrehman722@gmail.com (if a direct payout method is needed).
Payment method: Algora bounty-platform payout to GitHub user @jamilahmadzai for issue #55.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

📝 Walkthrough

Walkthrough

SMSComponent configuration is hardened by converting hard-coded credentials to environment-driven public properties with validation. The main config wires environment variables, a static validation script prevents credential leaks, and documentation describes the required setup.

Changes

SMS Provider Configuration Hardening

Layer / File(s) Summary
SMS Component Properties and Validation
common/components/SMSComponent.php
SMSComponent adds public apiEndpoint, username, password, sender properties with init() validation enforcing non-empty strings and HTTPS-only endpoints. SMS payload uses instance properties instead of hard-coded values, and HTTP requests include a 10-second timeout.
Configuration Environment Variable Wiring
common/config/main.php
SMS component config sources endpoint, username, password, and sender from environment variables (SMS_PROVIDER_ENDPOINT, SMS_PROVIDER_USERNAME, SMS_PROVIDER_PASSWORD, SMS_PROVIDER_SENDER), defaulting to null when unset.
Static Hardening Validation Script
scripts/check-sms-provider-hardening.py
New Python validation script checks that SMSComponent does not hardcode provider URLs or parameters, enforces HTTPS validation and presence of explanatory text, verifies that config references required environment variables via getenv(...), and rejects committed literal credential values.
Environment Variable Documentation
docs/setup.md
Introduces "Runtime Service Credentials" section documenting required SMS_PROVIDER_* environment variables and the fail-closed behavior when values are missing or endpoint is not HTTPS.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Credentials tucked away, no secrets in the hay,
Env vars light the path where messages relay,
HTTPS insisted, checks in place so tight,
A hopping dev grins at a safer midnight flight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Harden SMS provider configuration' accurately and concisely describes the main objective of the pull request, which is to improve the security of SMS provider configuration by removing hardcoded credentials, enforcing environment variables, and rejecting non-HTTPS endpoints.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot
Copy link
Copy Markdown

dosubot Bot commented May 16, 2026

Related Knowledge

2 documents with suggested updates are ready for review.

BAWES Universe

DB, API endpoint, S3, Slack, and payment keys
View Suggested Changes
@@ -42,6 +42,14 @@
 MYFATOORAH_KUWAIT_TEST_API_KEY=your_kuwait_test_api_key
 MYFATOORAH_SAUDI_LIVE_API_KEY=your_saudi_live_api_key
 
+# --- SMS Provider Configuration ---
+# The SMS component requires all four credentials to function.
+# The provider endpoint MUST use HTTPS; HTTP endpoints are rejected.
+SMS_PROVIDER_ENDPOINT=https://your.sms.provider/api
+SMS_PROVIDER_USERNAME=your_sms_username
+SMS_PROVIDER_PASSWORD=your_sms_password
+SMS_PROVIDER_SENDER=YourSenderName
+
 # --- Optional: Other Configuration Keys ---
 # The following keys are present in main.php but are not required for most local setups.
 # Uncomment and set as needed.
@@ -59,7 +67,6 @@
 
 # Additional optional components:
 # ARMADA_DELIVERY_KEY=your_armada_delivery_key
-# SMS_COMPONENT_KEY=your_sms_component_key
 # FILE_GENERATOR_KEY=your_file_generator_key
 # MASHKOR_DELIVERY_KEY=your_mashkor_delivery_key
 # AUTH0_CLIENT_ID=your_auth0_client_id
@@ -68,6 +75,7 @@
 
 **Notes:**
 - Replace all `your_*` values with your actual credentials or secrets.
-- The S3, Slack, and payment keys are directly mapped from the structure in [`common/config/main.php`](https://github.com/BAWES-Universe/plugn/blob/bc485b0a1da61d516955c5dc4fc29e95afccea92/common/config/main.php#L3-L172).
+- The S3, Slack, payment, and SMS provider keys are directly mapped from the structure in [`common/config/main.php`](https://github.com/BAWES-Universe/plugn/blob/bc485b0a1da61d516955c5dc4fc29e95afccea92/common/config/main.php#L3-L172).
+- **SMS Provider:** All four SMS_PROVIDER_* variables are required. The component will fail to initialize if any are missing or empty. The endpoint must use HTTPS; non-HTTPS endpoints are rejected.
 - Database and API endpoint keys are placeholders, as they are not defined in `main.php` and may be set elsewhere in your project.
 - All other configuration keys in `main.php` are optional and can be set if your local environment requires them.

[Accept] [Edit] [Decline]

StudentHub – Complete Services & Infrastructure Map (Org-wide)”
View Suggested Changes
@@ -12,6 +12,7 @@
 | Mixpanel         | Analytics    | Tracks user events and analytics                                        | backend, admin portal, frontend portals       | [studenthub/docs/analytics.md](https://github.com/BAWES-Universe/studenthub/blob/ca90a5502040ba8191fe9f80d29d0b6a4d2a6152/docs/analytics.md#L3-L41) | MIXPANEL_KEY (configurable in admin)   | N/A                                       |
 | Segment          | Analytics    | Aggregates analytics events and forwards to destinations                | backend, admin portal, frontend portals       | [studenthub/docs/analytics.md](https://github.com/BAWES-Universe/studenthub/blob/ca90a5502040ba8191fe9f80d29d0b6a4d2a6152/docs/analytics.md#L3-L41) | SEGMENT_KEY (configurable in admin)    | N/A                                       |
 | Puppeteer        | Other        | Headless browser automation for backend tasks                           | backend                                       | [studenthub/docs/setup.md](https://github.com/BAWES-Universe/studenthub/blob/ca90a5502040ba8191fe9f80d29d0b6a4d2a6152/docs/setup.md#L5-L59)        | N/A                                   | N/A                                       |
+| SMS Provider     | Messaging    | Sends password-reset and notification SMS messages                      | backend                                       | [studenthub/common/components/SMSComponent.php](https://github.com/BAWES-Universe/studenthub/blob/main/common/components/SMSComponent.php), [studenthub/common/config/main.php](https://github.com/BAWES-Universe/studenthub/blob/main/common/config/main.php) | SMS_PROVIDER_ENDPOINT, SMS_PROVIDER_USERNAME, SMS_PROVIDER_PASSWORD, SMS_PROVIDER_SENDER | HTTPS endpoint (provider-specific)        |
 
 ## Hosting & Environments
 
@@ -27,5 +28,5 @@
 
 - No evidence was found for DNS/CDN/security services such as Cloudflare, Route53, or WAF. These may be configured outside of code, for example in AWS or Netlify dashboards. Check "AWS Route53 Hosted Zones" or "Netlify Site Settings → Domain Management" for DNS/CDN configuration.
 - No explicit deployment or hosting configuration was found for the candidate or company portals. Check "Netlify Site Settings", "Vercel Project Settings", or "AWS S3 Buckets" for these portals.
-- No evidence of authentication, email, SMS, payments, monitoring, maps, queue, media, or support services in code or configs. These may be configured in third-party dashboards or as environment variables not present in the repositories. Check "Environment Variable dashboards" in Netlify, Railway, or AWS, and review admin portal configuration screens for integrations.
+- No evidence of authentication, email, payments, monitoring, maps, queue, media, or support services in code or configs. These may be configured in third-party dashboards or as environment variables not present in the repositories. Check "Environment Variable dashboards" in Netlify, Railway, or AWS, and review admin portal configuration screens for integrations.
 - Some environment variables and secrets are referenced only by name (e.g., in CircleCI contexts or Netlify build configs) and their actual values or usage may be managed in CI/CD or hosting dashboards. Check "CircleCI Contexts → org-global", "Netlify Site Settings → Environment Variables", and "Railway Variables" for full lists and values.

[Accept] [Edit] [Decline]

How did I do? Any feedback?  Join Discord

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
scripts/check-sms-provider-hardening.py (1)

19-20: 💤 Low value

Consider adding file existence checks for clearer error messages.

If the target files are missing, read_text() will raise a FileNotFoundError with a traceback. Adding explicit existence checks would provide clearer error messages when the repository structure is unexpected.

♻️ Proposed improvement
+if not SMS_COMPONENT.exists():
+    fail(f"SMSComponent not found at {SMS_COMPONENT}")
+if not MAIN_CONFIG.exists():
+    fail(f"Configuration file not found at {MAIN_CONFIG}")
+
 component = SMS_COMPONENT.read_text()
 config = MAIN_CONFIG.read_text()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-sms-provider-hardening.py` around lines 19 - 20, The current
calls to SMS_COMPONENT.read_text() and MAIN_CONFIG.read_text() can raise
FileNotFoundError with a traceback; add explicit existence checks using
SMS_COMPONENT.exists() and MAIN_CONFIG.exists() before calling read_text(), and
if either is missing raise or log a clear, contextual error (e.g., raise
FileNotFoundError or call sys.exit with a message like "Missing required file:
<symbol> — expected at repository root") so the script fails with a concise,
actionable message referencing the missing SMS_COMPONENT or MAIN_CONFIG.
docs/setup.md (1)

65-65: 💤 Low value

Consider more general phrasing for component usage.

The documentation refers to "password-reset SMS component," but the component is named SMSComponent and could potentially be used for other SMS purposes beyond password resets. Unless the component is exclusively used for password resets, consider more general phrasing like "SMS component."

📝 Suggested rewording
-The password-reset SMS component reads provider settings from environment variables. Do not commit provider usernames, passwords, sender accounts, or private provider URLs.
+The SMS component reads provider settings from environment variables. Do not commit provider usernames, passwords, sender accounts, or private provider URLs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/setup.md` at line 65, The docs currently call this the "password-reset
SMS component" but the implementation is named SMSComponent and can be used for
other SMS purposes; update the wording to a more general term (e.g., "SMS
component" or "SMSComponent") throughout the sentence/section to avoid implying
exclusivity, and ensure any examples or warnings about environment variables
still refer to SMSComponent so readers can map docs to the code.
common/components/SMSComponent.php (1)

45-52: ⚡ Quick win

Consider trimming validated properties to avoid confusing error messages.

The validation checks that trim($this->$attribute) === '', but doesn't actually trim the value. If an environment variable contains leading or trailing whitespace (e.g., SMS_PROVIDER_ENDPOINT=" https://example.com "), it will pass the empty check but fail the HTTPS check at line 54 with "must use HTTPS", even though the real issue is whitespace, not the lack of HTTPS.

♻️ Proposed fix to trim validated values
     foreach (['apiEndpoint', 'username', 'password', 'sender'] as $attribute) {
         if (!is_string($this->$attribute) || trim($this->$attribute) === '') {
             throw new InvalidConfigException(strtr('"{class}::{attribute}" cannot be empty.', [
                 '{class}' => static::class,
                 '{attribute}' => '$' . $attribute
             ]));
         }
+        $this->$attribute = trim($this->$attribute);
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/components/SMSComponent.php` around lines 45 - 52, Validation
currently checks trim($this->$attribute) but doesn't modify the property,
causing later checks (e.g. HTTPS check) to see untrimmed values; inside the
foreach in SMSComponent that iterates
['apiEndpoint','username','password','sender'], trim and reassign each string
property (e.g. $this->$attribute = trim((string)$this->$attribute)) before the
empty check and then proceed to throw InvalidConfigException as before so
subsequent validations operate on the trimmed values.
common/config/main.php (1)

11-12: Consider extending credential hardening to other components.

While this PR correctly hardens the SMS provider configuration, several other components in this file contain hardcoded credentials and API keys:

  • temporaryBucketResourceManager: AWS key and secret (lines 11-12)
  • reCaptcha: secret key (line 31)
  • jira: API token (line 47)
  • algolia: API key (line 52)
  • ipstack: access key (line 58)
  • cloudinaryManager: API key and secret (lines 63-64)
  • slack: webhook URL (line 77)

These credentials should follow the same environment-variable pattern used for the SMS provider to prevent credential leaks in version control.

Also applies to: 31-31, 47-47, 52-52, 58-58, 63-64, 77-77

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@common/config/main.php` around lines 11 - 12, Replace all hardcoded
credentials in this config with environment-driven values like the SMS provider
does: for temporaryBucketResourceManager (replace 'key' and 'secret' values),
reCaptcha (replace 'secretKey'), jira (replace 'apiToken'), algolia (replace
'apiKey'), ipstack (replace 'accessKey'), cloudinaryManager (replace API
'api_key' and 'api_secret'), and slack (replace 'webhookUrl') — use the same env
lookup pattern/function used for the SMS config to read each secret from process
environment (or your config helper) and provide sensible default/failure
behavior where appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@common/components/SMSComponent.php`:
- Around line 45-52: Validation currently checks trim($this->$attribute) but
doesn't modify the property, causing later checks (e.g. HTTPS check) to see
untrimmed values; inside the foreach in SMSComponent that iterates
['apiEndpoint','username','password','sender'], trim and reassign each string
property (e.g. $this->$attribute = trim((string)$this->$attribute)) before the
empty check and then proceed to throw InvalidConfigException as before so
subsequent validations operate on the trimmed values.

In `@common/config/main.php`:
- Around line 11-12: Replace all hardcoded credentials in this config with
environment-driven values like the SMS provider does: for
temporaryBucketResourceManager (replace 'key' and 'secret' values), reCaptcha
(replace 'secretKey'), jira (replace 'apiToken'), algolia (replace 'apiKey'),
ipstack (replace 'accessKey'), cloudinaryManager (replace API 'api_key' and
'api_secret'), and slack (replace 'webhookUrl') — use the same env lookup
pattern/function used for the SMS config to read each secret from process
environment (or your config helper) and provide sensible default/failure
behavior where appropriate.

In `@docs/setup.md`:
- Line 65: The docs currently call this the "password-reset SMS component" but
the implementation is named SMSComponent and can be used for other SMS purposes;
update the wording to a more general term (e.g., "SMS component" or
"SMSComponent") throughout the sentence/section to avoid implying exclusivity,
and ensure any examples or warnings about environment variables still refer to
SMSComponent so readers can map docs to the code.

In `@scripts/check-sms-provider-hardening.py`:
- Around line 19-20: The current calls to SMS_COMPONENT.read_text() and
MAIN_CONFIG.read_text() can raise FileNotFoundError with a traceback; add
explicit existence checks using SMS_COMPONENT.exists() and MAIN_CONFIG.exists()
before calling read_text(), and if either is missing raise or log a clear,
contextual error (e.g., raise FileNotFoundError or call sys.exit with a message
like "Missing required file: <symbol> — expected at repository root") so the
script fails with a concise, actionable message referencing the missing
SMS_COMPONENT or MAIN_CONFIG.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 88c9cbf8-552b-435a-9618-d97ba688a598

📥 Commits

Reviewing files that changed from the base of the PR and between 7b023ff and 7227e32.

📒 Files selected for processing (4)
  • common/components/SMSComponent.php
  • common/config/main.php
  • docs/setup.md
  • scripts/check-sms-provider-hardening.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant