Skip to content

[AzureMonitorAutoConfigure] Prevent local symlink/path squatting on the telemetry spool - #50463

Merged
xiang17 merged 12 commits into
mainfrom
xiang17/Local-Symlink-Path-check
Sep 17, 2026
Merged

xiang17 merged 12 commits into
mainfrom
xiang17/Local-Symlink-Path-check

Conversation

@xiang17

@xiang17 xiang17 commented Sep 15, 2026

Copy link
Copy Markdown
Member

Problem

azure-monitor-opentelemetry-autoconfigure spools offline telemetry to
<java.io.tmpdir>/<user>/applicationinsights/telemetry, created with
File.mkdirs(). That call follows symlinks, applies the process umask, and
performs no ownership check — only canRead()/canWrite(), which test
access, not ownership. On a shared host, a local unprivileged user can:

  1. Squat or symlink that path before the real application starts, causing the
    SDK to create/use the spool tree inside a directory it doesn't own, and
    plant a .trn file whose embedded connection string points at a host they
    control — the victim's managed identity then mints a fresh Entra token and
    delivers it to the attacker's host.
  2. Read the plaintext connection string out of spooled files, since neither
    the spool directory nor its files were ever restricted to the owner.

Fix

  • TempDirs now creates the spool directory (and every intermediate
    directory) with Files.createDirectory, rejects any directory that is a
    symlink, and verifies each directory is owned by the current user before
    using it. Directories end up owner-only (POSIX 0700, or a Windows ACL
    restricted to the owner) — verified equivalent on both platforms, since
    ownership/symlink checks use the same cross-platform NIO2 APIs and only the
    final permission-restriction step differs by OS.
  • LocalFileWriter writes spool files with owner-only (0600) permissions on
    POSIX filesystems.
  • FileUtil.listTrnFiles ignores .trn files that are themselves symlinks.

Because the spool directory is now guaranteed owner-only from the moment it's
created or first validated, no other local user can ever write a file into it
— which removes the ability to plant a .trn file in the first place, closing
both the token-theft and disclosure directions described in the report.

Testing

  • TempDirsTests: owner-only permissions, symlink rejection (both the target
    directory and an intermediate directory in the path).
  • Verified against the reported repro: planting a .trn file with an
    attacker-controlled IngestionEndpoint, and pre-creating the spool directory
    as a symlink, are both rejected — the SDK refuses to use the directory
    before ever touching its contents.

All SDK Contribution checklist:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, see this page.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

Copilot AI lite review requested due to automatic review settings September 15, 2026 13:39
@github-actions github-actions Bot added the Monitor - Autoconfigure Monitor OpenTelemetry Autoconfigure label Sep 15, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
35 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

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.

🟡 Changes recommended

Critical endpoint-routing and parent-path symlink issues remain, along with unresolved security-test and Checkstyle nits.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR hardens Azure Monitor offline telemetry spooling and authenticated export against symlink/path-squatting risks and untrusted destinations.

Changes:

  • Creates owner-only spool directories/files and rejects symlinked telemetry files.
  • Validates authenticated request destinations.
  • Adds endpoint exposure, tests, Checkstyle configuration, and changelog updates.
File summaries
File Summary
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/TempDirsTests.java Tests directory security. Nit (1 vote): add Windows-compatible ACL and symlink coverage.
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/AuthenticatedEndpointPolicyTests.java Tests endpoint rejection. Nit (1 vote): remove the unused assertThat import, which violates UnusedImports.
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/TempDirs.java Secures directory creation and ownership. Critical (2 votes): validate every ancestor of the configured temp path to prevent parent symlink traversal. Nit (1 vote): add Windows/ACL-capable security coverage.
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/AuthenticatedEndpointPolicy.java Validates authenticated request destinations.
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/localstorage/LocalFileWriter.java Creates POSIX spool files with owner-only permissions. Nit (1 vote): add a POSIX-gated permission assertion.
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/localstorage/FileUtil.java Ignores symlinked .trn files. Nit (1 vote): add test coverage for this behavior.
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/configuration/ConnectionString.java Exposes the ingestion endpoint as a URL.
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/AzureMonitorExporterBuilder.java Adds endpoint validation before bearer authentication. Critical (2 votes): the shared pipeline can reject configured Live Metrics endpoints; allow the trusted LiveEndpoint through a separate policy or pipeline.
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/checkstyle-suppressions.xml Adds the policy-specific Checkstyle suppression.
sdk/monitor/azure-monitor-opentelemetry-autoconfigure/CHANGELOG.md Documents the security fixes.
Review details

Suppressed comments (6)

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/localstorage/FileUtil.java:23

  • The new symlink exclusion has no coverage in the local-storage tests. Add a case with a .trn symlink alongside a regular .trn and verify that listing/loading ignores the symlink; otherwise this security behavior can regress unnoticed.
                .filter(file -> Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS))

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/localstorage/LocalFileWriter.java:117

  • The new POSIX 0600 creation path is not asserted by the existing LocalFileWriterTests; those tests only verify cache counts. A regression to default or umask-dependent permissions would still pass, so add a POSIX-gated assertion on the persisted .trn file mode.
        if (Files.getFileStore(telemetryFolder.toPath()).supportsFileAttributeView("posix")) {
            return Files
                .createTempFile(telemetryFolder.toPath(), prefix, null,
                    PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-------")))

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/TempDirs.java:122

  • The new ACL implementation is never exercised by these tests: every test assumes POSIX support, so Windows/non-POSIX providers skip the branch that implements the promised owner-only ACL behavior. Add a Windows/ACL-capable test that verifies the created directory is inaccessible to non-owner principals, otherwise regressions in this security-critical branch can pass CI unnoticed.
        } else {
            restrictAclToOwner(directory, owner);
        }

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/TempDirs.java:82

  • These checks only inspect the final parent component. If an ancestor is a symbolic link (for example, a configured java.io.tmpdir under a symlinked directory), Files.isSymbolicLink(parent) is false and isDirectory(..., NOFOLLOW_LINKS) still follows that ancestor, so the subsequent create/use operations can escape the intended path. Validate every component from the filesystem root through the parent before proceeding.
        Path parent = directory.getParent();
        if (parent == null || Files.isSymbolicLink(parent) || !Files.isDirectory(parent, LinkOption.NOFOLLOW_LINKS)) {
            throw new IOException("Directory parent is missing, invalid, or a symbolic link: " + parent);

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/AuthenticatedEndpointPolicyTests.java:21

  • assertThat is imported but never referenced in this new test. Track 2 Checkstyle enables UnusedImports, so this test will fail the module check; remove the unused static import.
import static org.assertj.core.api.Assertions.assertThat;

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/TempDirsTests.java:25

  • All tests in this class are guarded by POSIX support, so the Windows branch is skipped entirely. That leaves the new restrictAclToOwner implementation and Windows owner-only behavior without coverage; add Windows-compatible ACL assertions and symlink cases instead of making the whole class POSIX-only.
        assumeTrue(Files.getFileStore(tempDir).supportsFileAttributeView("posix"));
  • Files reviewed: 10/10 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Copilot AI left a comment

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.

🟡 Changes recommended

Endpoint validation remains incomplete, intermediate symlinks are not fully rejected, and the promised authentication policy is absent.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/TempDirs.java:68

  • This only checks the final java.io.tmpdir entry. If the configured path is /safe/link/tmp and link is a symlink, both checks pass and all subsequent operations follow that intermediate link, contrary to the stated guarantee that no component may be a symlink. Walk and validate every component of the shared temp path with NOFOLLOW_LINKS before creating descendants.
            || Files.isSymbolicLink(normalizedSharedTempDirectory)
            || !Files.isDirectory(normalizedSharedTempDirectory, LinkOption.NOFOLLOW_LINKS)) {
  • Files reviewed: 14/14 changed files
  • Comments generated: 5
  • Review effort level: Balanced

@xiang17
xiang17 merged commit 68765e0 into main Sep 17, 2026
26 checks passed
@xiang17
xiang17 deleted the xiang17/Local-Symlink-Path-check branch September 17, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Monitor - Autoconfigure Monitor OpenTelemetry Autoconfigure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants