{
  "steward": "python-config-steward",
  "project": "Azure-AI-RAG-CSharp-Semantic-Kernel-Functions",
  "runDate": "2026-03-22",
  "runId": "2026-03-22T00-00-00",
  "findings": [
    {
      "id": "PYCFG-ENVVAR-001",
      "title": "No startup validation of required environment variables",
      "severity": "notable",
      "category": "ENVVAR",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 34,
      "description": "All required environment variables are read inside the per-invocation blob trigger handler. Missing configuration is caught by the top-level except Exception block and swallowed, causing blobs to be silently dropped with no indication of a configuration problem. There is no fail-fast path.",
      "recommendation": "Add a module-level validation block at import time that checks all required variables before the function is registered. Raise a descriptive EnvironmentError listing every missing variable so the worker fails to start and misconfiguration is immediately visible.",
      "status": "open"
    },
    {
      "id": "PYCFG-ENVVAR-002",
      "title": "Bare environ[] subscript without error handling for required variables",
      "severity": "notable",
      "category": "ENVVAR",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 57,
      "description": "environ[\"AZURE_AI_SEARCH_ENDPOINT\"] and environ[\"AZURE_AI_SEARCH_INDEX\"] use bare subscript access (lines 57-58). If either variable is absent the runtime raises a KeyError that is caught by the generic except Exception and logged without context, providing no guidance on the missing variable.",
      "recommendation": "Replace bare subscript access with validated reads, either via a startup check (PYCFG-ENVVAR-001) or with explicit guard clauses that raise descriptive errors identifying the missing variable and its purpose.",
      "status": "open"
    },
    {
      "id": "PYCFG-ENVVAR-003",
      "title": "environ.get() returns None silently for required OpenAI parameters",
      "severity": "notable",
      "category": "ENVVAR",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 73,
      "description": "environ.get(\"AZURE_OPENAI_EMBEDDING\"), environ.get(\"AZURE_OPENAI_API_VERSION\"), and environ.get(\"AZURE_OPENAI_ENDPOINT\") return None when the variables are absent. These None values are passed directly to the AzureOpenAIEmbeddings constructor, which will fail during the first embedding call with an SDK-level exception that does not identify the missing variable.",
      "recommendation": "Validate all required variables at startup (PYCFG-ENVVAR-001) or use guarded access patterns. Never pass None silently to SDK constructors for required parameters.",
      "status": "open"
    },
    {
      "id": "PYCFG-ENVVAR-004",
      "title": "Runtime mutation of os.environ is a concurrency hazard",
      "severity": "notable",
      "category": "ENVVAR",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 49,
      "description": "OPENAI_API_TYPE, OPENAI_API_KEY, and AZURE_OPENAI_AD_TOKEN are written to os.environ inside the per-invocation handler (lines 49, 52, 54). Azure Functions Python workers may handle concurrent invocations in the same process, and os.environ is process-global. Concurrent writes create a race condition where one invocation may use the token fetched by another.",
      "recommendation": "Pass the credential token directly to the LangChain client via its constructor parameters (azure_ad_token, api_key) rather than routing it through os.environ, eliminating process-global state mutation.",
      "status": "open"
    },
    {
      "id": "PYCFG-LOCAL-001",
      "title": "No local.settings.json.example provided",
      "severity": "notable",
      "category": "LOCAL",
      "file": "src/DocumentLoaderFunction/",
      "description": "There is no local.settings.json.example, .env.example, or equivalent template committed to the repository. A developer cloning the repo has no machine-readable guide to set up their local environment for the DocumentLoaderFunction.",
      "recommendation": "Commit a local.settings.json.example to src/DocumentLoaderFunction/ listing all required Values keys with placeholder values and inline comments. Ensure this file is not excluded by .gitignore.",
      "status": "open"
    },
    {
      "id": "PYCFG-SECRET-001",
      "title": "Key Vault scaffolding present but unused",
      "severity": "minor",
      "category": "SECRET",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "description": "azure-keyvault-secrets is listed in requirements.txt and KeyVaultUri is provisioned as an empty string in loader-function.bicep, but no Key Vault client is instantiated or used in function_app.py. This scaffolding implies planned integration that was never completed, adding a dead dependency to the deployment.",
      "recommendation": "Either complete the Key Vault integration by reading KeyVaultUri and using SecretClient to retrieve sensitive values, or remove the dead dependency from requirements.txt and the empty KeyVaultUri setting from loader-function.bicep.",
      "status": "open"
    },
    {
      "id": "PYCFG-ENVVAR-005",
      "title": "Dead AZURE_STORAGE_URL environment variable — Bicep/code mismatch",
      "severity": "minor",
      "category": "ENVVAR",
      "file": "infra/app/loader-function.bicep",
      "line": 55,
      "description": "Bicep provisions AZURE_STORAGE_URL with the blob service URI, but the Python code reads BlobTriggerConnection__blobServiceUri instead. AZURE_STORAGE_URL is set in the function app environment but never consumed by any Python code, creating a dead configuration entry.",
      "recommendation": "Remove the AZURE_STORAGE_URL app setting from loader-function.bicep. The function already correctly reads BlobTriggerConnection__blobServiceUri.",
      "status": "open"
    },
    {
      "id": "PYCFG-LOGGING-001",
      "title": "Full document content logged at INFO level in production",
      "severity": "minor",
      "category": "LOGGING",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 68,
      "description": "logging.info(f\"Blob content as JSON: {json_data}\") logs the full parsed document payload at INFO level on every invocation. In production with Application Insights, this may log large structured payloads, increasing ingestion cost and potentially exposing document content in telemetry.",
      "recommendation": "Downgrade to logging.debug or log only a document identifier (blob name and reference code) at INFO. Gate full content logging on LOG_LEVEL=DEBUG.",
      "status": "open"
    },
    {
      "id": "PYCFG-ENVVAR-006",
      "title": "No environment-aware log level configuration",
      "severity": "minor",
      "category": "ENVVAR",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "description": "The function uses logging.info and logging.error uniformly with no mechanism to adjust verbosity between local development and production. No LOG_LEVEL or equivalent variable is read or documented.",
      "recommendation": "Read a LOG_LEVEL environment variable at startup and apply it via logging.basicConfig(level=...). Document it in local.settings.json.example. Set DEBUG locally and WARNING or INFO in production Bicep.",
      "status": "open"
    },
    {
      "id": "PYCFG-SECRET-002",
      "title": "AZURE_OPENAI_API_KEY parameter creates latent plaintext key fallback path",
      "severity": "info",
      "category": "SECRET",
      "file": "src/DocumentLoaderFunction/function_app.py",
      "line": 76,
      "description": "api_key=environ.get(\"AZURE_OPENAI_API_KEY\") is passed to AzureOpenAIEmbeddings. While this variable is not provisioned in the standard Bicep deployment, its presence means a plaintext API key would be accepted if placed in this variable, creating a latent fallback path alongside the Managed Identity token flow.",
      "recommendation": "Remove the api_key parameter from the AzureOpenAIEmbeddings constructor when Managed Identity is the intended authentication path, eliminating the plaintext key fallback.",
      "status": "open"
    },
    {
      "id": "PYCFG-INFRA-001",
      "title": "Managed Identity authentication correctly implemented for all Azure services",
      "severity": "info",
      "category": "INFRA",
      "file": "infra/app/loader-function.bicep",
      "description": "DefaultAzureCredential is used for all Azure service authentication (AI Search, Blob Storage). The Bicep provisions a User-Assigned Managed Identity and configures all BlobTriggerConnection and AzureWebJobsStorage settings with managedidentity credential type. No connection strings or API keys are stored for Azure service authentication.",
      "status": "open"
    }
  ],
  "summary": {
    "critical": 0,
    "notable": 4,
    "minor": 4,
    "info": 2,
    "total": 10
  }
}
