{
  "steward": "infra-security-steward",
  "project": "Azure-AI-RAG-CSharp-Semantic-Kernel-Functions",
  "runDate": "2026-03-22",
  "runId": "2026-03-22T00-00-00",
  "findings": [
    {
      "id": "ISEC-PUBLIC-001",
      "title": "Storage account has public network access explicitly enabled",
      "severity": "critical",
      "category": "PUBLIC",
      "file": "infra/core/storage/blob-storage-account.bicep",
      "line": 15,
      "description": "The storage account sets publicNetworkAccess: 'Enabled' with no IP restrictions or VNet rules. Any internet client can reach the storage endpoint, exposing blob, queue, and table services.",
      "recommendation": "Set publicNetworkAccess: 'Disabled' on the storage account and use private endpoints or VNet service endpoints for compute access.",
      "status": "open"
    },
    {
      "id": "ISEC-PUBLIC-002",
      "title": "Storage account missing allowBlobPublicAccess: false; three containers lack publicAccess: 'None'",
      "severity": "critical",
      "category": "PUBLIC",
      "file": "infra/core/storage/blob-storage-account.bicep",
      "line": 13,
      "description": "The allowBlobPublicAccess property is absent from the storage account, defaulting to enabled on older API configurations. Additionally, the load, completed, and images containers are created without a publicAccess: 'None' setting; only the archive container explicitly restricts public access.",
      "recommendation": "Add allowBlobPublicAccess: false to the storage account properties block. Add properties: { publicAccess: 'None' } to the load, completed, and images container resources.",
      "status": "open"
    },
    {
      "id": "ISEC-HTTPS-001",
      "title": "App Services and Function App missing httpsOnly: true",
      "severity": "critical",
      "category": "HTTPS",
      "file": "infra/app/api-app.bicep",
      "description": "None of the three Microsoft.Web/sites resources (API App Service, React Web App, Function App) set httpsOnly: true in their properties block. Without this setting, Azure App Service defaults to accepting HTTP traffic in addition to HTTPS, exposing credentials and data in transit.",
      "recommendation": "Add httpsOnly: true to the properties block of all three Microsoft.Web/sites resources: api-app.bicep, web-app.bicep, and loader-function.bicep.",
      "status": "open"
    },
    {
      "id": "ISEC-KV-001",
      "title": "No Key Vault provisioned; secrets have no managed secret store",
      "severity": "critical",
      "category": "KV",
      "file": "infra/core/security/key-vault.bicep",
      "description": "The key-vault.bicep file is empty (1 line). No Key Vault resource is deployed anywhere in the infrastructure. The API App Service has a CosmosDb_ConnectionString app setting with an empty placeholder value, and the Function App has a KeyVaultUri app setting also left empty, indicating Key Vault was planned but not implemented. Without a Key Vault, secrets cannot be stored securely, soft-deleted, purge-protected, or audited.",
      "recommendation": "Implement key-vault.bicep with enableSoftDelete: true, enablePurgeProtection: true, enableRbacAuthorization: true, and network access restrictions. Replace the CosmosDb_ConnectionString plain-text app setting with a Key Vault secret reference. Populate KeyVaultUri with the provisioned vault URI.",
      "status": "open"
    },
    {
      "id": "ISEC-PUBLIC-003",
      "title": "Azure OpenAI has public network access enabled with no IP restrictions",
      "severity": "notable",
      "category": "PUBLIC",
      "file": "infra/core/ai/openai/openai-account.bicep",
      "line": 28,
      "description": "The Azure OpenAI Cognitive Services account sets publicNetworkAccess: 'Enabled' with no networkAcls or IP rules. Any internet client can reach the OpenAI endpoint and attempt authentication.",
      "recommendation": "Set publicNetworkAccess: 'Disabled' and configure a private endpoint, or add networkAcls with allowedVirtualNetworkRules and ipRules to restrict access to known networks.",
      "status": "open"
    },
    {
      "id": "ISEC-PUBLIC-004",
      "title": "Azure AI Search public network access defaults to enabled with no IP rules",
      "severity": "notable",
      "category": "PUBLIC",
      "file": "infra/core/search/search-services.bicep",
      "line": 30,
      "description": "The search service parameter publicNetworkAccess defaults to 'enabled' and no IP rules are passed from main.bicep. The networkRuleSet parameter is populated with bypass: 'None' and an empty ipRules array, meaning all internet traffic is accepted.",
      "recommendation": "Set publicNetworkAccess to 'disabled' in the search service and configure a private endpoint, or pass explicit IP allowlist rules via networkRuleSet.ipRules from main.bicep.",
      "status": "open"
    },
    {
      "id": "ISEC-PUBLIC-005",
      "title": "Cosmos DB has no publicNetworkAccess restriction configured",
      "severity": "notable",
      "category": "PUBLIC",
      "file": "infra/core/database/cosmos-db/account.bicep",
      "line": 20,
      "description": "The Cosmos DB databaseAccounts resource does not set the publicNetworkAccess property, defaulting to public access. The database contains chat history and product data that should not be accessible from the public internet.",
      "recommendation": "Add publicNetworkAccess: 'Disabled' to the Cosmos DB account properties and use a private endpoint for application access.",
      "status": "open"
    },
    {
      "id": "ISEC-AUTH-001",
      "title": "Cosmos DB key-based authentication not disabled (disableKeyBasedAuth hardcoded to false)",
      "severity": "notable",
      "category": "AUTH",
      "file": "infra/core/database/cosmos-db/nosql/account.bicep",
      "line": 25,
      "description": "The nosql/account.bicep wrapper hardcodes disableKeyBasedAuth: false when calling the underlying account module, overriding the caller's disableKeyBasedAuth parameter. This means Cosmos DB master keys are always enabled regardless of the parameter passed at call site, preventing the enforcement of managed identity-only access.",
      "recommendation": "Replace the hardcoded disableKeyBasedAuth: false with disableKeyBasedAuth: disableKeyBasedAuth to honour the caller's intent. Set the default parameter value to true to enforce managed identity-only access by default.",
      "status": "open"
    },
    {
      "id": "ISEC-RBAC-001",
      "title": "Storage account has redundant and over-permissioned RBAC roles (Blob Data Owner + Blob Data Contributor + Account Contributor)",
      "severity": "notable",
      "category": "RBAC",
      "file": "infra/core/storage/blob-storage-account.bicep",
      "line": 55,
      "description": "Three overlapping roles are assigned to the same managed identity on the storage account: Storage Blob Data Contributor (ba92f5b4), Storage Blob Data Owner (b7e6dc6d), and Storage Account Contributor (17d1049b). Storage Blob Data Owner fully supersedes Storage Blob Data Contributor. Storage Account Contributor is a management-plane ARM role that grants the ability to list and rotate storage keys — unnecessary when managed identity handles all data access.",
      "recommendation": "Remove the Storage Blob Data Contributor role assignment (superseded by Owner). Remove the Storage Account Contributor role assignment (management plane not required). Keep Storage Blob Data Owner, Storage Queue Data Contributor, and Storage Table Data Contributor.",
      "status": "open"
    },
    {
      "id": "ISEC-RBAC-002",
      "title": "Search Service Contributor grants management-plane access beyond what RAG operations require",
      "severity": "notable",
      "category": "RBAC",
      "file": "infra/core/search/search-services.bicep",
      "line": 69,
      "description": "The Search Service Contributor role (7ca78c08) grants management-plane permissions including creating/deleting indexes, modifying service configuration, and managing API keys. The RAG workload only needs to ingest data and run queries, which is covered by Search Index Data Contributor and Search Index Data Reader.",
      "recommendation": "Remove the Search Service Contributor role assignment. Retain Search Index Data Contributor and Search Index Data Reader for the managed identity.",
      "status": "open"
    },
    {
      "id": "ISEC-DIAG-001",
      "title": "Storage, AI Search, OpenAI, Cosmos DB, and React web app have no diagnostic settings",
      "severity": "notable",
      "category": "DIAG",
      "file": "infra/core/storage/blob-storage-account.bicep",
      "description": "Five resources process or serve sensitive data but have no Microsoft.Insights/diagnosticSettings configured: the storage account, Azure AI Search, Azure OpenAI, Cosmos DB, and the React web app. Without diagnostic settings, access attempts, errors, and security events from these resources are not forwarded to the Log Analytics workspace for monitoring and alerting.",
      "recommendation": "Add Microsoft.Insights/diagnosticSettings resources for each of these five services, forwarding relevant log categories (StorageRead/Write/Delete, AuditEvent, RequestResponse, DataPlaneRequests, etc.) and AllMetrics to the existing Log Analytics workspace.",
      "status": "open"
    },
    {
      "id": "ISEC-STORAGE-001",
      "title": "Storage account has allowSharedKeyAccess: true — shared keys enabled alongside managed identity",
      "severity": "notable",
      "category": "STORAGE",
      "file": "infra/core/storage/blob-storage-account.bicep",
      "line": 14,
      "description": "The storage account explicitly sets allowSharedKeyAccess: true, keeping storage account keys active. All current consumers (Function App, managed identity role assignments) use managed identity. Keeping shared key access active unnecessarily broadens the attack surface — any leaked key grants full data-plane access bypassing RBAC.",
      "recommendation": "Set allowSharedKeyAccess: false to disable key-based authentication. All existing consumers already use managed identity and will continue to function.",
      "status": "open"
    },
    {
      "id": "ISEC-COSMOSDB-001",
      "title": "Cosmos DB data-plane role assignment module is never called; managed identity has no Cosmos DB access",
      "severity": "notable",
      "category": "RBAC",
      "file": "infra/main.bicep",
      "description": "The file infra/core/security/data-plane-role-assignment.bicep exists and defines a Cosmos DB SQL role assignment, but it is never invoked from main.bicep or database.bicep. As a result, the managed identity has no Cosmos DB data-plane permissions. Combined with the empty CosmosDb_ConnectionString app setting, the API App Service has no viable path to access Cosmos DB at deployment time.",
      "recommendation": "Call the data-plane-role-assignment.bicep module from database.bicep or main.bicep, passing the managed identity's principalId and the custom role definition ID. Replace the empty CosmosDb_ConnectionString app setting with managed identity-based access configuration.",
      "status": "open"
    },
    {
      "id": "ISEC-TLS-001",
      "title": "Storage account missing explicit minimumTlsVersion: 'TLS1_2'",
      "severity": "minor",
      "category": "TLS",
      "file": "infra/core/storage/blob-storage-account.bicep",
      "line": 13,
      "description": "The storage account properties block does not set minimumTlsVersion. Depending on the storage account API version and Azure region defaults, this can result in TLS 1.0 or TLS 1.1 being accepted. Explicit declaration ensures the minimum is always enforced.",
      "recommendation": "Add minimumTlsVersion: 'TLS1_2' to the storage account properties block.",
      "status": "open"
    },
    {
      "id": "ISEC-TLS-002",
      "title": "Storage account missing explicit supportsHttpsTrafficOnly: true",
      "severity": "minor",
      "category": "TLS",
      "file": "infra/core/storage/blob-storage-account.bicep",
      "line": 13,
      "description": "The supportsHttpsTrafficOnly property is not declared. While ARM defaults this to true for new accounts, explicit declaration makes the security posture clear in code and prevents accidental regression if the default changes.",
      "recommendation": "Add supportsHttpsTrafficOnly: true to the storage account properties block.",
      "status": "open"
    },
    {
      "id": "ISEC-RBAC-003",
      "title": "Role assignments use deprecated API version 2020-04-01-preview",
      "severity": "minor",
      "category": "RBAC",
      "file": "infra/core/storage/blob-storage-account.bicep",
      "description": "All Microsoft.Authorization/roleAssignments resources across blob-storage-account.bicep, search-services.bicep, and openai-account.bicep use the deprecated API version 2020-04-01-preview. The current stable API version is 2022-04-01.",
      "recommendation": "Update all Microsoft.Authorization/roleAssignments@2020-04-01-preview references to Microsoft.Authorization/roleAssignments@2022-04-01.",
      "status": "open"
    },
    {
      "id": "ISEC-MI-001",
      "title": "User-assigned managed identity used consistently across all compute resources",
      "severity": "info",
      "category": "IDENTITY",
      "file": "infra/core/security/managed-identity.bicep",
      "description": "A single user-assigned managed identity is provisioned and attached to the API App Service, Function App, Azure AI Search, and Azure OpenAI. RBAC roles are assigned at individual resource scope rather than subscription-wide. This is a correct and secure pattern for service-to-service authentication.",
      "status": "open"
    },
    {
      "id": "ISEC-MI-002",
      "title": "Function App uses managed identity for AzureWebJobsStorage and BlobTriggerConnection — no storage connection strings in config",
      "severity": "info",
      "category": "IDENTITY",
      "file": "infra/app/loader-function.bicep",
      "description": "The Function App configures AzureWebJobsStorage__credential: 'managedidentity' and all BlobTriggerConnection__ settings use managed identity with the user-assigned clientId. No storage account connection strings are present in the app settings. This is a positive security pattern.",
      "status": "open"
    },
    {
      "id": "ISEC-DIAG-002",
      "title": "API App Service and Function App have comprehensive diagnostic settings forwarding to Log Analytics",
      "severity": "info",
      "category": "DIAG",
      "file": "infra/app/api-app.bicep",
      "description": "The API App Service forwards AppServiceHTTPLogs, AppServiceConsoleLogs, AppServiceAppLogs, and AllMetrics to the Log Analytics workspace. The Function App forwards FunctionAppLogs and AllMetrics. This is correct and provides operational and security visibility for the two primary compute resources.",
      "status": "open"
    }
  ],
  "summary": {
    "critical": 4,
    "notable": 9,
    "minor": 3,
    "info": 3,
    "total": 19
  }
}
