The Shift to CI/CD Supply Chain Attacks
In our recent red teaming engagements, I observed a significant pivot in attacker methodology. Adversaries are no longer just targeting production environments; they are moving upstream to the CI/CD pipeline. By compromising an Azure DevOps (ADO) pipeline, an attacker can inject malicious code directly into the software supply chain, bypassing traditional perimeter defenses. In the Indian context, where many FinTechs and ITES firms are migrating to self-hosted agents on local infrastructure like Nxtra or E2E Networks to meet RBI data localization norms, this creates a "Shadow CI/CD" risk. These local agents often lag behind in patching compared to Microsoft-hosted counterparts.
Securing the pipeline requires moving beyond simple "secret masking." We need a multi-layered approach that includes environment hardening, strict access control, and, most importantly, real-time telemetry exported to a SIEM. Without centralized logging, a compromised service connection or a rogue pipeline modification can go undetected for months. This technical guide focuses on hardening the ADO environment and establishing a robust monitoring framework using Azure Sentinel and KQL.
Hardening Azure DevOps Pipeline Environment Security
The first line of defense is the environment where your code is built and deployed. We frequently see organizations using "Project Collection Administrators" for routine pipeline tasks, which violates the principle of least privilege. You must isolate your pipeline execution environments to prevent lateral movement. If an attacker gains execution capabilities on a build agent, they will immediately attempt to scrape the local filesystem for .git/config files, environment variables, and cached service principal tokens.
For self-hosted agents, especially those running on-premises or in local Indian data centers, I recommend using ephemeral agents. By running agents in a Kubernetes cluster (AKS) and using the Horizontal Runner Autoscaler (HRA), you ensure that every build starts with a clean slate. To monitor these ephemeral environments, we use the following command to track agent lifecycle events:
kubectl get events -n azure-devops-agents --sort-by='.lastTimestamp'
Restricting Agent Pool and Queue Access
Agent pools should never be shared across different security boundaries (e.g., Production vs. Staging). I have seen multiple instances where a vulnerability in a Dev pipeline allowed an attacker to access a shared agent pool and pivot into Production. You must restrict agent pool permissions at the project level. Use the Azure DevOps CLI to audit who has administrative access to your pools:
az devops security group membership list --id [Group-Descriptor-ID] --org https://dev.azure.com/{OrgName} --query '[].{User:agent.displayName, Email:agent.mailAddress}'
By identifying over-privileged users, we can reduce the blast radius of a credential compromise. Ensure that only the "Build Service" identity has "Use" permissions on the specific queues required for the build.
Managing Secrets: Secure Variables and Files
One of the most common findings in our audits is the presence of plaintext secrets in pipeline logs. While Azure DevOps attempts to mask variables marked as "secret," this mechanism is easily bypassed. For instance, if a secret is base64 encoded or transformed during the build process, the masker will fail to recognize it, leading to CWE-522 (Insufficiently Protected Credentials), a risk often highlighted in the OWASP Top 10. We must move away from locally stored pipeline variables and toward centralized secret management.
Integrating Azure Key Vault for Enhanced Secret Management
Instead of storing sensitive strings in the ADO UI, we link an Azure Key Vault to a Variable Group. This ensures that secrets are only fetched at runtime and are never stored within the ADO metadata database. This is particularly critical given CVE-2024-20667, which highlighted how unauthorized access to pipeline metadata could lead to information disclosure. By using Key Vault, we ensure that even if the ADO project is compromised, the attacker still needs separate permissions to access the vault.
When using the "Download Secure File" task for certificates or SSH keys, ensure the "Delete after run" option is checked. To further harden your infrastructure, ensure you have a strategy for secure SSH access for teams that doesn't rely on static keys left on build agents. The process should look like this in your YAML:
- task: DownloadSecureFile@1name: caCertificate inputs: secureFile: 'internal-ca.crt'
- script: |
echo "Using certificate located at $(caCertificate.secureFilePath)" # Perform operations displayName: 'Utilize Secure Certificate'
Automating Security with Scanning and Advanced Tools
Static Analysis (SAST) and dependency scanning must be non-negotiable gates in your CI/CD flow. Azure DevOps Advanced Security provides native capabilities for secret scanning and dependency analysis. However, for teams requiring more granular control or integration with local Indian compliance standards, third-party tools are often necessary. We integrate these tools directly into the YAML definition to fail the build if high-severity vulnerabilities are detected.
Running an Azure DevOps Pipeline Security Scan
I recommend using the Terrascan or Checkov tasks for Infrastructure as Code (IaC) scanning if you are deploying to Azure via the pipeline. This prevents misconfigured Network Security Groups (NSGs) or publicly accessible S3/Blob storage from being provisioned. To verify the integrity of the ADO web interface itself, we sometimes perform a quick check of the SSL/TLS configuration to ensure no man-in-the-middle (MITM) attacks are possible during the build trigger phase:
openssl s_client -connect dev.azure.com:443 -showcerts | openssl x509 -text -noout
This command allows us to verify the certificate chain and ensure that the connection to the ADO service is not being intercepted by an unauthorized proxy, a common concern in strictly monitored corporate networks in India.
Implementing Real-time SIEM Monitoring for CI/CD Logs
The most critical gap in pipeline security is the lack of visibility. If an attacker modifies a pipeline to exfiltrate data, how long would it take for your SOC to notice? For Indian enterprises, the CERT-In directive (April 2022) mandates a 180-day log retention period. Standard ADO audit logs in the UI are insufficient for this. We must stream AuditLogs to a Log Analytics Workspace or a SIEM like Microsoft Sentinel.
Configuring Diagnostic Settings via CLI
We use the Azure CLI to automate the creation of diagnostic settings. This ensures that every new ADO organization or project is automatically onboarded to our monitoring framework. The following command enables the streaming of AuditLogs to a centralized workspace:
az monitor diagnostic-settings create --name 'DevOps-to-SIEM' \
--resource '/subscriptions/{SubID}/resourceGroups/{RG}/providers/Microsoft.VisualStudio/account/{OrgName}' \ --logs '[{"category": "AuditLogs","enabled": true}]' \ --workspace '{LogAnalyticsWorkspaceID}'
KQL for Detecting Pipeline Tampering
Once the logs are in Sentinel, we deploy KQL (Kusto Query Language) rules to detect suspicious activity. One specific pattern we look for is the modification of Variable Groups or Service Endpoints by users who do not typically perform administrative tasks. Attackers often modify these to point to their own malicious servers or to inject their own credentials.
# Note: While this is KQL, we use the python class for syntax highlighting compatibility
AzureDevOpsAuditing | where OperationName contains "VariableGroup" or OperationName contains "ServiceEndpoint" | extend Actor = ActorUPN, Action = OperationName, IP = IpAddress | where Data contains "Password" or Data contains "Secret" or Data contains "Token" | project TimeGenerated, Actor, IP, Action, Data | sort by TimeGenerated desc
This query identifies any changes to sensitive configurations. If an actor from an unexpected IP address (e.g., outside of your corporate VPN range or India-based IP blocks) modifies a secret, an incident is automatically triggered in the SIEM.
Addressing Vulnerabilities: CVE-2023-21553 and CVE-2024-20667
We must also stay ahead of platform-specific vulnerabilities. CVE-2023-21553 was a critical Remote Code Execution (RCE) vulnerability in Azure DevOps Server, as documented in the NIST NVD. While the cloud service is patched by Microsoft, many Indian firms still run the on-premises "Server" version. If you are running an unpatched instance, an attacker can execute code with high privileges via the network.
CVE-2024-20667 is more subtle, involving information disclosure. It allows an attacker to gain unauthorized access to pipeline metadata. In my testing, I found that this metadata often contains clues about the internal network architecture, private IP addresses of build agents, and even the names of internal databases. Regular auditing of the "AuditLogs" table in your SIEM will show if any unauthorized service principals are querying the ADO API for this metadata.
Applying the Principle of Least Privilege to Service Connections
Service Connections are the "keys to the kingdom." They allow the pipeline to interact with Azure, AWS, or Kubernetes. I frequently see service connections with "Owner" or "Contributor" access at the Subscription level. This is a recipe for disaster. Instead, use Azure RBAC to grant the Service Principal only the "Contributor" role on a specific Resource Group. We use the following command to verify the scope of a service principal used in ADO:
az role assignment list --assignee [Service-Principal-ID] --output table
If the scope is /subscriptions/{SubID}, it must be restricted immediately. Ideally, use Workload Identity Federation to eliminate the need for long-lived client secrets altogether.
Securing YAML Pipelines vs. Classic Pipelines
From a security perspective, YAML pipelines are vastly superior to Classic pipelines. YAML allows for "Pipeline as Code," meaning every change to the build process is captured in git, subject to peer review, and audited. Classic pipelines are "black boxes" where a user with UI access can change a build step without any version control. If you are still using Classic pipelines, you are creating a massive blind spot.
In YAML, we can implement "Required Templates." This ensures that every pipeline in the organization must include a standard security header, which might include steps for dependency scanning and credential checking. By enforcing these templates, you ensure that developers cannot "opt-out" of security scans to speed up their builds.
Implementing Approval Gates and Checkpoints
For deployments to production environments, automated tests are not enough. We implement "Environment Checks" in Azure DevOps. This requires a manual approval or a successful health check from an external API before the deployment can proceed. For Indian financial institutions, this maps directly to the "Dual Control" requirements often cited in regulatory audits. You can configure these in the "Environments" section of ADO, ensuring that no single individual can push code from a laptop to a production server in Mumbai without a second pair of eyes.
Compliance with DPDP Act 2023 and Indian Regulations
The Digital Personal Data Protection (DPDP) Act 2023 introduces strict requirements for data processing and protection. If your CI/CD pipelines handle PII—for example, during automated testing against a masked production database—you must ensure that the logs do not leak this data. SIEM integration is vital here; we use KQL to scan logs for patterns matching Indian Aadhaar numbers or PAN cards to ensure developers are not accidentally logging sensitive data during the build process.
Furthermore, the 180-day retention mandate from CERT-In means that simply having the logs is not enough; they must be immutable and searchable. By exporting ADO logs to Azure Data Explorer or a dedicated Log Analytics Workspace with a long retention policy, you satisfy both the technical and legal requirements of operating in the Indian market.
Building a Culture of DevSecOps in Azure
Technical controls are only effective if the team understands the "why" behind them. We have found that the most successful security implementations involve "Security Champions" within the DevOps teams. These individuals are trained to recognize common CI/CD pitfalls, such as detecting malicious VS Code extensions that could compromise the local workstation or hardcoding secrets in azure-pipelines.yml. Every third-party task you add to your pipeline is a potential entry point for a supply chain attack.
I recommend auditing your installed extensions regularly. Many extensions request broad permissions to read your source code or manage your builds. If an extension is not from a "Verified Publisher," it should be treated with extreme caution. Use the following CLI approach to list all installed extensions and review their permissions:
az devops extension list --org https://dev.azure.com/{OrgName}
Future-Proofing Your Pipeline Security Strategy
The landscape of CI/CD security is evolving toward "Zero Trust Pipelines." This involves moving away from long-lived credentials and toward short-lived, identity-based access. By implementing Workload Identity Federation between Azure DevOps and Azure, you can remove the need for secrets in your service connections entirely. The pipeline authenticates using an OIDC token, which is only valid for the duration of the build.
As we continue to monitor the threat landscape, the integration of AI-driven anomaly detection in SIEMs will become the standard. By training models on your baseline pipeline behavior—such as typical build times, common committers, and standard deployment windows—you can detect the subtle deviations that signify a sophisticated supply chain compromise.
Next Command: Auditing Service Principal Scopes
To begin hardening your environment today, run the following command to identify every service principal that has access to your Azure subscription and check for over-privileged roles that could be exploited via a compromised pipeline:
az ad sp list --filter "displayName eq 'AzureDevOps'" --query '[].{Name:displayName, ID:appId}'