Security Review a Bicep MR on GitLab

Review and harden a Bicep IaC template by fixing 6 security flaws on GitLab.

Introduction

30 Second Summary

In the real world, a single misconfigured infrastructure template can expose sensitive data or grant attackers access to an entire cloud environment. Code reviews are the last line of defense before those mistakes reach production.

In this project, you will play the role of a security reviewer on a team. You will examine a teammate's Bicep infrastructure template submitted via a GitLab merge request, identify 6 deliberate security vulnerabilities, and push a hardened fix.

What You'll Build

A complete merge request review workflow on GitLab where you catch and fix security flaws in a Bicep template that deploys Azure resources including a storage account, Key Vault, and web app.

By the end of this project, you'll have:

  • A GitLab project with two branches and a deliberately insecure Bicep template submitted as a merge request.
  • A completed merge request review with inline comments identifying 6 security vulnerabilities using GitLab's batch review feature.
  • A security-hardened Bicep template using Microsoft-recommended patterns like @secure() decorators, managed identities, and least-privilege RBAC.
  • Secret Mission: Add network-level access restrictions to the storage account using virtual network rules.

Are there any prerequisites?

This project assumes basic familiarity with Bicep syntax and Azure resource concepts. You do not need an Azure subscription because we focus entirely on the code review workflow without deploying any resources.

Set Up GitLab and Your Local Environment

Before you can review any code, you need the tools that make the review workflow possible.

In this step, you will set up three things: Git for version control, a GitLab account where your merge request will live, and the Bicep extension so VS Code can help you catch syntax issues as you write.

In this step, get ready to:
  • Confirm Git is installed on your machine.
  • Create a free GitLab.com account.
  • Install the Bicep VS Code extension for syntax support.
Confirm Git is installed

Git is the version control tool that lets you create branches, commit changes, and push code to GitLab. Most macOS machines already have it installed via Xcode Command Line Tools.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Check if Git is already installed by running this command:
git --version

✔️ I see a version number

You should see something like git version 2.39.5 (your exact number may differ).

Git is ready to go. Skip ahead to the next substep.

ⓧ Command not found

No problem. You can install Git through the method below for your operating system.

macOS

  • Install Git via Xcode Command Line Tools by running this command:
xcode-select --install
  • A popup will appear asking you to install the tools. Click Install and wait for the download to finish (this may take a few minutes).
  • Once the installation completes, verify Git is now available by running:
git --version

You should now see a version number like git version 2.39.5.

Windows

  • Download the Git installer from git-scm.com/download/win.
  • Run the installer and accept the default settings for each prompt.
  • Close and reopen Command Prompt after the installation finishes.
  • Verify Git is now available by running:
git --version

You should now see a version number like git version 2.47.0.

Still not seeing a version number?

On macOS, make sure the Xcode Command Line Tools installation dialog completed successfully. If it was interrupted, run xcode-select --install again.

On Windows, make sure you closed and reopened Command Prompt after installation. Git won't be available in sessions that were open during the install.

Help me troubleshoot my Git installation

Create a free GitLab.com account

GitLab is the platform where your merge request review will happen. The Free tier includes full merge request review features with no credit card required.

  • Open https://gitlab.com/users/sign_up in your browser.
  • Fill in the First name field.
  • Fill in the Last name field.
  • Choose a Username (this will appear in your project URLs).
  • Enter your Email address.
  • Create a Password.
  • Click Register to create your account.
  • Check your email inbox for a confirmation link from GitLab and click it to verify your account.

Why GitLab?

GitLab's merge request review feature lets you batch multiple comments together and submit them all at once. This is the standard workflow on professional teams where reviewers leave all their feedback in a single pass rather than sending a notification for every individual comment.

After confirming your email, you should see your GitLab dashboard. This is your home base for projects and merge requests.

Don't see the confirmation email?

Check your spam or junk folder. GitLab confirmation emails sometimes land there.

If you still cannot find it, click Resend confirmation email on the GitLab sign-in page.

Help me get into my GitLab account

Install the Bicep VS Code extension

The Bicep extension gives you syntax highlighting, error detection, and autocomplete as you edit Bicep templates. This will help you spot issues in the insecure template and validate your fixes.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • Open the Extensions panel by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows).
  • Type Bicep into the search field at the top of the Extensions panel.
  • Find the extension published by Microsoft (extension ID: ms-azuretools.vscode-bicep).
  • Click Install.

Once installed, you should see a green checkmark or "Installed" label next to the Bicep extension in the panel.

Can't find the Bicep extension?

Make sure you are searching in the Extensions panel (the sidebar with the square icon), not in the file explorer. The extension name is just Bicep and the publisher should say Microsoft.

If you see multiple results, look for the one with the extension ID ms-azuretools.vscode-bicep shown in its details.

Help me find and install the Bicep extension

Your environment is ready. Next up, you will create a GitLab project and add the insecure Bicep template that your "teammate" submitted for review.

Create the GitLab Project and Initial Template

Your local environment is ready with Git, VS Code, and the Bicep extension. Now you need a place to host the code your "teammate" will submit for review.

In this step, you will create a GitLab project, clone it to your machine, and push a minimal placeholder file that establishes the main branch.

In this step, get ready to:
  • Create a private GitLab project with a README.
  • Clone the repository to your local machine.
  • Push an initial Bicep file to the main branch.
Create a new blank project on GitLab

A GitLab project is a container that holds your repository, merge requests, and issue tracker all in one place.

  • In GitLab, click the + icon in the top-left navigation bar.
  • Select New project/repository.
  • Click Create blank project.
  • In the Project name field, enter bicep-security-review.
  • The Project slug auto-fills to match the name. Leave it as-is.
  • Set the Visibility Level to Private.
  • Check the Initialize repository with a README checkbox.
  • Click Create project.

GitLab redirects you to your new project page. You should see a README.md file listed in the repository.

Why initialize with a README?

Initializing with a README creates the main branch and makes the repository cloneable immediately. Without it, the repository would be empty and you would need extra steps to set up the default branch.

Don't see the Create project button?

Make sure you selected Create blank project on the new project page, not one of the template options.

If the page asks for a group or namespace, select your personal namespace (your username) from the dropdown. Help me create my GitLab project.

Clone the repository to your machine

Cloning downloads a copy of the GitLab repository to your computer so you can edit files locally in VS Code.

  • On your project page in GitLab, click the blue Code button near the top-right.
  • Under Clone with HTTPS, click the copy icon to copy the URL.
  • Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows), type Terminal, and press Enter.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Clone the repository and navigate into it by running these commands:
git clone [[GITLAB_CLONE_URL="https://gitlab.com/your-username/bicep-security-review.git"]]
cd bicep-security-review

What does this command do?

git clone downloads the entire repository (including the README) to a new folder on your Desktop. cd bicep-security-review moves your terminal into that folder so all future commands run inside the project.

  • Confirm the clone worked by running:
ls

You should see README.md in the output.

Seeing 'repository not found' or authentication errors?

Double-check you copied the HTTPS URL (not SSH) from the Code dropdown. GitLab may prompt you for your username and password. Enter your GitLab username and a personal access token (or your password if you have not enabled 2FA).

Help me fix my git clone error.

Create and push the initial Bicep file

You need a starting point on the main branch so that later, when your "teammate" submits their insecure template on a feature branch, GitLab can show the difference.

  • Open the bicep-security-review folder in VS Code. Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), type File: Open Folder, and select the bicep-security-review folder on your Desktop.
  • Create a new file called main.bicep in the root of the project. Press Cmd+N (macOS) or Ctrl+N (Windows) to create a new file, then Cmd+S (macOS) or Ctrl+S (Windows) to save it as main.bicep.
  • Add the following placeholder content to main.bicep:
targetScope = 'resourceGroup'

// Infrastructure deployment template - placeholder

What does this code do?

targetScope = 'resourceGroup' tells Bicep that this template deploys resources inside an Azure resource group. The comment is a placeholder that your "teammate" will replace with the full infrastructure code in the next step.

  • Save main.bicep.
  • Commit and push the file to GitLab by running these commands in your terminal:
git add main.bicep
git commit -m "Add initial Bicep template placeholder"
git push

What do these commands do?

  • git add main.bicep stages the new file for commit.
  • git commit -m "..." saves a snapshot of your changes with a descriptive message.
  • git push uploads the commit to your GitLab repository's main branch.
  • Go back to your GitLab project page in the browser and refresh the page.

You should see main.bicep listed alongside the README.md in your project's file list.

Push rejected or permission denied?

If Git asks for credentials, enter your GitLab username and password (or personal access token if you have 2FA enabled). Make sure you are on the main branch by running git branch and confirming it shows * main.

Help me push my code to GitLab.

✔️ Awesome, I've got everything!

Great. Double check you have saved main.bicep and confirmed it appears on GitLab.

ⓧ I'd like to double check the full code

Here is the complete main.bicep file at this point:

targetScope = 'resourceGroup'

// Infrastructure deployment template - placeholder

Your main branch now has a baseline Bicep file. Next, you will create a feature branch and commit the full insecure template that your "teammate" wants reviewed.

Create the Feature Branch with the Insecure Template

Your GitLab repository is set up with a placeholder main.bicep on the main branch. Now it's time to simulate a teammate's code submission.

In a real team workflow, developers create feature branches to propose changes before merging them into main. You'll create a feature branch and populate it with a deliberately insecure Bicep template that contains 6 security flaws for you to review later.

In this step, get ready to:
  • Create a feature branch for the infrastructure code submission.
  • Add the insecure Bicep template with 6 deliberate vulnerabilities.
  • Push the branch to GitLab so it's ready for a merge request.
Create a new feature branch

Feature branches keep proposed changes separate from main until they've been reviewed and approved. Your branch name should describe what the code does.

  • In your terminal (still inside the bicep-security-review folder), create and switch to a new branch by running this command:
git checkout -b feature/infra-deploy
  • You should see the message: Switched to a new branch 'feature/infra-deploy'.

Seeing an error about the branch?

Make sure you're inside the bicep-security-review folder. Run pwd to check your current directory.

If you see "not a git repository", navigate into your cloned project folder first with cd bicep-security-review.

Help me fix my git checkout error

Add the insecure Bicep template

This template simulates what a teammate might submit for review. It deploys an Azure web app with a storage account, Key Vault, and role assignment. It works functionally, but contains security flaws that you'll catch during your review.

  • Open main.bicep in VS Code.
  • Select all the existing content (Cmd+A on macOS or Ctrl+A on Windows) and delete it.
  • Replace the file contents with the following insecure template:
targetScope = 'resourceGroup'

@description('The Azure region for all resources')
param location string = resourceGroup().location

@description('Environment name')
param environmentName string = 'dev'

@description('Administrator password for the application')
param adminPassword string

@description('The name prefix for all resources')
param namePrefix string = 'myapp'

// Storage Account
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: '${namePrefix}stor${environmentName}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    allowBlobPublicAccess: true
    minimumTlsVersion: 'TLS1_0'
  }
}

// Key Vault
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
  name: '${namePrefix}-kv-${environmentName}'
  location: location
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    accessPolicies: []
  }
}

// App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: '${namePrefix}-plan-${environmentName}'
  location: location
  sku: {
    name: 'B1'
    tier: 'Basic'
  }
}

// Web App
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
  name: '${namePrefix}-app-${environmentName}'
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      appSettings: [
        {
          name: 'STORAGE_CONNECTION_STRING'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}'
        }
        {
          name: 'ADMIN_PASSWORD'
          value: adminPassword
        }
      ]
    }
  }
}

// Role Assignment - grants Owner to a placeholder principal
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(resourceGroup().id, 'owner-assignment')
  properties: {
    principalId: 'placeholder-principal-id'
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')
    principalType: 'ServicePrincipal'
  }
}

// Outputs
output storageConnectionString string = 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}'
output keyVaultUri string = keyVault.properties.vaultUri
output adminPasswordOutput string = adminPassword

What does this template deploy?

This Bicep template defines five Azure resources: a Storage Account, a Key Vault, an App Service Plan, a Web App, and a Role Assignment. Together, they form a common web application deployment pattern.

The template is functional, but it contains 6 deliberate security vulnerabilities. You'll identify each one during your merge request review in a later step. For now, just paste it as-is.

  • Save main.bicep (Cmd+S on macOS or Ctrl+S on Windows).
  • Check VS Code's Bicep extension output. You should see resource icons next to each resource declaration and no red underline syntax errors. Yellow warnings about security issues are expected and confirm the extension is working correctly.

Seeing red squiggly underlines?

Red underlines indicate syntax errors. Double check that you copied the entire template, including the first line targetScope = 'resourceGroup' and the last line output adminPasswordOutput string = adminPassword.

Make sure the Bicep VS Code extension (published by Microsoft, extension ID ms-azuretools.vscode-bicep) is installed. Without it, VS Code won't understand .bicep files.

Help me fix Bicep syntax errors in VS Code

✔️ Awesome, I've got everything!

Great. Make sure you've saved main.bicep before continuing to the next substep.

ⓧ I'd like to double check the full code

Here's the complete main.bicep file. Make sure your file matches this exactly:

targetScope = 'resourceGroup'

@description('The Azure region for all resources')
param location string = resourceGroup().location

@description('Environment name')
param environmentName string = 'dev'

@description('Administrator password for the application')
param adminPassword string

@description('The name prefix for all resources')
param namePrefix string = 'myapp'

// Storage Account
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: '${namePrefix}stor${environmentName}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    allowBlobPublicAccess: true
    minimumTlsVersion: 'TLS1_0'
  }
}

// Key Vault
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
  name: '${namePrefix}-kv-${environmentName}'
  location: location
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    accessPolicies: []
  }
}

// App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: '${namePrefix}-plan-${environmentName}'
  location: location
  sku: {
    name: 'B1'
    tier: 'Basic'
  }
}

// Web App
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
  name: '${namePrefix}-app-${environmentName}'
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      appSettings: [
        {
          name: 'STORAGE_CONNECTION_STRING'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}'
        }
        {
          name: 'ADMIN_PASSWORD'
          value: adminPassword
        }
      ]
    }
  }
}

// Role Assignment - grants Owner to a placeholder principal
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(resourceGroup().id, 'owner-assignment')
  properties: {
    principalId: 'placeholder-principal-id'
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')
    principalType: 'ServicePrincipal'
  }
}

// Outputs
output storageConnectionString string = 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${storageAccount.listKeys().keys[0].value}'
output keyVaultUri string = keyVault.properties.vaultUri
output adminPasswordOutput string = adminPassword
Commit and push to GitLab

With the insecure template saved locally, you need to commit and push it to GitLab so it appears on your remote feature branch. This is what makes it available for a merge request review.

  • Stage, commit, and push your changes by running these commands:
git add main.bicep
git commit -m "Add infrastructure deployment template"
git push -u origin feature/infra-deploy

What do these commands do?

  • git add main.bicep stages the file for commit.
  • git commit -m "..." saves the staged changes as a snapshot with a descriptive message.
  • git push -u origin feature/infra-deploy uploads the branch to GitLab. The -u flag sets up tracking so future pushes only need git push.
  • After running the push command, you should see output that includes * [new branch] feature/infra-deploy -> feature/infra-deploy confirming your branch is now on GitLab.

GitLab may ask for credentials

If prompted for a username and password, enter your GitLab username and a personal access token (not your account password). GitLab requires a personal access token for HTTPS Git operations. You can create one at Settings > Access Tokens in your GitLab profile with the write_repository scope.

Push rejected or authentication failed?

If you see "remote: HTTP Basic: Access denied", you need a personal access token. Go to your GitLab profile, navigate to Settings > Access Tokens, create a token with write_repository scope, and use that token as your password when prompted.

If you see "failed to push some refs", make sure you're on the feature/infra-deploy branch by running git branch. The active branch should have an asterisk (*) next to it.

Help me fix my git push error

Your insecure template is now live on GitLab's feature/infra-deploy branch. Next up, you'll create a merge request so you can start reviewing the code like a security engineer would.

Create the Merge Request on GitLab

You have an insecure Bicep template sitting on a feature branch in GitLab. In a real team, that branch would never go straight to main.

Instead, a developer opens a merge request (MR) to propose changes for review. The MR becomes the central place for discussion, inline feedback, and approval before any code lands in production. In this step, you will create that MR and explore its interface so you know where to leave your security feedback.

In this step, get ready to:
  • Create a merge request from the feature branch into main.
  • Fill in a descriptive title and description for the MR.
  • Explore the MR interface tabs you will use during your security review.
Create a new merge request on GitLab

A merge request proposes merging your feature/infra-deploy branch into main. Once created, the MR page becomes the hub for code review conversations.

  • Open your bicep-security-review project in the GitLab web UI.
  • In the left sidebar, select Code, then Merge requests.
  • In the upper-right corner, click New merge request.
  • Under Source branch, select feature/infra-deploy.
  • Confirm the Target branch is set to main.
  • Click Compare branches and continue.

What is a merge request?

A merge request is a proposal to incorporate changes from a source branch into a target branch. It gives teammates a dedicated space to review the diff, leave inline comments, and approve (or request changes) before anything merges.

Fill in the merge request details

Good MR titles and descriptions help reviewers understand the purpose of the change at a glance.

  • In the Title field, enter Add infrastructure deployment template.
  • In the Description field, enter Adds Bicep template for deploying the web app infrastructure including storage, Key Vault, and role assignments.
  • Leave all other settings at their defaults.
  • Click Create merge request.

GitLab creates the MR and takes you to the MR Overview page.

Don't see feature/infra-deploy in the source branch list?

This means the branch was not pushed to GitLab. Go back to your terminal and run git push -u origin feature/infra-deploy. Then refresh the MR creation page and try again.

Help me troubleshoot why my branch is not showing on GitLab.

Explore the merge request interface

Before you start your security review in the next step, take a moment to understand the key areas of the MR page.

  • Click the Overview tab at the top of the MR page.
  • Notice the description you just wrote, plus an activity feed where future review comments will appear.
  • Click the Changes tab.
  • You should see a diff of main.bicep showing all the lines your feature branch added (highlighted in green).

What do the tabs do?

The Overview tab shows the MR description, assigned reviewers, labels, and a chronological activity feed. This is where review summaries and general comments appear.

The Changes tab shows a line-by-line diff of every file modified in the branch. You can click any line number to leave an inline comment directly on that piece of code.

Your merge request is live and ready for review. Next up, you will put on your security reviewer hat and identify every vulnerability hiding in that template.

Review the MR and Leave Security Comments

Your merge request is open and the diff is waiting for review. In a real team, a security-focused teammate would review this template before it reaches production. Today, you are that teammate.

Your goal is to read through the Bicep template line by line, spot all 6 security vulnerabilities, and leave clear inline comments explaining each issue. You will use GitLab's batch review feature to keep your comments private until you are ready to publish them all at once.

In this step, get ready to:
  • Start a batch review on the merge request Changes tab.
  • Leave 6 inline comments identifying distinct security vulnerabilities.
  • Submit the review with a summary comment.
Start a batch review

GitLab's batch review feature lets you draft multiple comments privately before publishing them in one go. This prevents your teammate from getting 6 separate email notifications and gives you a chance to revise your wording before anyone sees it.

  • In your merge request, select the Changes tab to open the diff view.
  • Scroll down to the adminPassword parameter line (around line 8 of the diff).
  • Hover over that line and click the comment icon (speech bubble) that appears in the left gutter.
  • In the comment box that opens, type the following review comment:
Security: This parameter accepts a password but has no `@secure()` decorator. Without it, the password value is logged in plaintext in the deployment history and visible in the Azure portal. Add `@secure()` above this parameter.
  • Below the comment box, click Start a review.

Why Start a review instead of Comment now?

Selecting Start a review keeps your comment in a pending state that only you can see. Selecting Comment now publishes immediately and sends a notification. Batch reviewing is the professional standard for code reviews because it lets you provide complete, cohesive feedback in a single notification.

You should see a badge appear at the top of the page showing 1 pending comment.

Don't see the comment icon in the gutter?

Make sure you are on the Changes tab, not the Overview tab. The inline comment gutter only appears in the diff view.

If you still cannot see it, try hovering directly over the line number on the left side of the diff.

Help me find the inline comment button on my GitLab MR

Comment on all 6 security issues

Now that your review is started, you will add 5 more inline comments. For each one, hover over the relevant line, click the comment icon, type your feedback, and click Add to review.

Here are the remaining 5 vulnerabilities to flag. Find each line in the diff and leave a comment.

Issue 2: Secrets exposed in outputs
  • Scroll to the bottom of the diff and find the output storageConnectionString line.
  • Click the comment icon on that line and type:
Security: This output exposes the storage account key via `listKeys()`. Outputs are stored in the deployment history and visible to anyone with read access to the resource group. The `adminPasswordOutput` below also leaks the password. Remove all secrets from outputs and expose only resource IDs or names instead.
  • Click Add to review.
Issue 3: Over-permissive role assignment
  • Find the roleDefinitionId line inside the roleAssignment resource.
  • Click the comment icon and type:
Security: This assigns the Owner role (`8e3af657-a8ff-443c-a75c-2fe8c4bcb635`), which grants full control over all resources in the scope. Follow least-privilege principles and assign a scoped data-plane role like Storage Blob Data Reader (`2a2b9908-6ea1-4ae2-8e65-a410df84e7d1`) instead.
  • Click Add to review.
Issue 4: Public blob access enabled
  • Find the allowBlobPublicAccess: true line inside the storage account resource.
  • Click the comment icon and type:
Security: Public blob access is enabled, meaning anyone on the internet could read blobs if a container is misconfigured. Set `allowBlobPublicAccess: false`. Also upgrade `minimumTlsVersion` from `TLS1_0` to `TLS1_2` to enforce modern encryption.
  • Click Add to review.
Issue 5: Hardcoded connection string instead of managed identity
  • Find the STORAGE_CONNECTION_STRING app setting inside the web app resource.
  • Click the comment icon and type:
Security: The storage account key is embedded directly in the app settings via `listKeys()`. If this web app is compromised, the attacker gets full storage access. Use a system-assigned managed identity on the web app and authenticate via Azure RBAC instead of connection strings. Also remove the hardcoded `ADMIN_PASSWORD` setting — store secrets in Key Vault.
  • Click Add to review.
Issue 6: No parameter validation
  • Find the param environmentName string = 'dev' line near the top of the diff.
  • Click the comment icon and type:
Security/Quality: These parameters have no validation decorators. Add `@allowed(['dev', 'staging', 'prod'])` to `environmentName` to prevent typos. Add `@minLength(3)` and `@maxLength(10)` to `namePrefix` to prevent naming collisions and Azure naming limit errors. Remove the default value from `namePrefix` to force the deployer to provide a deliberate choice.
  • Click Add to review.

What makes a good review comment?

Each comment follows a consistent pattern: state the category (Security), describe the risk in concrete terms, and recommend a specific fix. Vague comments like "this is bad" do not help the author improve.

In professional teams, reviewers often use conventional comment labels like Security:, Nit:, or Question: so the author can prioritize which feedback to address first.

You should now see a badge showing 6 pending review comments at the top of the merge request.

Submit the review

Your 6 comments are drafted and ready. Time to publish them all at once so your teammate sees a complete, organized review.

  • At the top right of the merge request, click Finish review.
  • A dialog opens with a Summary comment text area. Type the following summary:
This template has 6 security issues that must be addressed before merging: missing @secure() on password param, secrets leaked in outputs, over-permissive Owner role, public blob access enabled, hardcoded connection strings (should use managed identity), and no parameter validation. Please fix these and push an updated commit.
  • Leave the action set to Comment (the default option).
  • Click Submit review.

Why choose Comment instead of Request changes?

The Request changes option blocks the merge request from being merged until the reviewer approves. On GitLab's Free tier, this blocking behaviour is not enforced. Since you are both the author and reviewer in this project, Comment is the right choice. It still publishes your feedback without blocking yourself from merging later.

Your review is now published. Switch to the Overview tab to see all 6 comments and your summary appear in the activity feed.

Comments not appearing after submit?

Refresh the page. GitLab sometimes takes a moment to render newly published review threads.

If you see 0 comments in the activity, you may have accidentally clicked Comment now on individual comments instead of Add to review. In that case, the comments are already published individually. This is fine. Your feedback is still visible on the MR.

My GitLab review comments are not showing up after I submitted

Your security review is complete. You have identified 6 real-world vulnerabilities and communicated them clearly using GitLab's professional review workflow. Next up, you will fix every issue by pushing a hardened version of the template.

Push the Security-Hardened Fix and Merge

You have identified all 6 security vulnerabilities and left clear, actionable review comments on the merge request. Now it is time to put on your developer hat and fix every issue.

In this step, you will replace the insecure template with a hardened version that follows Microsoft-recommended security patterns. Then you will push the fix, resolve each review thread, and merge the MR to complete the review lifecycle.

In this step, get ready to:
  • Replace the insecure Bicep template with a hardened version fixing all 6 issues.
  • Commit and push the security fix to GitLab.
  • Resolve all review threads and merge the MR.
Replace main.bicep with the hardened template

The hardened template addresses each vulnerability you flagged in your review. It uses @secure() decorators, parameter validation, managed identities, least-privilege RBAC, disabled public access, and safe outputs.

  • In VS Code, open main.bicep from the Explorer sidebar.
  • Select all the existing content with Cmd+A (macOS) or Ctrl+A (Windows), then delete it.
  • Replace the entire file with this hardened version:
targetScope = 'resourceGroup'

@description('The Azure region for all resources')
param location string = resourceGroup().location

@description('Environment name')
@allowed([
  'dev'
  'staging'
  'prod'
])
param environmentName string = 'dev'

@description('Administrator password for the application')
@secure()
param adminPassword string

@description('The name prefix for all resources')
@minLength(3)
@maxLength(10)
param namePrefix string

@description('The principal ID to assign the storage role to')
param storagePrincipalId string

// Storage Account
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: '${namePrefix}stor${environmentName}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    allowBlobPublicAccess: false
    minimumTlsVersion: 'TLS1_2'
  }
}

// Key Vault
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
  name: '${namePrefix}-kv-${environmentName}'
  location: location
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    accessPolicies: []
    enableRbacAuthorization: true
  }
}

// App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: '${namePrefix}-plan-${environmentName}'
  location: location
  sku: {
    name: 'B1'
    tier: 'Basic'
  }
}

// Web App with Managed Identity
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
  name: '${namePrefix}-app-${environmentName}'
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      appSettings: [
        {
          name: 'STORAGE_ACCOUNT_NAME'
          value: storageAccount.name
        }
        {
          name: 'KEY_VAULT_URI'
          value: keyVault.properties.vaultUri
        }
      ]
    }
  }
}

// Role Assignment - grants Storage Blob Data Reader (least privilege)
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(resourceGroup().id, storagePrincipalId, 'storage-blob-reader')
  properties: {
    principalId: storagePrincipalId
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')
    principalType: 'ServicePrincipal'
  }
}

// Outputs - only non-sensitive resource identifiers
output storageAccountId string = storageAccount.id
output storageAccountName string = storageAccount.name
output keyVaultUri string = keyVault.properties.vaultUri
output webAppName string = webApp.name
output webAppIdentityPrincipalId string = webApp.identity.principalId

What changed in this hardened template?

This version fixes all 6 security issues you identified in your review:

  • @secure() added to adminPassword so the value is never logged or displayed in the Azure portal.
  • Outputs now expose only resource IDs and names. No listKeys() or password values leak into deployment history.
  • Role assignment changed from Owner to Storage Blob Data Reader for least-privilege access.
  • allowBlobPublicAccess set to false and minimumTlsVersion upgraded to TLS1_2.
  • Web app now uses a system-assigned managed identity instead of connection strings with embedded keys.
  • Parameter validation added: @allowed() on environment name, @minLength(3) and @maxLength(10) on the name prefix.
  • Save main.bicep.
  • Check the Problems panel at the bottom of VS Code (Cmd+Shift+M on macOS or Ctrl+Shift+M on Windows). You should see no errors or warnings from the Bicep extension.

Seeing Bicep linter errors?

Make sure you replaced the entire file content. A partial paste can leave leftover code from the insecure version that conflicts with the new parameters.

Check that the Bicep VS Code extension is installed and active. The extension ID is ms-azuretools.vscode-bicep.

Help me fix Bicep linter errors in my hardened template.

✔️ Awesome, I've got everything!

Great. Make sure you have saved main.bicep before moving on to the commit step.

ⓧ I'd like to double check the full code

Here is the complete hardened main.bicep for comparison:

targetScope = 'resourceGroup'

@description('The Azure region for all resources')
param location string = resourceGroup().location

@description('Environment name')
@allowed([
  'dev'
  'staging'
  'prod'
])
param environmentName string = 'dev'

@description('Administrator password for the application')
@secure()
param adminPassword string

@description('The name prefix for all resources')
@minLength(3)
@maxLength(10)
param namePrefix string

@description('The principal ID to assign the storage role to')
param storagePrincipalId string

// Storage Account
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: '${namePrefix}stor${environmentName}'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    allowBlobPublicAccess: false
    minimumTlsVersion: 'TLS1_2'
  }
}

// Key Vault
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
  name: '${namePrefix}-kv-${environmentName}'
  location: location
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    accessPolicies: []
    enableRbacAuthorization: true
  }
}

// App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
  name: '${namePrefix}-plan-${environmentName}'
  location: location
  sku: {
    name: 'B1'
    tier: 'Basic'
  }
}

// Web App with Managed Identity
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
  name: '${namePrefix}-app-${environmentName}'
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      appSettings: [
        {
          name: 'STORAGE_ACCOUNT_NAME'
          value: storageAccount.name
        }
        {
          name: 'KEY_VAULT_URI'
          value: keyVault.properties.vaultUri
        }
      ]
    }
  }
}

// Role Assignment - grants Storage Blob Data Reader (least privilege)
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(resourceGroup().id, storagePrincipalId, 'storage-blob-reader')
  properties: {
    principalId: storagePrincipalId
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')
    principalType: 'ServicePrincipal'
  }
}

// Outputs - only non-sensitive resource identifiers
output storageAccountId string = storageAccount.id
output storageAccountName string = storageAccount.name
output keyVaultUri string = keyVault.properties.vaultUri
output webAppName string = webApp.name
output webAppIdentityPrincipalId string = webApp.identity.principalId
Commit and push the fix

Your hardened template is saved locally. Now you need to commit it and push to GitLab so the merge request picks up the new code.

  • Stage, commit, and push your fix by running these commands in your terminal:
git add main.bicep
git commit -m "fix: harden template per security review"
git push

What do these commands do?

  • git add main.bicep stages the updated file for commit.
  • git commit -m "..." saves a snapshot with a descriptive message. The fix: prefix follows the Conventional Commits standard for bug fixes.
  • git push sends the commit to the remote feature/infra-deploy branch on GitLab. The MR automatically picks up the new commit.

You should see output ending with a line like To https://gitlab.com/... confirming the push succeeded.

Push rejected or authentication failed?

If GitLab asks for credentials, use your GitLab username and a Personal Access Token (not your password). You can create a token at GitLab > Edit Profile > Access Tokens with the write_repository scope.

Help me fix a git push authentication issue with GitLab.

Resolve review threads and merge the MR

The final step of the code review lifecycle is confirming each issue is fixed, then merging the hardened code into main.

  • Go back to your merge request in the GitLab web UI.
  • Click the Overview tab. You should see your new commit (fix: harden template per security review) listed in the activity feed.
  • Click the Changes tab to see the diff of your security fix.
  • For each of your 6 review comment threads, click Resolve thread to mark it as addressed.
  • Once all 6 threads show as resolved, go back to the Overview tab.
  • Click the Merge button to merge the hardened template into main.

The MR status changes to Merged. Your security-hardened template is now part of the main branch.

Why resolve threads before merging?

Resolving threads signals to the team that each piece of feedback has been addressed. On paid GitLab tiers, unresolved threads can block merges entirely. Getting into the habit of resolving threads keeps your review history clean and auditable.

Don't see the Merge button?

Make sure you are on the Overview tab of the merge request, not the Changes tab. The Merge button appears at the bottom of the Overview section.

If you see a message about pipeline status, scroll down past it. On the Free tier without CI configured, the Merge button should still be available.

Help me find and click the Merge button on my GitLab merge request.

Secret mission

Add a Virtual Network Restriction to the Storage Account

Your hardened template is solid, but there's one gap left. The storage account still accepts connections from any network on the internet. Can you lock it down to a specific virtual network subnet?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally and on GitLab's Free tier, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • GitLab.com account (Free tier, no cost).
  • GitLab project: bicep-security-review (private repository).
  • Local Git repository (bicep-security-review folder on your Desktop).
  • VS Code Bicep extension (ms-azuretools.vscode-bicep).

Keep everything running

No action needed. Choose this if you want to keep your GitLab project as a portfolio piece or reference for future Bicep security reviews.

  • Your GitLab project stays in your account at no cost.
  • Your local repository stays on your Desktop for easy access.
  • The Bicep VS Code extension remains installed for future use.

Pause - I'll come back to this later

Free up local disk space while keeping the remote project on GitLab.

  • Delete the local bicep-security-review folder from your Desktop.
  • Your GitLab project stays online. You can re-clone it anytime with git clone.
  • Optionally uninstall the Bicep extension from VS Code if you do not need it for other projects.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Delete the local bicep-security-review folder from your Desktop.
  • In GitLab, go to your project, then navigate to Settings > General.
  • Scroll to Advanced and expand it.
  • Scroll to Delete this project and follow the confirmation steps.
  • Optionally uninstall the Bicep VS Code extension by opening Extensions (Cmd+Shift+X), finding Bicep, and clicking Uninstall.
  • If you no longer need your GitLab account, go to User Settings > Account > Delete account.

Nice Work!

Nice Work!

You just completed a full security code review cycle on a Bicep infrastructure template using GitLab's merge request workflow.

You've learned how to:

  • Identify 6 common security vulnerabilities in a Bicep template through systematic code review, including missing @secure() decorators, exposed secrets in outputs, and over-permissive role assignments.
  • Execute the full GitLab merge request review workflow, from creating batch review comments to resolving threads and merging.
  • Harden a Bicep template using Microsoft-recommended security patterns including managed identities, least-privilege RBAC, parameter validation, and disabled public access.
  • Add network-level access restrictions to a storage account using virtual network rules and service endpoints.

Ready to quiz yourself?