Skip to content

Security Enforcement for Trusted Registries: Only allow module restore from trusted registries#19395

Merged
jiangmingzhe merged 11 commits into
Azure:mainfrom
jiangmingzhe:mingzhejiang/module-restore-guard
May 1, 2026
Merged

Security Enforcement for Trusted Registries: Only allow module restore from trusted registries#19395
jiangmingzhe merged 11 commits into
Azure:mainfrom
jiangmingzhe:mingzhejiang/module-restore-guard

Conversation

@jiangmingzhe
Copy link
Copy Markdown
Member

@jiangmingzhe jiangmingzhe commented Apr 11, 2026

Description: Block credential exfiltration via untrusted OCI registry restore

Problem

Opening an untrusted .bicep file triggers automatic OCI module restore, which sends Azure credentials to any referenced registry — including attacker-controlled ones — via the authenticate challenge. Simply opening a file is sufficient to exfiltrate credentials.

Solution

A registry trust allowlist enforced before any network I/O. Restore is blocked entirely for registries not on the list — no connection, no credential challenge.

Built-in trusted registries (*.azurecr.io, *.azurecr.cn, *.azurecr.us, mcr.microsoft.com, mcr.azure.cn, ghcr.io) are hardcoded. Users extend the list via security.trustedRegistries in bicepconfig.json.

Two new diagnostics: BCP446 (registry not trusted) and BCP447 (invalid pattern in config). Invalid patterns fail closed — all restore blocked until fixed.

Trust is checked against the literal hostname string in the source file. No DNS resolution is performed, preventing DNS rebinding attacks.

Checklist

Microsoft Reviewers: Open in CodeFlow

Comment thread src/Bicep.Core/Configuration/SecurityConfiguration.cs
/// Validates a single trusted registry pattern string.
/// Returns null if the pattern is valid, or an error message string if it is invalid.
/// </summary>
public static string? ValidateRegistryPattern(string pattern)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are there any libraries we can use for this validation and host matching?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I haven't found any library that can do the matching out of the box yet. I checked the System.Uri library but it requires a scheme (like "https://") to construct the hostname and doesn't accept ".azurecr.io" hostnames with wildcards. Also some of the hostname match checks make it hard to adopt a library, and manual checks may still be needed. For example:
Accept "
.azurecrl.io" and reject "*.com",
Match "contoso.azurecr.io" against *.azurecr.io", but does not match "azurecr.io" against *.azurecr.io"

/// <see cref="GetConfiguration"/> re-scan the file system. Useful in tests where the
/// virtual file system is modified between compilation runs.
/// </summary>
void PurgeLookupCache() { }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder whether we can avoid exposing implementation details through the interface. If the test needs to reset the configuration cache, we could instead cast the interface to ConfigurationManager. A better approach might be to inject IConcurrentCache into ConfigurationManager, and let the tests clear the cache via IConcurrentCache.TryRemove.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure, I have removed the exposing API from the interface and instead make changes in the test code to solve the stall cache issue for parallel testcases running.

@jiangmingzhe jiangmingzhe force-pushed the mingzhejiang/module-restore-guard branch from eec9fc1 to ea1f9a4 Compare April 20, 2026 06:58
? module.Registry[..module.Registry.IndexOf(':')] // "localhost:5000" → "localhost"
: module.Registry;
var publishSecurityJson = JsonDocument.Parse(
$"{{\"trustedRegistries\":[\"{registryHostOnly}\"]}}").RootElement;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

JsonDocument implements IDisposable and may lead to memory not being released promptly if not disposed. We may need to change all JsonDocument.Parse().RootElement to use JsonElementFactory.CreateElement instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure, I will fix this

@jiangmingzhe jiangmingzhe force-pushed the mingzhejiang/module-restore-guard branch from 9a85323 to 62e06f3 Compare April 30, 2026 23:01
Copy link
Copy Markdown
Contributor

@shenglol shenglol left a comment

Choose a reason for hiding this comment

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

:shipit:

@jiangmingzhe jiangmingzhe merged commit 6713a4c into Azure:main May 1, 2026
41 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Test this change out locally with the following install scripts (Action run 25198811164)

VSCode
  • Mac/Linux
    bash <(curl -Ls https://aka.ms/bicep/nightly-vsix.sh) --run-id 25198811164
  • Windows
    iex "& { $(irm https://aka.ms/bicep/nightly-vsix.ps1) } -RunId 25198811164"
Azure CLI
  • Mac/Linux
    bash <(curl -Ls https://aka.ms/bicep/nightly-cli.sh) --run-id 25198811164
  • Windows
    iex "& { $(irm https://aka.ms/bicep/nightly-cli.ps1) } -RunId 25198811164"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants