Skip to content

🛡️ Sentinel: [HIGH] Remove insecure secret retrieval from environment variables#958

Open
anchapin wants to merge 1 commit intomainfrom
jules-5917544077159129643-00cb535f
Open

🛡️ Sentinel: [HIGH] Remove insecure secret retrieval from environment variables#958
anchapin wants to merge 1 commit intomainfrom
jules-5917544077159129643-00cb535f

Conversation

@anchapin
Copy link
Copy Markdown
Owner

@anchapin anchapin commented Apr 5, 2026

🚨 Severity: HIGH
💡 Vulnerability: Hardcoded environment variables don't support dynamic rotation, centralized security management (AWS Secrets Manager, Vault, etc), and potentially risk exposure if .env files are accidentally committed or logged.
🎯 Impact: Using core.secrets.get_secret() allows transparent migration to secure vaults.
🔧 Fix: Replaced os.getenv with get_secret from core.secrets when accessing secrets like API keys or credentials.
✅ Verification: Ran pytest backend tests to ensure no regressions.


PR created automatically by Jules for task 5917544077159129643 started by @anchapin

Summary by Sourcery

Replace direct environment variable access for external service credentials with centralized secret retrieval to improve security.

Enhancements:

  • Use core.secrets.get_secret for CurseForge API key configuration instead of reading from environment variables directly.
  • Use core.secrets.get_secret for SendGrid API key retrieval in the email service to align with centralized secret management.

Documentation:

  • Document the new security practice of using centralized secret retrieval instead of direct environment variable access in Sentinel security notes.

… variables

- Update CurseForge API Key Retrieval to use `get_secret` from `core.secrets`
- Update SendGrid API Key Retrieval to use `get_secret` from `core.secrets`
- Updated sentinel journal

Co-authored-by: anchapin <6326294+anchapin@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings April 5, 2026 23:22
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 5, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Replaces direct environment variable access for API keys with a centralized secret retrieval helper and documents the associated security pattern in the Sentinel guide.

Sequence diagram for secure secret retrieval in services

sequenceDiagram
    participant App
    participant CurseForgeService
    participant EmailService
    participant CoreSecrets as core_secrets
    participant Env as environment

    App->>CurseForgeService: import module
    CurseForgeService->>CoreSecrets: get_secret(CURSEFORGE_API_KEY, "")
    CoreSecrets-->>CurseForgeService: curseforge_api_key

    App->>EmailService: get_email_service(api_key=None)
    EmailService->>CoreSecrets: get_secret(SENDGRID_API_KEY)
    CoreSecrets-->>EmailService: sendgrid_api_key
    EmailService-->>App: EmailService_instance
Loading

File-Level Changes

Change Details Files
Use centralized secret retrieval helper instead of os.getenv for external service API keys.
  • Import get_secret from core.secrets in CurseForge and email services.
  • Replace CURSEFORGE_API_KEY initialization to use get_secret with a default empty string.
  • Change SendGrid API key resolution to use get_secret instead of os.getenv while preserving the existing override via function argument.
backend/src/services/curseforge_service.py
backend/src/services/email_service.py
Document the new secret management guidance in Sentinel security documentation.
  • Add a new Sentinel entry describing the vulnerability of retrieving secrets via os.getenv.
  • Explain security benefits of centralized secrets management and the required use of core.secrets.get_secret for secrets.
.jules/sentinel.md

Possibly linked issues

  • #P2: PR replaces os.getenv with core.secrets.get_secret, aligning with the issue’s requirement to remove hardcoded secrets.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • Loading CURSEFORGE_API_KEY via get_secret at module import time makes rotation and test overrides harder; consider resolving the secret in a factory or on-demand within CurseForgeService methods instead of as a module-level constant.
  • For both CURSEFORGE_API_KEY and SENDGRID_API_KEY, consider failing fast (e.g., raising a clear exception) when the secret is missing instead of silently defaulting to an empty or falsy value to avoid misconfigurations going unnoticed.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Loading `CURSEFORGE_API_KEY` via `get_secret` at module import time makes rotation and test overrides harder; consider resolving the secret in a factory or on-demand within `CurseForgeService` methods instead of as a module-level constant.
- For both `CURSEFORGE_API_KEY` and `SENDGRID_API_KEY`, consider failing fast (e.g., raising a clear exception) when the secret is missing instead of silently defaulting to an empty or falsy value to avoid misconfigurations going unnoticed.

## Individual Comments

### Comment 1
<location path="backend/src/services/email_service.py" line_range="287" />
<code_context>
         import os

-        api_key = api_key or os.getenv("SENDGRID_API_KEY")
+        api_key = api_key or get_secret("SENDGRID_API_KEY")

         if not api_key:
</code_context>
<issue_to_address>
**issue (bug_risk):** Validate that get_secret’s behavior on missing SENDGRID_API_KEY still allows the log-only fallback path.

Previously, a missing `SENDGRID_API_KEY` resulted in `None` and fell through to the `if not api_key:` block, triggering the log-only behavior. With `get_secret("SENDGRID_API_KEY")`, if the secret is missing and it either raises or returns a truthy sentinel, we may skip that fallback and fail earlier. Please confirm that `get_secret` returns a falsy value (e.g., `None`) when the secret is absent, or catch/handle its exceptions here to preserve the existing log-only behavior.
</issue_to_address>

### Comment 2
<location path=".jules/sentinel.md" line_range="43" />
<code_context>
+
+## 2025-04-05 - Remove insecure secret retrieval from environment variables
+**Vulnerability:** Retrieving API keys directly from `os.getenv` instead of using the centralized secrets management solution.
+**Learning:** Hardcoded environment variables don't support dynamic rotation, centralized security management (AWS Secrets Manager, Vault, etc), and potentially risk exposure if `.env` files are accidentally committed or logged. Using `core.secrets.get_secret()` allows transparent migration to secure vaults.
+**Prevention:** Always use `get_secret` from `core.secrets` when accessing secrets like API keys or credentials.
</code_context>
<issue_to_address>
**issue (typo):** Add a period after "etc" inside the parentheses for correct punctuation.

Change `etc)` to `etc.)` so the abbreviation is correctly punctuated within the parentheses.

```suggestion
**Learning:** Hardcoded environment variables don't support dynamic rotation, centralized security management (AWS Secrets Manager, Vault, etc.), and potentially risk exposure if `.env` files are accidentally committed or logged. Using `core.secrets.get_secret()` allows transparent migration to secure vaults.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

import os

api_key = api_key or os.getenv("SENDGRID_API_KEY")
api_key = api_key or get_secret("SENDGRID_API_KEY")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Validate that get_secret’s behavior on missing SENDGRID_API_KEY still allows the log-only fallback path.

Previously, a missing SENDGRID_API_KEY resulted in None and fell through to the if not api_key: block, triggering the log-only behavior. With get_secret("SENDGRID_API_KEY"), if the secret is missing and it either raises or returns a truthy sentinel, we may skip that fallback and fail earlier. Please confirm that get_secret returns a falsy value (e.g., None) when the secret is absent, or catch/handle its exceptions here to preserve the existing log-only behavior.


## 2025-04-05 - Remove insecure secret retrieval from environment variables
**Vulnerability:** Retrieving API keys directly from `os.getenv` instead of using the centralized secrets management solution.
**Learning:** Hardcoded environment variables don't support dynamic rotation, centralized security management (AWS Secrets Manager, Vault, etc), and potentially risk exposure if `.env` files are accidentally committed or logged. Using `core.secrets.get_secret()` allows transparent migration to secure vaults.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (typo): Add a period after "etc" inside the parentheses for correct punctuation.

Change etc) to etc.) so the abbreviation is correctly punctuated within the parentheses.

Suggested change
**Learning:** Hardcoded environment variables don't support dynamic rotation, centralized security management (AWS Secrets Manager, Vault, etc), and potentially risk exposure if `.env` files are accidentally committed or logged. Using `core.secrets.get_secret()` allows transparent migration to secure vaults.
**Learning:** Hardcoded environment variables don't support dynamic rotation, centralized security management (AWS Secrets Manager, Vault, etc.), and potentially risk exposure if `.env` files are accidentally committed or logged. Using `core.secrets.get_secret()` allows transparent migration to secure vaults.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to route sensitive configuration (API keys) through the centralized secrets manager (core.secrets.get_secret) rather than direct os.getenv access, supporting future migration to vault-backed secret backends.

Changes:

  • Updated SendGrid API key lookup in the email service to use get_secret("SENDGRID_API_KEY").
  • Updated CurseForge API key lookup to use get_secret("CURSEFORGE_API_KEY", "").
  • Added a Sentinel knowledge-base entry documenting the pattern change.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
backend/src/services/email_service.py Switches SendGrid API key retrieval to core.secrets.get_secret.
backend/src/services/curseforge_service.py Switches CurseForge API key retrieval to core.secrets.get_secret.
.jules/sentinel.md Documents the change as a Sentinel security learning/prevention entry.
Comments suppressed due to low confidence (1)

backend/src/services/email_service.py:293

  • The warning says emails will be "logged only", but the code assigns a dummy API key and still constructs a SendGridEmailService. If sendgrid is installed, this will initialize a real SendGrid client and attempt network sends (likely failing with 401), which is inconsistent with the intended behavior. Consider using a dedicated logging/no-op email service when the key is missing, or keep the service in logging-only mode instead of setting a dummy key.
        if not api_key:
            logger.warning("SendGrid API key not configured. Emails will be logged only.")
            api_key = "dummy-key-for-development"

        _email_service = SendGridEmailService(api_key=api_key, from_email=from_email)

Comment on lines 285 to 286
import os

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

import os inside get_email_service() is now unused after switching to get_secret(). Removing it will avoid lint/unused-import failures and keeps the function minimal.

Suggested change
import os

Copilot uses AI. Check for mistakes.
Comment on lines 8 to +12
import os
import httpx
from typing import Optional, Dict, Any
import logging
from core.secrets import get_secret
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

import os is unused in this module after replacing os.getenv() with get_secret() for the API key. Please remove it to prevent unused-import lint issues.

Copilot uses AI. Check for mistakes.
Comment on lines 16 to 20
# CurseForge API configuration
CURSEFORGE_API_BASE_URL = "https://api.curseforge.com/v1"
# Note: Requires API key in production - get from https://console.curseforge.com/
CURSEFORGE_API_KEY = os.getenv("CURSEFORGE_API_KEY", "")
CURSEFORGE_API_KEY = get_secret("CURSEFORGE_API_KEY", "")

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

CURSEFORGE_API_KEY is now retrieved at import time via get_secret(). In core/secrets.py, backend initialization (e.g., Vault/Doppler) can raise before the internal try/except in SecretsManager.get_secret(), which would cause this module import to fail even though the CurseForge key is optional. Consider deferring secret lookup to CurseForgeService.__init__ (or first use), or updating SecretsManager.get_secret() to fail-soft during backend initialization when a default is provided.

Copilot uses AI. Check for mistakes.
Comment on lines 16 to 20
# CurseForge API configuration
CURSEFORGE_API_BASE_URL = "https://api.curseforge.com/v1"
# Note: Requires API key in production - get from https://console.curseforge.com/
CURSEFORGE_API_KEY = os.getenv("CURSEFORGE_API_KEY", "")
CURSEFORGE_API_KEY = get_secret("CURSEFORGE_API_KEY", "")

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

PR description says secrets access was migrated from os.getenv to core.secrets.get_secret for API keys/credentials, but there are still API credentials being read directly from os.getenv (e.g. MODRINTH_TOKEN in backend/src/services/modrinth_service.py). Either expand this change to cover the remaining secret lookups, or adjust the PR description/sentinel entry to match the narrower scope.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +44
## 2025-04-05 - Remove insecure secret retrieval from environment variables
**Vulnerability:** Retrieving API keys directly from `os.getenv` instead of using the centralized secrets management solution.
**Learning:** Hardcoded environment variables don't support dynamic rotation, centralized security management (AWS Secrets Manager, Vault, etc), and potentially risk exposure if `.env` files are accidentally committed or logged. Using `core.secrets.get_secret()` allows transparent migration to secure vaults.
**Prevention:** Always use `get_secret` from `core.secrets` when accessing secrets like API keys or credentials.
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

The wording "Hardcoded environment variables" is misleading—environment variables aren’t hardcoded in code. Consider rephrasing to something like "Retrieving secrets directly via os.getenv" / "direct environment variable access" to keep the vulnerability description accurate.

Suggested change
## 2025-04-05 - Remove insecure secret retrieval from environment variables
**Vulnerability:** Retrieving API keys directly from `os.getenv` instead of using the centralized secrets management solution.
**Learning:** Hardcoded environment variables don't support dynamic rotation, centralized security management (AWS Secrets Manager, Vault, etc), and potentially risk exposure if `.env` files are accidentally committed or logged. Using `core.secrets.get_secret()` allows transparent migration to secure vaults.
**Prevention:** Always use `get_secret` from `core.secrets` when accessing secrets like API keys or credentials.
## 2025-04-05 - Remove insecure direct environment secret retrieval
**Vulnerability:** Retrieving API keys directly from `os.getenv` instead of using the centralized secrets management solution.
**Learning:** Direct environment variable access for secrets via `os.getenv` does not provide centralized rotation, auditing, or secret management (AWS Secrets Manager, Vault, etc.), and still risks exposure if `.env` files are accidentally committed or logged. Using `core.secrets.get_secret()` allows transparent migration to secure vaults.
**Prevention:** Always use `get_secret` from `core.secrets` when accessing secrets like API keys or credentials, rather than retrieving them directly from environment variables.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants