Replaces the static-only ExportRoot / env-var-only configuration in NodeSetExportTools with an instance-aware ResolveExportRoot(IServiceProvider) that consults McpServerOptions from DI first. Same pattern applied to PacketDecodeTools.GetDecodeAllowedRoot for symmetric naming.
Precedence (both options): services.GetService<McpServerOptions>().Property > OPCUA_MCP_EXPORT_ROOT env var (NodeSet) or PcapOptions.BaseFolder (Decode) > per-tool default. Backward-compat: ExportRoot static property + OPCUA_MCP_EXPORT_ROOT env var both still work.
Program.cs now seeds McpServerOptions from OPCUA_MCP_NODESET_EXPORT_ROOT and OPCUA_MCP_PCAP_BASE_FOLDER environment variables so hosts can override without code changes.
Tests: 7 NUnit tests in McpServerOptionsTests covering DI > env var > default precedence for both options. Reflection-based to reach internal/private helpers — same pattern as the existing PacketDecodePathValidationTests.
Build: 0 warnings / 0 errors on Applications/McpServer for net10.0. Pcap.Tests build blocked locally by the unrelated SDK 10 workload manifest issue (see prior commit for details).
ExportNodeSetAsync.filePath and ExportNodeSetPerNamespaceAsync.
outputDirectory are LLM-supplied MCP tool arguments. The old code
passed them directly to Directory.CreateDirectory and to
new FileStream(path, FileMode.Create) with no canonicalization or
allowlist. A prompt-injected LLM call could overwrite arbitrary files
the MCP-server process can write to.
Add a ResolveExportPath helper that:
- rejects null/whitespace
- resolves the request via Path.GetFullPath (so .. segments cannot
escape after canonicalization)
- rejects any candidate that would resolve outside the export root
(relative or already-absolute paths must both end up inside)
- returns the canonicalized absolute path
ExportRoot:
- defaults to {Path.GetTempPath}/Opc.Ua.Mcp/exports
- overridable via the OPCUA_MCP_EXPORT_ROOT environment variable
set before the MCP server starts
- cached via Lazy<string> with ExecutionAndPublication semantics
- exposed publicly as NodeSetExportTools.ExportRoot for callers
and tests
Both ExportNodeSetAsync and ExportNodeSetPerNamespaceAsync now call
ResolveExportPath first; if the LLM supplied an out-of-root path,
the call throws ArgumentException before any filesystem write
occurs.
Activity-level success records continue to include the (resolved)
filePath so the MCP caller still sees where the file landed.
Verified Applications/McpServer/Opc.Ua.Mcp.csproj builds clean
(net10.0): 0 W, 0 E.
* New certificate and secrete abstraction with certificate infrastructure and certificate/secret stores.
* leak testing for certificate resources to validate new ref counting in every test assembly
* Migration guide updated and documentation added for all
* Remove IDisposable from Nodestate and UserIdentity.
* UserCertificateTokenHandler now just points into certificate registry/stores, no dispose required
* Fix CA2000 warnings and cleanup of other diagnostics, increase warning levels in .editorconfig