[AzureMonitorAutoConfigure] Prevent local symlink/path squatting on the telemetry spool - #50463
Conversation
|
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. |
There was a problem hiding this comment.
🟡 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
.trnsymlink alongside a regular.trnand 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.trnfile 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
parentcomponent. If an ancestor is a symbolic link (for example, a configuredjava.io.tmpdirunder a symlinked directory),Files.isSymbolicLink(parent)is false andisDirectory(..., 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
assertThatis imported but never referenced in this new test. Track 2 Checkstyle enablesUnusedImports, 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
restrictAclToOwnerimplementation 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.
There was a problem hiding this comment.
🟡 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.tmpdirentry. If the configured path is/safe/link/tmpandlinkis 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 withNOFOLLOW_LINKSbefore creating descendants.
|| Files.isSymbolicLink(normalizedSharedTempDirectory)
|| !Files.isDirectory(normalizedSharedTempDirectory, LinkOption.NOFOLLOW_LINKS)) {
- Files reviewed: 14/14 changed files
- Comments generated: 5
- Review effort level: Balanced
Problem
azure-monitor-opentelemetry-autoconfigurespools offline telemetry to<java.io.tmpdir>/<user>/applicationinsights/telemetry, created withFile.mkdirs(). That call follows symlinks, applies the process umask, andperforms no ownership check — only
canRead()/canWrite(), which testaccess, not ownership. On a shared host, a local unprivileged user can:
SDK to create/use the spool tree inside a directory it doesn't own, and
plant a
.trnfile whose embedded connection string points at a host theycontrol — the victim's managed identity then mints a fresh Entra token and
delivers it to the attacker's host.
the spool directory nor its files were ever restricted to the owner.
Fix
TempDirsnow creates the spool directory (and every intermediatedirectory) with
Files.createDirectory, rejects any directory that is asymlink, and verifies each directory is owned by the current user before
using it. Directories end up owner-only (POSIX
0700, or a Windows ACLrestricted 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.
LocalFileWriterwrites spool files with owner-only (0600) permissions onPOSIX filesystems.
FileUtil.listTrnFilesignores.trnfiles 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
.trnfile in the first place, closingboth the token-theft and disclosure directions described in the report.
Testing
TempDirsTests: owner-only permissions, symlink rejection (both the targetdirectory and an intermediate directory in the path).
.trnfile with anattacker-controlled
IngestionEndpoint, and pre-creating the spool directoryas a symlink, are both rejected — the SDK refuses to use the directory
before ever touching its contents.
All SDK Contribution checklist:
General Guidelines and Best Practices
Testing Guidelines