Skip to content

Harden Cloudinary credential config#98

Open
haradahinata wants to merge 1 commit into
BAWES-Universe:masterfrom
haradahinata:codex/cloudinary-config-hardening-55
Open

Harden Cloudinary credential config#98
haradahinata wants to merge 1 commit into
BAWES-Universe:masterfrom
haradahinata:codex/cloudinary-config-hardening-55

Conversation

@haradahinata
Copy link
Copy Markdown

@haradahinata haradahinata commented May 17, 2026

/claim #55

Contributes to #55.

Scope

This is a narrow Cloudinary credential hardening slice. It removes the checked-in Cloudinary cloud name, API key, and API secret from common/config/main.php, wires the component through runtime environment variables, and makes CloudinaryManager fail closed before upload, delete, or asset lookup calls when any required Cloudinary setting is missing.

This intentionally does not touch AWS/IAM/S3, Civil ID backend/UI flows, SQS, MediaConvert, SES/mailers, Xero, OneSignal, Google/Jira/Algolia/IP/Slack slices already covered by other PRs, live Cloudinary access, candidate data, or real secret values.

Demo

Privacy-safe static demo GIF: https://github.com/haradahinata/studenthub/blob/codex/cloudinary-config-hardening-55/docs/demo/studenthub-55-cloudinary-hardening-demo.gif?raw=1

Verification

python scripts/check-cloudinary-hardening.py
# Cloudinary hardening check passed.

python -m py_compile scripts/check-cloudinary-hardening.py

git diff --check

php -l and Docker-based PHP linting could not be run in this Windows environment because neither php nor docker is installed. The PHP change is limited to env-backed config wiring and a fail-closed guard in CloudinaryManager; the regression script checks the security-sensitive behavior directly.

Runtime variables

CLOUDINARY_CLOUD_NAME
CLOUDINARY_API_KEY
CLOUDINARY_API_SECRET

Summary by CodeRabbit

Release Notes

  • New Features

    • Cloudinary credentials can now be configured via environment variables (CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET) at runtime.
    • Enhanced validation ensures missing credentials are properly detected before API operations.
  • Documentation

    • Updated setup guide with instructions for configuring Cloudinary credentials via environment variables.
  • Chores

    • Added configuration validation script to verify Cloudinary setup requirements.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

Cloudinary credentials are hardened by moving them from hardcoded values to environment variables, gating all API operations behind a configuration assertion in CloudinaryManager, and enforcing the pattern via a CI validation script that checks code, config, and docs consistency.

Changes

Cloudinary Credential Hardening

Layer / File(s) Summary
CloudinaryManager credential gating and assertion
common/components/CloudinaryManager.php
CloudinaryManager normalizes cloud_name, api_key, and api_secret from configuration by trimming and converting empty values to null. A new $configured flag tracks readiness. New helpers normalizeConfigValue() and assertConfigured() were added; all three public API methods (upload(), delete(), getUrl()) now call assertConfigured() before performing Cloudinary operations, throwing InvalidConfigException if credentials are missing. Unused imports were removed and yii\base\InvalidConfigException was explicitly imported.
Environment-based configuration
common/config/main.php
cloudinaryManager component now sources cloud_name, api_key, and api_secret from CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET environment variables respectively, defaulting to null when unset.
Hardening validation script
scripts/check-cloudinary-hardening.py
New CI-style validation script ensures Cloudinary hardening is correctly implemented: verifies environment variables are documented in setup docs and referenced in config, prevents inline credential literals in the configuration array, confirms CloudinaryManager includes normalization and assertion helpers, validates that all three API methods contain $this->assertConfigured() calls, and rejects lowercase API class instantiation (e.g., new uploadApi()).
Setup documentation
docs/setup.md
New "Cloudinary Runtime Configuration" section documents configuring credentials via environment variables instead of repository commits, notes that missing variables prevent API operations, and references running the hardening validation script as a pre-submission step.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐇 Credentials tucked in env's safe keep,
No secrets hardcoded, no digital creep,
Each API call checks if ready it be,
Validation scripts guard, configuration-free,
Cloudinary's journey: from plain to secure! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Harden Cloudinary credential config' clearly and concisely summarizes the main change: moving Cloudinary credentials from hardcoded config to environment variables with validation guards.
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.

✏️ 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 17, 2026

Related Knowledge

3 documents with suggested updates are ready for review.

BAWES Universe

plugn backend – local setup
View Suggested Changes
@@ -62,7 +62,7 @@
   - TapPayments: `plugnLiveApiKey`, `plugnTestApiKey`, `destinationId`
   - MyFatoorah: `kuwaitLiveApiKey`, `kuwaitTestApiKey`, `saudiLiveApiKey`
 - **Other Keys**:  
-  - Cloudinary: `cloud_name`, `api_key`, `api_secret`
+  - Cloudinary: Configured via environment variables `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET` (set these in your `.env` file or local environment; `main.php` reads from these variables)
   - ApplePay, Ipstack, reCaptcha, Auth0, Zapier, JWT, Google Maps, Netlify, Github, OneSignal, currencylayer
 
 See [`main.php`](https://github.com/BAWES-Universe/plugn/blob/bc485b0a1da61d516955c5dc4fc29e95afccea92/common/config/main.php#L3-L172) for full details.

[Accept] [Edit] [Decline]

Plugn Integrations
View Suggested Changes
@@ -117,15 +117,15 @@
 ```php
 'cloudinaryManager' => [
     'class' => 'common\components\CloudinaryManager',
-    'cloud_name' => 'plugn',
-    'api_key' => '...',
-    'api_secret' => '...',
+    'cloud_name' => getenv('CLOUDINARY_CLOUD_NAME') ?: null,
+    'api_key' => getenv('CLOUDINARY_API_KEY') ?: null,
+    'api_secret' => getenv('CLOUDINARY_API_SECRET') ?: null,
 ],
 ```
 [Source](https://github.com/BAWES-Universe/plugn/blob/bc485b0a1da61d516955c5dc4fc29e95afccea92/common/config/main.php#L3-L172)
 
 **Local Development:**  
-OPTIONAL. Required for media management features, but can be stubbed or omitted for local development if not needed.
+REQUIRED. `CloudinaryManager` implements fail-closed behavior: when any of the three environment variables (`CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET`) is missing, upload/delete/asset lookup operations will throw an exception. For local development, either set valid test credentials or avoid features that depend on Cloudinary.
 
 ---
 
@@ -144,7 +144,9 @@
 
 ## Local-only Minimal Config
 
-To run the backend locally, only core dependencies are required (such as MySQL, Redis, PHP, etc.). All external integrations listed above (AWS S3, Slack webhooks, TapPayments, MyFatoorah, Cloudinary) can be stubbed or omitted for local development. You do not need to provide real API keys or connect to external services unless you are specifically developing or testing those features.
+To run the backend locally, only core dependencies are required (such as MySQL, Redis, PHP, etc.). Most external integrations listed above (AWS S3, Slack webhooks, TapPayments, MyFatoorah) can be stubbed or omitted for local development. You do not need to provide real API keys or connect to external services unless you are specifically developing or testing those features.
+
+**Exception: Cloudinary** cannot be stubbed with empty strings. `CloudinaryManager` implements fail-closed behavior and will throw an exception when environment variables are missing. Either set valid test credentials or avoid Cloudinary-dependent features during local development.
 
 **Example minimal config for local development:**
 
@@ -152,9 +154,10 @@
 // In plugn/common/config/main.php, you can stub integrations like this:
 'cloudinaryManager' => [
     'class' => 'common\components\CloudinaryManager',
-    'cloud_name' => '',
-    'api_key' => '',
-    'api_secret' => '',
+    'cloud_name' => getenv('CLOUDINARY_CLOUD_NAME') ?: null,
+    'api_key' => getenv('CLOUDINARY_API_KEY') ?: null,
+    'api_secret' => getenv('CLOUDINARY_API_SECRET') ?: null,
+    // Do not use empty strings; operations will fail if credentials are missing
 ],
 'temporaryBucketResourceManager' => [
     'class' => 'common\components\S3ResourceManager',
@@ -185,4 +188,4 @@
 // ...other stubs as needed
 ```
 
-No external integration is strictly required to boot and run the backend locally, unless you are actively developing or testing features that depend on them.
+Most external integrations are not strictly required to boot and run the backend locally, unless you are actively developing or testing features that depend on them.

[Accept] [Edit] [Decline]

Production Operations & Deployments Runbook
View Suggested Changes
@@ -39,7 +39,12 @@
 ### Backend (Railway)
 
 - **Build & Deploy**: Uses Railway CLI. Set `RAILWAY_DOCKERFILE_PATH` to `./Dockerfile-nginx-dev-railway` (dev) or `./Dockerfile-nginx-railway` (prod). Use `railway login`, `railway link`, and `railway up` to deploy. [Reference](https://github.com/BAWES-Universe/studenthub/blob/ca90a5502040ba8191fe9f80d29d0b6a4d2a6152/railway/railway.md#L1-L34)
-- **Required Env Vars**: Set via Railway dashboard or `.env` files (not in repo).
+- **Required Env Vars**: Set via Railway dashboard or `.env` files (not in repo). Must include:
+  - `CLOUDINARY_CLOUD_NAME`: Cloudinary cloud name for image uploads
+  - `CLOUDINARY_API_KEY`: Cloudinary API key
+  - `CLOUDINARY_API_SECRET`: Cloudinary API secret
+  
+  CloudinaryManager will throw exceptions on upload/delete/asset lookup operations if any of these variables are missing.
 - **Database Connectivity**: 
   - Dev: MySQL (host=mysql, db=studenthub, user=studenthubuser, pass=12345)
   - Prod: AWS RDS (host=studenthub-prod.cluster-c8mekjvvbygf.eu-west-2.rds.amazonaws.com, db=studenthub, user=bawes, pass=bawes12student!hub)
@@ -131,6 +136,7 @@
 ## Day-1 Ops Checklist
 
 - Verify Railway deployments for dev/prod: run `railway link`, confirm `RAILWAY_DOCKERFILE_PATH`, inspect AWS RDS/Redis connections in `environments/prod/common/config/main-local.php`.
+- Set Cloudinary environment variables via Railway dashboard: `CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET`. Cloudinary upload/delete/asset lookup operations will fail if any of these are missing.
 - Confirm CircleCI contexts: ensure `org-global` context holds AWS credentials for admin/staff S3/CloudFront buckets as listed in `.circleci/config.yml`.
 - Check Sentry project (`dsn ...@sentry.io/168200`) for alerting rules tied to `environment` tags and ensure Slack logger targets the correct workspace.
 - Review cron job logs under `~/logs` and trigger key jobs manually (e.g., `php ~/www/yii cron/daily`) after verifying cache flush steps from README.

[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 (1)
scripts/check-cloudinary-hardening.py (1)

60-69: ⚡ Quick win

Consider making the method body regex more robust.

The regex pattern on line 62 requires a newline before the closing brace and uses non-greedy matching, which could fail with different code formatting (e.g., single-line methods or varied whitespace). Consider using a more flexible pattern.

♻️ Proposed fix for more robust regex
     method_match = re.search(
-        rf"public function {method}\([^)]*\)\s*\{{(.*?)\n\s*\}}",
+        rf"public function {method}\([^)]*\)\s*\{{(.*?)\}}",
         manager,
         flags=re.S,
     )

Or for even more robustness, match balanced braces:

     method_match = re.search(
-        rf"public function {method}\([^)]*\)\s*\{{(.*?)\n\s*\}}",
+        rf"public function {method}\([^)]*\)\s*\{{([^}}]*\$this->assertConfigured\(\);[^}}]*)\}}",
         manager,
         flags=re.S,
     )
🤖 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-cloudinary-hardening.py` around lines 60 - 69, The current
regex that searches for the method bodies of CloudinaryManager::upload,
::delete, and ::getUrl is fragile because it requires a newline before the
closing brace and can fail for single-line or differently formatted methods;
update the search so it does not require a newline and accepts any
whitespace/newline between the body and the closing brace (or alternatively
implement a small brace-aware extractor to find the method body), ensure to keep
re.S so dot matches newlines, and then check the extracted body for the
"$this->assertConfigured();" call for the methods named upload, delete, and
getUrl.
🤖 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 `@scripts/check-cloudinary-hardening.py`:
- Around line 60-69: The current regex that searches for the method bodies of
CloudinaryManager::upload, ::delete, and ::getUrl is fragile because it requires
a newline before the closing brace and can fail for single-line or differently
formatted methods; update the search so it does not require a newline and
accepts any whitespace/newline between the body and the closing brace (or
alternatively implement a small brace-aware extractor to find the method body),
ensure to keep re.S so dot matches newlines, and then check the extracted body
for the "$this->assertConfigured();" call for the methods named upload, delete,
and getUrl.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f534344b-c811-42d7-9d0e-fb1941cd614b

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • docs/demo/studenthub-55-cloudinary-hardening-demo.gif is excluded by !**/*.gif
📒 Files selected for processing (4)
  • common/components/CloudinaryManager.php
  • common/config/main.php
  • docs/setup.md
  • scripts/check-cloudinary-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